Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,6 @@ xcuserdata/
# Claude Config File
**/.claude/settings.local.json
CLAUDE.md

# incremental builds
Makefile
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
## [v1.3.7] - 2025-05-08
- Fix Claude Code issue due to long tool names

## [v1.4.0-beta.3] - 2025-05-07
- Fixed issue where incremental builds would only work for "Debug" build configurations
-
## [v1.4.0-beta.2] - 2025-05-07
- Same as beta 1 but has the latest features from the main release channel

## [v1.4.0-beta.1] - 2025-05-05
- Added experimental support for incremental builds (requires opt-in)

## [v1.3.6] - 2025-05-07
- Added support for enabling/disabling tools via environment variables

Expand Down
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The XcodeBuildMCP server provides the following tool capabilities:
- **Build Operations**: Platform-specific build tools for macOS, iOS simulator, and iOS device targets
- **Project Information**: Tools to list schemes and show build settings for Xcode projects and workspaces
- **Clean Operations**: Clean build products using xcodebuild's native clean action
- **Incremental build support**: Lightning fast builds using incremental build support (experimental, opt-in required)

### Simulator management
- **Simulator Control**: List, boot, and open iOS simulators
Expand Down Expand Up @@ -104,7 +105,7 @@ Configure your MCP client (Windsurf, Cursor, Claude Desktop, etc.) to use the Xc
"command": "mise",
"args": [
"x",
"npm:xcodebuildmcp@1.3.8",
"npm:xcodebuildmcp@1.4.0-beta.4",
"--",
"xcodebuildmcp"
]
Expand All @@ -129,14 +130,14 @@ To generate
let obj = {
"name": "XcodeBuildMCP",
"command": "mise",
"args": [ "x", "npm:xcodebuildmcp@1.3.8", "--", "xcodebuildmcp"]
"args": [ "x", "npm:xcodebuildmcp@1.4.0-beta.4", "--", "xcodebuildmcp"]
}

// For Insiders, use `vscode-insiders` instead of `code`
const link = encodeURIComponent(`vscode:mcp/install?${(JSON.stringify(obj))}`);
``` -->

[<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22command%22%3A%22mise%22%2C%22args%22%3A%5B%22x%22%2C%22npm%3Axcodebuildmcp%401.3.8%22%2C%22--%22%2C%22xcodebuildmcp%22%5D%7D) [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22command%22%3A%22mise%22%2C%22args%22%3A%5B%22x%22%2C%22npm%3Axcodebuildmcp%401.3.8%22%2C%22--%22%2C%22xcodebuildmcp%22%5D%7D)
[<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22command%22%3A%22mise%22%2C%22args%22%3A%5B%22x%22%2C%22npm%3Axcodebuildmcp%401.4.0-beta.4%22%2C%22--%22%2C%22xcodebuildmcp%22%5D%7D) [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22command%22%3A%22mise%22%2C%22args%22%3A%5B%22x%22%2C%22npm%3Axcodebuildmcp%401.4.0-beta.4%22%2C%22--%22%2C%22xcodebuildmcp%22%5D%7D)


### Enabling UI Automation (beta)
Expand All @@ -162,8 +163,32 @@ pipx install fb-idb==1.1.7

## Incremental build support

XcodeBuildMCP now includes experimental incremental build support in version 1.4.0-beta, dramatically reducing build times after the first full compile. This can significantly speed up your development workflow. Get started here:
https://github.com/cameroncooke/XcodeBuildMCP/tree/1.4.0-beta?tab=readme-ov-file#incremental-build-support
XcodeBuildMCP includes experimental support for incremental builds. This feature is disabled by default and can be enabled by setting the `INCREMENTAL_BUILDS_ENABLED` environment variable to `true`:

To enable incremental builds, set the `INCREMENTAL_BUILDS_ENABLED` environment variable to `true`:

Example MCP client configuration:
```bash
{
"mcpServers": {
"XcodeBuildMCP": {
"command": "mise",
"args": [
"x",
"npm:xcodebuildmcp@1.4.0-beta.4",
"--",
"xcodebuildmcp"
],
"env": {
"INCREMENTAL_BUILDS_ENABLED": "true"
}
}
}
}
```

> [!IMPORTANT]
> Please note that incremental builds support is currently highly experimental and your mileage may vary. Please report any issues you encounter to the [issue tracker](https://github.com/cameroncooke/XcodeBuildMCP/issues).

## Troubleshooting

Expand All @@ -177,14 +202,14 @@ The diagnostic tool is a standalone utility that checks your system configuratio

```bash
# Run the diagnostic tool using mise
mise x npm:xcodebuildmcp@1.3.8 -- xcodebuildmcp-diagnostic
mise x npm:xcodebuildmcp@1.4.0-beta.4 -- xcodebuildmcp-diagnostic
```

#### Using with npx

```bash
# Run the diagnostic tool using npx
npx xcodebuildmcp@1.3.8 xcodebuildmcp-diagnostic
npx xcodebuildmcp@1.4.0-beta.4 xcodebuildmcp-diagnostic
```

The diagnostic tool will output comprehensive information about:
Expand Down Expand Up @@ -227,7 +252,7 @@ Example MCP client configuration:
"command": "mise",
"args": [
"x",
"npm:xcodebuildmcp@1.3.8",
"npm:xcodebuildmcp@1.4.0-beta.4",
"--",
"xcodebuildmcp"
],
Expand All @@ -252,7 +277,7 @@ Once you have enabled one or more tools or groups of tools all other tools will
"command": "mise",
"args": [
"x",
"npm:xcodebuildmcp@1.3.8",
"npm:xcodebuildmcp@1.4.0-beta.4",
"--",
"xcodebuildmcp"
],
Expand Down
2 changes: 1 addition & 1 deletion TOOL_OPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Here is a fully worked example of how to configure Cursor/Windsurf to use specif
"command": "mise",
"args": [
"x",
"npm:xcodebuildmcp@1.3.8",
"npm:xcodebuildmcp@1.4.0-beta.4",
"--",
"xcodebuildmcp"
],
Expand Down
4 changes: 2 additions & 2 deletions example_projects/iOS/MCPTest/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI
import OSLog

struct ContentView: View {
@State private var text = ""
@State private var text: String = ""

var body: some View {
VStack {
Expand All @@ -24,7 +24,7 @@ struct ContentView: View {
Button("Log something") {
let message = ProcessInfo.processInfo.environment.map { "\($0.key): \($0.value)" }.joined(separator: "\n")
Logger.myApp.debug("Environment: \(message)")
debugPrint("Button was pressed")
debugPrint("Button was pressed.")

text = "You just pressed the button!"
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xcodebuildmcp",
"version": "1.3.8",
"version": "1.4.0-beta.4",
"main": "build/index.js",
"type": "module",
"bin": {
Expand Down
112 changes: 96 additions & 16 deletions release.sh
Original file line number Diff line number Diff line change
@@ -1,46 +1,126 @@
#!/bin/bash
set -e

# Usage: ./release.sh 1.2.3
# Usage: ./release.sh <version> [npm-tag] [--dry-run]
VERSION=$1
DRY_RUN=false
NPM_TAG_SPECIFIED=false
NPM_TAG="latest"

# Validate version format
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?$ ]]; then
echo "❌ Invalid version format: $VERSION"
echo "Version must be in format: x.y.z or x.y.z-tag.n (e.g., 1.4.0 or 1.4.0-beta.3)"
exit 1
fi

# Set default tag based on version format
if [[ "$VERSION" =~ -beta ]]; then
NPM_TAG="beta"
elif [[ "$VERSION" =~ -alpha ]]; then
NPM_TAG="alpha"
elif [[ "$VERSION" =~ -rc ]]; then
NPM_TAG="rc"
elif [[ "$VERSION" =~ -experimental ]]; then
NPM_TAG="experimental"
fi

# Check for arguments and set flags
for arg in "$@"; do
if [[ "$arg" == "--dry-run" ]]; then
DRY_RUN=true
elif [[ "$arg" != "$VERSION" && "$arg" != "--dry-run" ]]; then
# If argument is not the version and not --dry-run, treat it as the npm tag
NPM_TAG="$arg"
NPM_TAG_SPECIFIED=true
fi
done

if [ -z "$VERSION" ]; then
echo "Usage: $0 <version>"
echo "Usage: $0 <version> [npm-tag] [--dry-run]"
exit 1
fi

# Detect current branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Enforce branch/tag policy (customize as needed)
if [[ "$BRANCH" == "main" && "$NPM_TAG" != "latest" && "$NPM_TAG_SPECIFIED" == false ]]; then
echo "⚠️ Warning: Publishing a non-latest tag from main branch."
echo "Continue? (y/n)"
read -r CONTINUE
if [[ "$CONTINUE" != "y" ]]; then
echo "❌ Release cancelled."
exit 1
fi
fi

if [[ "$BRANCH" != "main" && "$NPM_TAG" == "latest" ]]; then
echo "⚠️ Warning: Publishing with tag '$NPM_TAG' from non-main branch."
echo "Continue? (y/n)"
read -r CONTINUE
if [[ "$CONTINUE" != "y" ]]; then
echo "❌ Release cancelled."
exit 1
fi
fi

run() {
if $DRY_RUN; then
echo "[dry-run] $*"
else
eval "$@"
fi
}

# Version update
echo ""
echo "🔧 Setting version to $VERSION..."
npm version "$VERSION" --no-git-tag-version
run "npm version \"$VERSION\" --no-git-tag-version"

# README update
echo ""
echo "📝 Updating version in README.md..."
# Update version references in code examples using extended regex for precise semver matching
sed -i '' -E "s/@[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?/@$VERSION/g" README.md
run "sed -i '' -E 's/@[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?/@'"$VERSION"'/g' README.md"

# Update URL-encoded version references in shield links
echo "📝 Updating version in README.md shield links..."
sed -i '' -E "s/npm%3Axcodebuildmcp%40[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?/npm%3Axcodebuildmcp%40$VERSION/g" README.md
run "sed -i '' -E 's/npm%3Axcodebuildmcp%40[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?/npm%3Axcodebuildmcp%40'"$VERSION"'/g' README.md"

echo ""
echo "📝 Updating version in TOOL_OPTIONS.md..."
sed -i '' -E "s/@[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?/@$VERSION/g" TOOL_OPTIONS.md
run "sed -i '' -E 's/@[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?/@'"$VERSION"'/g' TOOL_OPTIONS.md"

echo "🛠 Running build..."
npm run build
# Build
echo ""
echo "🛠 Running build..."
run "npm run build"

# Git operations
echo ""
echo "📦 Committing changes..."
git add .
git commit -m "Release v$VERSION"
git tag "v$VERSION"
run "git add ."
run "git commit -m \"Release v$VERSION\""
run "git tag \"v$VERSION\""

echo ""
echo "🚀 Pushing to origin..."
git push origin main --tags
run "git push origin $BRANCH --tags"

echo "📦 Creating GitHub release..."
gh release create "v$VERSION" --generate-notes -t "Release v$VERSION"
if [[ "$NPM_TAG" == "beta" || "$NPM_TAG" == "alpha" || "$NPM_TAG" == "rc" || "$NPM_TAG" == "experimental" ]]; then
run "gh release create "v$VERSION" --generate-notes -t \"Release v$VERSION\" --prerelease"
else
run "gh release create "v$VERSION" --generate-notes -t \"Release v$VERSION\""
fi

echo "📤 Publishing to npm..."
npm publish
# npm publish
echo ""
echo "📤 Publishing to npm with tag '$NPM_TAG'..."
run "npm publish --tag $NPM_TAG"

echo "✅ Release v$VERSION complete!"
# Completion message
echo ""
echo "✅ Release v$VERSION complete!"
echo "📝 Don't forget to update the changelog"
19 changes: 19 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,30 @@ import { log } from './utils/logger.js';
import { version } from './version.js';
import { registerTools } from './utils/register-tools.js';

// Import xcodemake utilities
import { isXcodemakeEnabled, isXcodemakeAvailable } from './utils/xcodemake.js';

/**
* Main function to start the server
*/
async function main(): Promise<void> {
try {
// Check if xcodemake is enabled and available
if (isXcodemakeEnabled()) {
log('info', 'xcodemake is enabled, checking if available...');
const available = await isXcodemakeAvailable();
if (available) {
log('info', 'xcodemake is available and will be used for builds');
} else {
log(
'warn',
'xcodemake is enabled but could not be made available, falling back to xcodebuild',
);
}
} else {
log('debug', 'xcodemake is disabled, using standard xcodebuild');
}

// Create the server
const server = createServer();

Expand Down
8 changes: 5 additions & 3 deletions src/tools/build_ios_device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ import {
extraArgsSchema,
BaseWorkspaceParams,
BaseProjectParams,
preferXcodebuildSchema,
} from './common.js';

// --- Parameter Type Definitions (Specific to iOS Device Build) ---
// None needed currently, using base types

// --- Tool Registration Functions ---

/**
Expand All @@ -47,6 +45,7 @@ export function registerIOSDeviceBuildWorkspaceTool(server: McpServer): void {
configuration: configurationSchema,
derivedDataPath: derivedDataPathSchema,
extraArgs: extraArgsSchema,
preferXcodebuild: preferXcodebuildSchema,
},
async (params: Params) => {
const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath);
Expand All @@ -64,6 +63,7 @@ export function registerIOSDeviceBuildWorkspaceTool(server: McpServer): void {
platform: XcodePlatform.iOS,
logPrefix: 'iOS Device Build',
},
params.preferXcodebuild,
'build',
);
},
Expand All @@ -85,6 +85,7 @@ export function registerIOSDeviceBuildProjectTool(server: McpServer): void {
configuration: configurationSchema,
derivedDataPath: derivedDataPathSchema,
extraArgs: extraArgsSchema,
preferXcodebuild: preferXcodebuildSchema,
},
async (params: Params) => {
const projectValidation = validateRequiredParam('projectPath', params.projectPath);
Expand All @@ -102,6 +103,7 @@ export function registerIOSDeviceBuildProjectTool(server: McpServer): void {
platform: XcodePlatform.iOS,
logPrefix: 'iOS Device Build',
},
params.preferXcodebuild,
'build',
);
},
Expand Down
Loading