From 0d097fce2ec0e48f68a7940440d31eaf712727be Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 3 Feb 2026 12:45:37 +0000 Subject: [PATCH 1/5] Improve tool analysis and docs --- README.md | 28 +- docs/CLI.md | 254 +++++++-------- docs/GETTING_STARTED.md | 18 +- docs/TOOLS-CLI.md | 180 +++++++++++ docs/TOOLS.md | 85 +++++- package.json | 3 +- scripts/analysis/tools-analysis.ts | 233 ++++++++++---- scripts/generate-tools-manifest.ts | 109 +++++++ scripts/update-tools-docs.ts | 323 ++++++++++++++++---- skills/xcodebuilmcp-cli/SKILL.md | 155 ++++++++++ src/cli/commands/tools.ts | 165 +++++++++- src/cli/yargs-app.ts | 2 +- src/core/generated-plugins.ts | 8 +- src/mcp/tools/simulator/stop_sim_log_cap.ts | 2 + 14 files changed, 1284 insertions(+), 281 deletions(-) create mode 100644 docs/TOOLS-CLI.md create mode 100644 scripts/generate-tools-manifest.ts create mode 100644 skills/xcodebuilmcp-cli/SKILL.md create mode 100644 src/mcp/tools/simulator/stop_sim_log_cap.ts diff --git a/README.md b/README.md index 94ae7cad..62f8aaab 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config ```json "XcodeBuildMCP": { "command": "npx", - "args": ["-y", "xcodebuildmcp@latest", "mcp"] + "args": ["-y", "xcodebuildmcp@beta", "mcp"] } ``` @@ -26,7 +26,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config "mcpServers": { "XcodeBuildMCP": { "command": "npx", - "args": ["-y", "xcodebuildmcp@latest", "mcp"] + "args": ["-y", "xcodebuildmcp@beta", "mcp"] } } } @@ -34,7 +34,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config Or use the quick install link: - [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=XcodeBuildMCP&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInhjb2RlYnVpbGRtY3BAbGF0ZXN0IiwibWNwIl19) +[![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/en-US/install-mcp?name=XcodeBuildMCP&config=eyJjb21tYW5kIjoibnB4IC15IHhjb2RlYnVpbGRtY3BAYmV0YSBtY3AifQ%3D%3D)
@@ -44,7 +44,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config Run: ```bash - claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp + claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@beta mcp ```
@@ -55,14 +55,14 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config Run: ```bash - codex mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp + codex mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@beta mcp ``` Or add to `~/.codex/config.toml`: ```toml [mcp_servers.XcodeBuildMCP] command = "npx" - args = ["-y", "xcodebuildmcp@latest", "mcp"] + args = ["-y", "xcodebuildmcp@beta", "mcp"] ```
@@ -77,7 +77,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config "mcpServers": { "XcodeBuildMCP": { "command": "npx", - "args": ["-y", "xcodebuildmcp@latest", "mcp"] + "args": ["-y", "xcodebuildmcp@beta", "mcp"] } } } @@ -95,7 +95,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config "servers": { "XcodeBuildMCP": { "command": "npx", - "args": ["-y", "xcodebuildmcp@latest", "mcp"] + "args": ["-y", "xcodebuildmcp@beta", "mcp"] } } } @@ -103,8 +103,8 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config Or use the quick install links: - [Install in VS Code](https://insiders.vscode.dev/redirect/mcp/install?name=XcodeBuildMCP&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40latest%22%2C%22mcp%22%5D%7D) - [Install in VS Code Insiders](https://insiders.vscode.dev/redirect/mcp/install?name=XcodeBuildMCP&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40latest%22%2C%22mcp%22%5D%7D&quality=insiders) + [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/en-US/install-mcp?name=XcodeBuildMCP&config=eyJjb21tYW5kIjoibnB4IC15IHhjb2RlYnVpbGRtY3BAYmV0YSBtY3AifQ%3D%3D) + [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-XcodeBuildMCP-24bfa5?style=flat&logo=visualstudiocode&logoColor=ffffff)](vscode-insiders:mcp/install?%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40beta%22%2C%22mcp%22%5D%7D)
@@ -118,7 +118,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config "mcpServers": { "XcodeBuildMCP": { "command": "npx", - "args": ["-y", "xcodebuildmcp@latest", "mcp"] + "args": ["-y", "xcodebuildmcp@beta", "mcp"] } } } @@ -136,7 +136,7 @@ Add XcodeBuildMCP to your MCP client configuration. Most clients use JSON config "mcpServers": { "XcodeBuildMCP": { "command": "npx", - "args": ["-y", "xcodebuildmcp@latest", "mcp"] + "args": ["-y", "xcodebuildmcp@beta", "mcp"] } } } @@ -182,7 +182,7 @@ XcodeBuildMCP provides a unified command-line interface. The `mcp` subcommand st ```bash # Install globally -npm install -g xcodebuildmcp +npm install -g xcodebuildmcp@beta # Start the MCP server (for MCP clients) xcodebuildmcp mcp @@ -191,7 +191,7 @@ xcodebuildmcp mcp xcodebuildmcp tools # Build for simulator -xcodebuildmcp build-sim --scheme MyApp --project-path ./MyApp.xcodeproj +xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj ``` The CLI uses a per-workspace daemon for stateful operations (log capture, debugging, etc.) that auto-starts when needed. See [docs/CLI.md](docs/CLI.md) for full documentation. diff --git a/docs/CLI.md b/docs/CLI.md index 9f9b2c60..a1d7eb3d 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,36 +1,139 @@ # XcodeBuildMCP CLI -`xcodebuildmcp` is a unified command-line interface that provides both the MCP server and direct tool access. Use `xcodebuildmcp mcp` to start the MCP server, or invoke tools directly from your terminal. +`xcodebuildmcp` is a unified command-line interface that provides both an MCP server and direct tool access via first-class CLI interface. + +Use `xcodebuildmcp` CLI to invoke tools or start the MCP server by passing the `mcp` argument. ## Installation ```bash # Install globally -npm install -g xcodebuildmcp +npm install -g xcodebuildmcp@beta # Or run via npx -npx xcodebuildmcp --help +npx xcodebuildmcp@beta --help ``` ## Quick Start ```bash -# Start MCP server (for MCP clients like Claude, Cursor, etc.) -xcodebuildmcp mcp - # List available tools xcodebuildmcp tools -# Build for simulator +# View CLI help +xcodebuildmcp --help + +# View tool help +xcodebuildmcp --help +``` + +## Tool Options + +Each tool supports `--help` for detailed options: + +```bash +xcodebuildmcp simulator build-sim --help +``` + +Common patterns: + +```bash +# Pass options as flags xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj -# List simulators -xcodebuildmcp simulator list-sims +# Pass complex options as JSON +xcodebuildmcp simulator build-sim --json '{"scheme": "MyApp", "projectPath": "./MyApp.xcodeproj"}' -# Run tests -xcodebuildmcp simulator test-sim --scheme MyApp --simulator-name "iPhone 17 Pro" +# Control output format +xcodebuildmcp simulator list-sims --output json ``` +## Examples + +### Build and Run Workflow + +```bash +# Discover projects +xcodebuildmcp simulator discover-projs + +# List schemes +xcodebuildmcp simulator list-schemes --project-path ./MyApp.xcodeproj + +# Build +xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj + +# Boot simulator +xcodebuildmcp simulator boot-sim --simulator-name "iPhone 17 Pro" + +# Install and launch +xcodebuildmcp simulator install-app-sim --simulator-id --app-path ./build/MyApp.app + +xcodebuildmcp simulator launch-app-sim --simulator-id --bundle-id com.example.MyApp + +# Or... build and run in a single command +xcodebuildmcp simulator build-run-sim --scheme MyApp --project-path ./MyApp.xcodeproj +``` + +### Log Capture Workflow + +```bash +# Start log capture +xcodebuildmcp logging start-sim-log-cap --simulator-id --bundle-id com.example.MyApp + +> Log capture started successfully. Session ID: 51e2142a-1a99-442a-af01-0586540043df. + +# Stop and retrieve logs +xcodebuildmcp logging stop-sim-log-cap --session-id +``` + +### Testing + +```bash +# Run all tests +xcodebuildmcp simulator test-sim --scheme MyAppTests --project-path ./MyApp.xcodeproj + +# Run with specific simulator +xcodebuildmcp simulator test-sim --scheme MyAppTests --simulator-name "iPhone 17 Pro" +``` + +For a full list of workflows and tools, see [TOOLS-CLI.md](TOOLS-CLI.md). + +## Configuration + +The CLI respects the same configuration as the MCP server: + +```yaml +# .xcodebuildmcp/config.yaml +sessionDefaults: + scheme: MyApp + projectPath: ./MyApp.xcodeproj + simulatorName: iPhone 17 Pro + +enabledWorkflows: + - simulator + - project-discovery +``` + +See [CONFIGURATION.md](CONFIGURATION.md) for the full schema. + +## Environment Variables + +| Variable | Description | +|----------|-------------| +| `XCODEBUILDMCP_SOCKET` | Override socket path for all commands | +| `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` | Disable session defaults | + +## CLI vs MCP Mode + +| Feature | CLI (`xcodebuildmcp `) | MCP (`xcodebuildmcp mcp`) | +|---------|------------------------------|---------------------------| +| Invocation | Direct terminal | MCP client (Claude, etc.) | +| Session state | Per-workspace daemon | In-process | +| Use case | Scripts, CI, manual | AI-assisted development | +| Configuration | Same config.yaml | Same config.yaml | + +Both share the same underlying tool implementations. + ## Per-Workspace Daemon The CLI uses a per-workspace daemon architecture for stateful operations (log capture, video recording, debugging). Each workspace gets its own daemon instance. @@ -95,36 +198,15 @@ Daemons: Total: 2 (1 running, 1 stale) ``` -## Global Options - -| Option | Description | -|--------|-------------| -| `--socket ` | Override the daemon socket path (hidden) | -| `--daemon` | Force daemon execution for stateless tools (hidden) | -| `--no-daemon` | Disable daemon usage; stateful tools will fail | -| `-h, --help` | Show help | -| `-v, --version` | Show version | - -## Tool Options +### Opting Out of Daemon -Each tool supports `--help` for detailed options: +If you want to disable daemon auto-start (stateful tools will error): ```bash -xcodebuildmcp simulator build-sim --help +xcodebuildmcp build-sim --no-daemon --scheme MyApp ``` -Common patterns: - -```bash -# Pass options as flags -xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj - -# Pass complex options as JSON -xcodebuildmcp simulatorbuild-sim --json '{"scheme": "MyApp", "projectPath": "./MyApp.xcodeproj"}' - -# Control output format -xcodebuildmcp simulator list-sims --output json -``` +This is useful for CI environments or when you want explicit control. ## Stateful vs Stateless Tools @@ -132,7 +214,7 @@ xcodebuildmcp simulator list-sims --output json Most tools run directly without the daemon: - `build-sim`, `test-sim`, `clean` - `list-sims`, `list-schemes`, `discover-projs` -- `boot-sim`, `install-app-sim`, `launch-app-sim` +- `boot-sim`, `install-app-sim`, `launch-app-sim` etc. ### Stateful Tools (require daemon) Some tools maintain state and route through the daemon: @@ -143,95 +225,15 @@ Some tools maintain state and route through the daemon: When you invoke a stateful tool, the daemon auto-starts if needed. -## Opting Out of Daemon - -If you want to disable daemon auto-start (stateful tools will error): - -```bash -xcodebuildmcp build-sim --no-daemon --scheme MyApp -``` - -This is useful for CI environments or when you want explicit control. - -## Configuration - -The CLI respects the same configuration as the MCP server: - -```yaml -# .xcodebuildmcp/config.yaml -sessionDefaults: - scheme: MyApp - projectPath: ./MyApp.xcodeproj - simulatorName: iPhone 17 Pro - -enabledWorkflows: - - simulator - - project-discovery -``` - -See [CONFIGURATION.md](CONFIGURATION.md) for the full schema. - -## Environment Variables - -| Variable | Description | -|----------|-------------| -| `XCODEBUILDMCP_SOCKET` | Override socket path for all commands | -| `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` | Disable session defaults | - -## Examples - -### Build and Run Workflow - -```bash -# Discover projects -xcodebuildmcp simulatordiscover-projs - -# List schemes -xcodebuildmcp simulatordiscover list-schemes --project-path ./MyApp.xcodeproj - -# Build -xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj - -# Boot simulator -xcodebuildmcp simulator boot-sim --simulator-name "iPhone 17 Pro" - -# Install and launch -xcodebuildmcp simulator install-app-sim --simulator-id --app-path ./build/MyApp.app -xcodebuildmcp simulator launch-app-sim --simulator-id --bundle-id com.example.MyApp -``` - -### Log Capture Workflow - -```bash -# Start log capture (daemon auto-starts) -xcodebuildmcp logging start-sim-log-cap --simulator-id --bundle-id com.example.MyApp - -# ... use your app ... - -# Stop and retrieve logs -xcodebuildmcp logging stop-sim-log-cap --session-id -``` - -### Testing - -```bash -# Run all tests -xcodebuildmcp simulator test-sim --scheme MyAppTests --project-path ./MyApp.xcodeproj - -# Run with specific simulator -xcodebuildmcp simulator test-sim --scheme MyAppTests --simulator-name "iPhone 17 Pro" -``` - -## CLI vs MCP Mode - -| Feature | CLI (`xcodebuildmcp `) | MCP (`xcodebuildmcp mcp`) | -|---------|------------------------------|---------------------------| -| Invocation | Direct terminal | MCP client (Claude, etc.) | -| Session state | Per-workspace daemon | In-process | -| Use case | Scripts, CI, manual | AI-assisted development | -| Configuration | Same config.yaml | Same config.yaml | +## Global Options -Both share the same underlying tool implementations. +| Option | Description | +|--------|-------------| +| `--socket ` | Override the daemon socket path (hidden) | +| `--daemon` | Force daemon execution for stateless tools (hidden) | +| `--no-daemon` | Disable daemon usage; stateful tools will fail | +| `-h, --help` | Show help | +| `-v, --version` | Show version | ## Troubleshooting @@ -264,4 +266,4 @@ The socket directory (`~/.xcodebuildmcp/daemons/`) should have mode 0700. If you ```bash chmod 700 ~/.xcodebuildmcp chmod -R 700 ~/.xcodebuildmcp/daemons -``` +``` \ No newline at end of file diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index cbb66a3b..3473e767 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -12,7 +12,7 @@ XcodeBuildMCP provides a unified CLI with two modes: | Command | Use Case | |---------|----------| | `xcodebuildmcp mcp` | Start MCP server for AI-assisted development | -| `xcodebuildmcp ` | Direct terminal usage, scripts, CI pipelines | +| `xcodebuildmcp ` | Direct terminal usage, scripts, CI pipelines | Both share the same tools and configuration. @@ -25,7 +25,7 @@ Most MCP clients use JSON configuration. Add the following server entry to your "command": "npx", "args": [ "-y", - "xcodebuildmcp@latest", + "xcodebuildmcp@beta", "mcp" ] } @@ -35,13 +35,19 @@ Most MCP clients use JSON configuration. Add the following server entry to your ```bash # Install globally -npm install -g xcodebuildmcp +npm install -g xcodebuildmcp@beta # Verify installation xcodebuildmcp --version # List available tools xcodebuildmcp tools + +# View CLI help +xcodebuildmcp --help + +# View tool help +xcodebuildmcp --help ``` See [CLI.md](CLI.md) for full CLI documentation. @@ -63,7 +69,7 @@ Codex uses TOML for MCP configuration. Add this to `~/.codex/config.toml`: ```toml [mcp_servers.XcodeBuildMCP] command = "npx" -args = ["-y", "xcodebuildmcp@latest", "mcp"] +args = ["-y", "xcodebuildmcp@beta", "mcp"] env = { "XCODEBUILDMCP_SENTRY_DISABLED" = "false" } ``` @@ -79,10 +85,10 @@ https://github.com/openai/codex/blob/main/docs/config.md#connecting-to-mcp-serve ### Claude Code CLI ```bash # Add XcodeBuildMCP server to Claude Code -claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp +claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@beta mcp # Or with environment variables -claude mcp add XcodeBuildMCP -e XCODEBUILDMCP_SENTRY_DISABLED=false -- npx -y xcodebuildmcp@latest mcp +claude mcp add XcodeBuildMCP -e XCODEBUILDMCP_SENTRY_DISABLED=false -- npx -y xcodebuildmcp@beta mcp ``` Note: XcodeBuildMCP requests xcodebuild to skip macro validation to avoid Swift Macro build errors. diff --git a/docs/TOOLS-CLI.md b/docs/TOOLS-CLI.md new file mode 100644 index 00000000..5ebee8f2 --- /dev/null +++ b/docs/TOOLS-CLI.md @@ -0,0 +1,180 @@ +# XcodeBuildMCP CLI Tools Reference + +This document lists CLI tool names as exposed by `xcodebuildmcp `. + +XcodeBuildMCP provides 68 canonical tools organized into 12 workflow groups. + +## Workflow Groups + +### iOS Device Development (`device`) +**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting physical devices (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro). Build, test, deploy, and debug apps on real hardware. (14 tools) + +- `build-device` - Build for device. +- `clean` - Defined in Project Utilities workflow. +- `discover-projs` - Defined in Project Discovery workflow. +- `get-app-bundle-id` - Defined in Project Discovery workflow. +- `get-device-app-path` - Get device built app path. +- `install-app-device` - Install app on device. +- `launch-app-device` - Launch app on device. +- `list-devices` - List connected devices. +- `list-schemes` - Defined in Project Discovery workflow. +- `show-build-settings` - Defined in Project Discovery workflow. +- `start-device-log-cap` - Defined in Log Capture & Management workflow. +- `stop-app-device` - Stop device app. +- `stop-device-log-cap` - Defined in Log Capture & Management workflow. +- `test-device` - Test on device. + + + +### iOS Simulator Development (`simulator`) +**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators. (20 tools) + +- `boot-sim` - Boot iOS simulator. +- `build-run-sim` - Build and run iOS sim. +- `build-sim` - Build for iOS sim. +- `clean` - Defined in Project Utilities workflow. +- `discover-projs` - Defined in Project Discovery workflow. +- `get-app-bundle-id` - Defined in Project Discovery workflow. +- `get-sim-app-path` - Get sim built app path. +- `install-app-sim` - Install app on sim. +- `launch-app-logs-sim` - Launch sim app with logs. +- `launch-app-sim` - Launch app on simulator. +- `list-schemes` - Defined in Project Discovery workflow. +- `list-sims` - List iOS simulators. +- `open-sim` - Open Simulator app. +- `record-sim-video` - Record sim video. +- `screenshot` - Defined in UI Automation workflow. +- `show-build-settings` - Defined in Project Discovery workflow. +- `snapshot-ui` - Defined in UI Automation workflow. +- `stop-app-sim` - Stop sim app. +- `stop-sim-log-cap` - Defined in Log Capture & Management workflow. +- `test-sim` - Test on iOS sim. + + + +### Log Capture & Management (`logging`) +**Purpose**: Log capture and management tools for iOS simulators and physical devices. Start, stop, and analyze application and system logs during development and testing. (4 tools) + +- `start-device-log-cap` - Start device log capture. +- `start-sim-log-cap` - Start sim log capture. +- `stop-device-log-cap` - Stop device app and return logs. +- `stop-sim-log-cap` - Stop sim app and return logs. + + + +### macOS Development (`macos`) +**Purpose**: Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications. (11 tools) + +- `build-macos` - Build macOS app. +- `build-run-macos` - Build and run macOS app. +- `clean` - Defined in Project Utilities workflow. +- `discover-projs` - Defined in Project Discovery workflow. +- `get-mac-app-path` - Get macOS built app path. +- `get-mac-bundle-id` - Defined in Project Discovery workflow. +- `launch-mac-app` - Launch macOS app. +- `list-schemes` - Defined in Project Discovery workflow. +- `show-build-settings` - Defined in Project Discovery workflow. +- `stop-mac-app` - Stop macOS app. +- `test-macos` - Test macOS target. + + + +### Project Discovery (`project-discovery`) +**Purpose**: Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information. (5 tools) + +- `discover-projs` - Scans a directory (defaults to workspace root) to find Xcode project (.xcodeproj) and workspace (.xcworkspace) files. +- `get-app-bundle-id` - Extract bundle id from .app. +- `get-mac-bundle-id` - Extract bundle id from macOS .app. +- `list-schemes` - List Xcode schemes. +- `show-build-settings` - Show build settings. + + + +### Project Scaffolding (`project-scaffolding`) +**Purpose**: Tools for creating new iOS and macOS projects from templates. Bootstrap new applications with best practices, standard configurations, and modern project structures. (2 tools) + +- `scaffold-ios-project` - Scaffold iOS project. +- `scaffold-macos-project` - Scaffold macOS project. + + + +### Project Utilities (`utilities`) +**Purpose**: Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files. (1 tools) + +- `clean` - Clean build products. + + + +### Simulator Debugging (`debugging`) +**Purpose**: Interactive iOS Simulator debugging tools: attach LLDB, manage breakpoints, inspect stack/variables, and run LLDB commands. (8 tools) + +- `debug-attach-sim` - Attach LLDB to sim app. +- `debug-breakpoint-add` - Add breakpoint. +- `debug-breakpoint-remove` - Remove breakpoint. +- `debug-continue` - Continue debug session. +- `debug-detach` - Detach debugger. +- `debug-lldb-command` - Run LLDB command. +- `debug-stack` - Get backtrace. +- `debug-variables` - Get frame variables. + + + +### Simulator Management (`simulator-management`) +**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (8 tools) + +- `boot-sim` - Defined in iOS Simulator Development workflow. +- `erase-sims` - Erase simulator. +- `list-sims` - Defined in iOS Simulator Development workflow. +- `open-sim` - Defined in iOS Simulator Development workflow. +- `reset-sim-location` - Reset sim location. +- `set-sim-appearance` - Set sim appearance. +- `set-sim-location` - Set sim location. +- `sim-statusbar` - Set sim status bar network. + + + +### Swift Package Manager (`swift-package`) +**Purpose**: Swift Package Manager operations for building, testing, running, and managing Swift packages and dependencies. Complete SPM workflow support. (6 tools) + +- `swift-package-build` - swift package target build. +- `swift-package-clean` - swift package clean. +- `swift-package-list` - List SwiftPM processes. +- `swift-package-run` - swift package target run. +- `swift-package-stop` - Stop SwiftPM run. +- `swift-package-test` - Run swift package target tests. + + + +### System Doctor (`doctor`) +**Purpose**: Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability. (1 tools) + +- `doctor` - MCP environment info. + + + +### UI Automation (`ui-automation`) +**Purpose**: UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows. (11 tools) + +- `button` - Press simulator hardware button. +- `gesture` - Simulator gesture preset. +- `key-press` - Press key by keycode. +- `key-sequence` - Press a sequence of keys by their keycodes. +- `long-press` - Long press at coords. +- `screenshot` - Capture screenshot. +- `snapshot-ui` - Print view hierarchy with precise view coordinates (x, y, width, height) for visible elements. +- `swipe` - Swipe between points. +- `tap` - Tap coordinate or element. +- `touch` - Touch down/up at coords. +- `type-text` - Type text. + + + +## Summary Statistics + +- **Canonical Tools**: 68 +- **Total Tools**: 91 +- **Workflow Groups**: 12 + +--- + +*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-03T12:43:04.479Z UTC* diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 8d187c5d..e979dc14 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -1,34 +1,55 @@ -# XcodeBuildMCP Tools Reference +# XcodeBuildMCP MCP Tools Reference -XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehensive Apple development workflows. +This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP provides 72 canonical tools organized into 14 workflow groups for comprehensive Apple development workflows. ## Workflow Groups ### iOS Device Development (`device`) -**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting physical devices (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro). Build, test, deploy, and debug apps on real hardware. (7 tools) +**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting physical devices (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro). Build, test, deploy, and debug apps on real hardware. (14 tools) - `build_device` - Build for device. +- `clean` - Defined in Project Utilities workflow. +- `discover_projs` - Defined in Project Discovery workflow. +- `get_app_bundle_id` - Defined in Project Discovery workflow. - `get_device_app_path` - Get device built app path. - `install_app_device` - Install app on device. - `launch_app_device` - Launch app on device. - `list_devices` - List connected devices. +- `list_schemes` - Defined in Project Discovery workflow. +- `show_build_settings` - Defined in Project Discovery workflow. +- `start_device_log_cap` - Defined in Log Capture & Management workflow. - `stop_app_device` - Stop device app. +- `stop_device_log_cap` - Defined in Log Capture & Management workflow. - `test_device` - Test on device. + + + ### iOS Simulator Development (`simulator`) -**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators. (12 tools) +**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators. (20 tools) - `boot_sim` - Boot iOS simulator. - `build_run_sim` - Build and run iOS sim. - `build_sim` - Build for iOS sim. +- `clean` - Defined in Project Utilities workflow. +- `discover_projs` - Defined in Project Discovery workflow. +- `get_app_bundle_id` - Defined in Project Discovery workflow. - `get_sim_app_path` - Get sim built app path. - `install_app_sim` - Install app on sim. - `launch_app_logs_sim` - Launch sim app with logs. - `launch_app_sim` - Launch app on simulator. +- `list_schemes` - Defined in Project Discovery workflow. - `list_sims` - List iOS simulators. - `open_sim` - Open Simulator app. - `record_sim_video` - Record sim video. +- `screenshot` - Defined in UI Automation workflow. +- `show_build_settings` - Defined in Project Discovery workflow. +- `snapshot_ui` - Defined in UI Automation workflow. - `stop_app_sim` - Stop sim app. +- `stop_sim_log_cap` - Defined in Log Capture & Management workflow. - `test_sim` - Test on iOS sim. + + + ### Log Capture & Management (`logging`) **Purpose**: Log capture and management tools for iOS simulators and physical devices. Start, stop, and analyze application and system logs during development and testing. (4 tools) @@ -36,15 +57,26 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens - `start_sim_log_cap` - Start sim log capture. - `stop_device_log_cap` - Stop device app and return logs. - `stop_sim_log_cap` - Stop sim app and return logs. + + + ### macOS Development (`macos`) -**Purpose**: Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications. (6 tools) +**Purpose**: Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications. (11 tools) - `build_macos` - Build macOS app. - `build_run_macos` - Build and run macOS app. +- `clean` - Defined in Project Utilities workflow. +- `discover_projs` - Defined in Project Discovery workflow. - `get_mac_app_path` - Get macOS built app path. +- `get_mac_bundle_id` - Defined in Project Discovery workflow. - `launch_mac_app` - Launch macOS app. +- `list_schemes` - Defined in Project Discovery workflow. +- `show_build_settings` - Defined in Project Discovery workflow. - `stop_mac_app` - Stop macOS app. - `test_macos` - Test macOS target. + + + ### Project Discovery (`project-discovery`) **Purpose**: Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information. (5 tools) @@ -53,21 +85,33 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens - `get_mac_bundle_id` - Extract bundle id from macOS .app. - `list_schemes` - List Xcode schemes. - `show_build_settings` - Show build settings. + + + ### Project Scaffolding (`project-scaffolding`) **Purpose**: Tools for creating new iOS and macOS projects from templates. Bootstrap new applications with best practices, standard configurations, and modern project structures. (2 tools) - `scaffold_ios_project` - Scaffold iOS project. - `scaffold_macos_project` - Scaffold macOS project. + + + ### Project Utilities (`utilities`) **Purpose**: Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files. (1 tools) - `clean` - Clean build products. + + + ### session-management (`session-management`) **Purpose**: Manage session defaults for project/workspace paths, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS, arch, suppressWarnings, derivedDataPath, preferXcodebuild, platform, and bundleId. Defaults can be seeded from .xcodebuildmcp/config.yaml at startup. (3 tools) - `session_clear_defaults` - Clear session defaults. - `session_set_defaults` - Set the session defaults, should be called at least once to set tool defaults. - `session_show_defaults` - Show session defaults. + + + ### Simulator Debugging (`debugging`) **Purpose**: Interactive iOS Simulator debugging tools: attach LLDB, manage breakpoints, inspect stack/variables, and run LLDB commands. (8 tools) @@ -79,14 +123,23 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens - `debug_lldb_command` - Run LLDB command. - `debug_stack` - Get backtrace. - `debug_variables` - Get frame variables. + + + ### Simulator Management (`simulator-management`) -**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (5 tools) +**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (8 tools) +- `boot_sim` - Defined in iOS Simulator Development workflow. - `erase_sims` - Erase simulator. +- `list_sims` - Defined in iOS Simulator Development workflow. +- `open_sim` - Defined in iOS Simulator Development workflow. - `reset_sim_location` - Reset sim location. - `set_sim_appearance` - Set sim appearance. - `set_sim_location` - Set sim location. - `sim_statusbar` - Set sim status bar network. + + + ### Swift Package Manager (`swift-package`) **Purpose**: Swift Package Manager operations for building, testing, running, and managing Swift packages and dependencies. Complete SPM workflow support. (6 tools) @@ -96,10 +149,16 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens - `swift_package_run` - swift package target run. - `swift_package_stop` - Stop SwiftPM run. - `swift_package_test` - Run swift package target tests. + + + ### System Doctor (`doctor`) **Purpose**: Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability. (1 tools) - `doctor` - MCP environment info. + + + ### UI Automation (`ui-automation`) **Purpose**: UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows. (11 tools) @@ -114,16 +173,22 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens - `tap` - Tap coordinate or element. - `touch` - Touch down/up at coords. - `type_text` - Type text. -### workflow-discovery (`workflow-discovery`) -**Purpose**: workflow-discovery related tools (1 tools) + + + +### Workflow Discovery (`workflow-discovery`) +**Purpose**: Manage the workflows that are enabled and disabled. (1 tools) - `manage_workflows` - Workflows are groups of tools exposed by XcodeBuildMCP. By default, not all workflows (and therefore tools) are enabled; only simulator tools are enabled by default. Some workflows are mandatory and can't be disabled. Available workflows: ${availableWorkflows} + + ## Summary Statistics -- **Total Tools**: 72 canonical tools + 22 re-exports = 94 total +- **Canonical Tools**: 72 +- **Total Tools**: 95 - **Workflow Groups**: 14 --- -*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2026-02-02* +*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-03T12:43:04.479Z UTC* diff --git a/package.json b/package.json index b957435b..cb0fc662 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,11 @@ "scripts": { "build": "npm run build:tsup && npx smithery build --transport stdio", "dev": "npm run generate:version && npm run generate:loaders && npx smithery dev", - "build:tsup": "npm run generate:version && npm run generate:loaders && tsup", + "build:tsup": "npm run generate:version && npm run generate:loaders && tsup && npm run generate:tools-manifest", "dev:tsup": "npm run build:tsup && tsup --watch", "generate:version": "npx tsx scripts/generate-version.ts", "generate:loaders": "npx tsx scripts/generate-loaders.ts", + "generate:tools-manifest": "npx tsx scripts/generate-tools-manifest.ts", "bundle:axe": "scripts/bundle-axe.sh", "lint": "eslint 'src/**/*.{js,ts}'", "lint:fix": "eslint 'src/**/*.{js,ts}' --fix", diff --git a/scripts/analysis/tools-analysis.ts b/scripts/analysis/tools-analysis.ts index 0ee43f53..8f210d64 100644 --- a/scripts/analysis/tools-analysis.ts +++ b/scripts/analysis/tools-analysis.ts @@ -42,6 +42,9 @@ export interface ToolInfo { path: string; relativePath: string; description: string; + cliName?: string; + originWorkflow?: string; + stateful?: boolean; isCanonical: boolean; } @@ -68,11 +71,72 @@ export interface StaticAnalysisResult { stats: AnalysisStats; } +type ExtractStringOptions = { + allowFallback: boolean; +}; + +function extractStringValue( + sourceFile: SourceFile, + node: Node, + options: ExtractStringOptions = { allowFallback: false }, +): string | null { + if (isStringLiteral(node)) { + return node.text; + } + + if (isTemplateExpression(node) || isNoSubstitutionTemplateLiteral(node)) { + let text = node.getFullText(sourceFile).trim(); + if (text.startsWith('`') && text.endsWith('`')) { + text = text.slice(1, -1); + } + return text; + } + + if (!options.allowFallback) { + return null; + } + + const fullText = node.getFullText(sourceFile).trim(); + let cleaned = fullText; + if ( + (cleaned.startsWith('"') && cleaned.endsWith('"')) || + (cleaned.startsWith("'") && cleaned.endsWith("'")) + ) { + cleaned = cleaned.slice(1, -1); + } + return cleaned.replace(/\s+/g, ' ').trim(); +} + +function extractBooleanValue(node: Node): boolean | null { + if (node.kind === SyntaxKind.TrueKeyword) { + return true; + } + if (node.kind === SyntaxKind.FalseKeyword) { + return false; + } + return null; +} + +function getCodeLines(content: string): string[] { + const contentWithoutBlockComments = content.replace(/\/\*[\s\S]*?\*\//g, ''); + + return contentWithoutBlockComments + .split('\n') + .map((line) => line.split('//')[0].trim()) + .filter((line) => line.length > 0); +} + /** * Extract the description from a tool's default export using TypeScript AST */ -function extractToolDescription(sourceFile: SourceFile): string { +function extractToolMetadata(sourceFile: SourceFile): { + description: string; + cliName?: string; + stateful?: boolean; +} { let description: string | null = null; + let cliName: string | null = null; + let stateful: boolean | null = null; function visit(node: Node): void { let objectExpression: ObjectLiteralExpression | null = null; @@ -86,43 +150,36 @@ function extractToolDescription(sourceFile: SourceFile): string { } if (objectExpression) { - // Found export default { ... }, now look for description property + // Found export default { ... }, now look for description and CLI metadata for (const property of objectExpression.properties) { - if ( - isPropertyAssignment(property) && - isIdentifier(property.name) && - property.name.text === 'description' - ) { - // Extract the description value - if (isStringLiteral(property.initializer)) { - // This is the most common case - simple string literal - description = property.initializer.text; - } else if ( - isTemplateExpression(property.initializer) || - isNoSubstitutionTemplateLiteral(property.initializer) - ) { - // Handle template literals - get the raw text and clean it - description = property.initializer.getFullText(sourceFile).trim(); - // Remove surrounding backticks - if (description.startsWith('`') && description.endsWith('`')) { - description = description.slice(1, -1); + if (!isPropertyAssignment(property) || !isIdentifier(property.name)) { + continue; + } + + if (property.name.text === 'description') { + description = extractStringValue(sourceFile, property.initializer, { + allowFallback: true, + }); + continue; + } + + if (property.name.text === 'cli' && isObjectLiteralExpression(property.initializer)) { + for (const cliProperty of property.initializer.properties) { + if (!isPropertyAssignment(cliProperty) || !isIdentifier(cliProperty.name)) { + continue; + } + + if (cliProperty.name.text === 'name') { + cliName = extractStringValue(sourceFile, cliProperty.initializer, { + allowFallback: true, + }); + continue; } - } else { - // Handle any other expression (multiline strings, computed values) - const fullText = property.initializer.getFullText(sourceFile).trim(); - // This covers cases where the description spans multiple lines - // Remove surrounding quotes and normalize whitespace - let cleaned = fullText; - if ( - (cleaned.startsWith('"') && cleaned.endsWith('"')) || - (cleaned.startsWith("'") && cleaned.endsWith("'")) - ) { - cleaned = cleaned.slice(1, -1); + + if (cliProperty.name.text === 'stateful') { + stateful = extractBooleanValue(cliProperty.initializer); } - // Collapse multiple whitespaces and newlines into single spaces - description = cleaned.replace(/\s+/g, ' ').trim(); } - return; // Found description, stop looking } } } @@ -136,7 +193,11 @@ function extractToolDescription(sourceFile: SourceFile): string { throw new Error('Could not extract description from tool export default object'); } - return description; + return { + description, + cliName: cliName ?? undefined, + stateful: stateful ?? undefined, + }; } /** @@ -144,19 +205,7 @@ function extractToolDescription(sourceFile: SourceFile): string { */ function isReExportFile(filePath: string): boolean { const content = fs.readFileSync(filePath, 'utf-8'); - - // Remove comments and empty lines, then check for re-export pattern - // First remove multi-line comments - const contentWithoutBlockComments = content.replace(/\/\*[\s\S]*?\*\//g, ''); - - const cleanedLines = contentWithoutBlockComments - .split('\n') - .map((line) => { - // Remove inline comments but preserve the code before them - const codeBeforeComment = line.split('//')[0].trim(); - return codeBeforeComment; - }) - .filter((line) => line.length > 0); + const cleanedLines = getCodeLines(content); // Should have exactly one line: export { default } from '...'; if (cleanedLines.length !== 1) { @@ -167,6 +216,42 @@ function isReExportFile(filePath: string): boolean { return /^export\s*{\s*default\s*}\s*from\s*['"][^'"]+['"];?\s*$/.test(exportLine); } +function getReExportTargetInfo(filePath: string): { filePath: string; workflow: string } | null { + const content = fs.readFileSync(filePath, 'utf-8'); + const cleanedLines = getCodeLines(content); + + if (cleanedLines.length !== 1) { + return null; + } + + const match = cleanedLines[0].match(/export\s*{\s*default\s*}\s*from\s*['"]([^'"]+)['"];?\s*$/); + if (!match) { + return null; + } + + let targetFilePath = path.resolve(path.dirname(filePath), match[1]); + if (!path.extname(targetFilePath)) { + targetFilePath += '.ts'; + } + + if (!targetFilePath.startsWith(toolsDir)) { + throw new Error( + `Re-export target for ${path.relative(projectRoot, filePath)} is outside tools directory: ${targetFilePath}`, + ); + } + + if (!fs.existsSync(targetFilePath)) { + throw new Error( + `Re-export target for ${path.relative(projectRoot, filePath)} does not exist: ${targetFilePath}`, + ); + } + + return { + filePath: targetFilePath, + workflow: path.basename(path.dirname(targetFilePath)), + }; +} + /** * Get workflow metadata from index.ts file if it exists */ @@ -204,13 +289,16 @@ async function getWorkflowMetadata( if (isPropertyAssignment(property) && isIdentifier(property.name)) { const propertyName = property.name.text; - if (propertyName === 'name' && isStringLiteral(property.initializer)) { - workflowExport.name = property.initializer.text; - } else if ( - propertyName === 'description' && - isStringLiteral(property.initializer) - ) { - workflowExport.description = property.initializer.text; + if (propertyName === 'name') { + const value = extractStringValue(sourceFile, property.initializer); + if (value) { + workflowExport.name = value; + } + } else if (propertyName === 'description') { + const value = extractStringValue(sourceFile, property.initializer); + if (value) { + workflowExport.description = value; + } } } } @@ -327,6 +415,9 @@ export async function getStaticToolAnalysis(): Promise { const isReExport = isReExportFile(filePath); let description = ''; + let cliName: string | undefined; + let originWorkflow: string | undefined; + let stateful: boolean | undefined; if (!isReExport) { // Extract description from canonical tool using AST @@ -334,13 +425,38 @@ export async function getStaticToolAnalysis(): Promise { const content = fs.readFileSync(filePath, 'utf-8'); const sourceFile = createSourceFile(filePath, content, ScriptTarget.Latest, true); - description = extractToolDescription(sourceFile); + const metadata = extractToolMetadata(sourceFile); + description = metadata.description; + cliName = metadata.cliName; + stateful = metadata.stateful; canonicalCount++; } catch (error) { throw new Error(`Failed to extract description from ${relativePath}: ${error}`); } } else { - description = '(Re-exported from shared workflow)'; + const reExportInfo = getReExportTargetInfo(filePath); + if (!reExportInfo) { + throw new Error(`Failed to resolve re-export target for ${relativePath}`); + } + + originWorkflow = reExportInfo.workflow; + try { + const targetContent = fs.readFileSync(reExportInfo.filePath, 'utf-8'); + const targetSourceFile = createSourceFile( + reExportInfo.filePath, + targetContent, + ScriptTarget.Latest, + true, + ); + const metadata = extractToolMetadata(targetSourceFile); + description = metadata.description; + cliName = metadata.cliName; + stateful = metadata.stateful; + } catch (error) { + throw new Error( + `Failed to extract description for re-export ${relativePath}: ${error as Error}`, + ); + } reExportCount++; } @@ -350,6 +466,9 @@ export async function getStaticToolAnalysis(): Promise { path: filePath, relativePath, description, + cliName, + originWorkflow, + stateful, isCanonical: !isReExport, }; diff --git a/scripts/generate-tools-manifest.ts b/scripts/generate-tools-manifest.ts new file mode 100644 index 00000000..e6f3e219 --- /dev/null +++ b/scripts/generate-tools-manifest.ts @@ -0,0 +1,109 @@ +#!/usr/bin/env node + +/** + * XcodeBuildMCP Tools Manifest Generator + * + * Generates build/tools-manifest.json from static AST analysis. + * This is the canonical source of truth for docs and CLI tooling output. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; +import { getStaticToolAnalysis } from './analysis/tools-analysis.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, '..'); + +type ToolManifestEntry = { + name: string; + mcpName: string; + cliName: string; + workflow: string; + description: string; + originWorkflow?: string; + isCanonical: boolean; + stateful: boolean; +}; + +type WorkflowManifestEntry = { + name: string; + displayName: string; + description: string; + toolCount: number; + canonicalCount: number; + reExportCount: number; +}; + +type ToolsManifest = { + generatedAt: string; + stats: { + totalTools: number; + canonicalTools: number; + reExportTools: number; + workflowCount: number; + }; + workflows: WorkflowManifestEntry[]; + tools: ToolManifestEntry[]; +}; + +function toKebabCase(name: string): string { + return name + .trim() + .replace(/_/g, '-') + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/\s+/g, '-') + .toLowerCase() + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); +} + +async function main(): Promise { + const analysis = await getStaticToolAnalysis(); + + const tools: ToolManifestEntry[] = analysis.tools.map((tool) => ({ + name: tool.name, + mcpName: tool.name, + cliName: tool.cliName ?? toKebabCase(tool.name), + workflow: tool.workflow, + description: tool.description, + originWorkflow: tool.originWorkflow, + isCanonical: tool.isCanonical, + stateful: tool.stateful ?? false, + })); + + tools.sort( + (a, b) => a.workflow.localeCompare(b.workflow) || a.name.localeCompare(b.name), + ); + + const workflows: WorkflowManifestEntry[] = analysis.workflows.map((workflow) => ({ + name: workflow.name, + displayName: workflow.displayName, + description: workflow.description, + toolCount: workflow.toolCount, + canonicalCount: workflow.canonicalCount, + reExportCount: workflow.reExportCount, + })); + + const manifest: ToolsManifest = { + generatedAt: new Date().toISOString(), + stats: analysis.stats, + workflows, + tools, + }; + + const outputPath = path.join(projectRoot, 'build', 'tools-manifest.json'); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8'); + + process.stdout.write( + `✅ Generated tools manifest: ${path.relative(projectRoot, outputPath)}\n`, + ); +} + +main().catch((error) => { + process.stderr.write(`❌ Failed to generate tools manifest: ${String(error)}\n`); + process.exit(1); +}); + diff --git a/scripts/update-tools-docs.ts b/scripts/update-tools-docs.ts index 91938196..a692e6f9 100644 --- a/scripts/update-tools-docs.ts +++ b/scripts/update-tools-docs.ts @@ -3,8 +3,8 @@ /** * XcodeBuildMCP Tools Documentation Updater * - * Automatically updates docs/TOOLS.md with current tool and workflow information - * using static AST analysis. Ensures documentation always reflects the actual codebase. + * Automatically updates docs/TOOLS.md and docs/TOOLS-CLI.md with current tool and workflow information + * using the build tools manifest. * * Usage: * npx tsx scripts/update-tools-docs.ts [--dry-run] [--verbose] @@ -18,17 +18,27 @@ import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; -import { - getStaticToolAnalysis, - type StaticAnalysisResult, - type WorkflowInfo, -} from './analysis/tools-analysis.js'; // Get project paths const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.resolve(__dirname, '..'); const docsPath = path.join(projectRoot, 'docs', 'TOOLS.md'); +const docsCliPath = path.join(projectRoot, 'docs', 'TOOLS-CLI.md'); +const manifestPath = path.join(projectRoot, 'build', 'tools-manifest.json'); +const cliExcludedWorkflows = new Set(['session-management', 'workflow-discovery']); + +type ToolsManifest = { + generatedAt: string; + stats: { + totalTools: number; + canonicalTools: number; + reExportTools: number; + workflowCount: number; + }; + workflows: DocumentationWorkflow[]; + tools: DocumentationTool[]; +}; // CLI options const args = process.argv.slice(2); @@ -53,7 +63,7 @@ if (options.help) { console.log(` ${colors.bright}${colors.blue}XcodeBuildMCP Tools Documentation Updater${colors.reset} -Automatically updates docs/TOOLS.md with current tool and workflow information. +Automatically updates docs/TOOLS.md and docs/TOOLS-CLI.md with current tool and workflow information. ${colors.bright}Usage:${colors.reset} npx tsx scripts/update-tools-docs.ts [options] @@ -64,7 +74,7 @@ ${colors.bright}Options:${colors.reset} --help, -h Show this help message ${colors.bright}Examples:${colors.reset} - ${colors.cyan}npx tsx scripts/update-tools-docs.ts${colors.reset} # Update docs/TOOLS.md + ${colors.cyan}npx tsx scripts/update-tools-docs.ts${colors.reset} # Update docs/TOOLS.md + docs/TOOLS-CLI.md ${colors.cyan}npx tsx scripts/update-tools-docs.ts --dry-run${colors.reset} # Preview changes ${colors.cyan}npx tsx scripts/update-tools-docs.ts --verbose${colors.reset} # Show detailed progress `); @@ -74,56 +84,239 @@ ${colors.bright}Examples:${colors.reset} /** * Generate the workflow section content */ -function generateWorkflowSection(workflow: WorkflowInfo): string { - const canonicalTools = workflow.tools.filter((tool) => tool.isCanonical); - const toolCount = canonicalTools.length; +function cleanToolDescription(description: string | undefined): string { + if (!description) { + return 'No description available'; + } + + return description + .replace(/IMPORTANT:.*?Example:.*?\)/g, '') // Remove IMPORTANT sections + .replace(/\s+/g, ' ') // Normalize whitespace + .trim(); +} + +type DocumentationTool = { + name: string; + description?: string; + isCanonical?: boolean; + originWorkflowDisplayName?: string; + workflow?: string; + cliName?: string; + originWorkflow?: string; +}; + +type DocumentationWorkflow = { + name: string; + displayName: string; + description: string; +}; + +function generateWorkflowSection( + workflow: DocumentationWorkflow, + tools: DocumentationTool[], +): string { + const toolCount = tools.length; let content = `### ${workflow.displayName} (\`${workflow.name}\`)\n`; content += `**Purpose**: ${workflow.description} (${toolCount} tools)\n\n`; // List each tool with its description - for (const tool of canonicalTools.sort((a, b) => a.name.localeCompare(b.name))) { - // Clean up the description for documentation - const cleanDescription = tool.description - .replace(/IMPORTANT:.*?Example:.*?\)/g, '') // Remove IMPORTANT sections - .replace(/\s+/g, ' ') // Normalize whitespace - .trim(); + const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name)); + for (const tool of sortedTools) { + let description = tool.description; + if (tool.isCanonical === false) { + if (tool.originWorkflowDisplayName) { + description = `Defined in ${tool.originWorkflowDisplayName} workflow.`; + } else { + description = 'Defined in another workflow.'; + } + } + const cleanDescription = cleanToolDescription(description); content += `- \`${tool.name}\` - ${cleanDescription}\n`; } + content += '\n\n'; + return content; } +function loadManifest(): ToolsManifest { + if (!fs.existsSync(manifestPath)) { + throw new Error( + `Missing tools manifest at ${path.relative(projectRoot, manifestPath)}. Run \"npm run build\" first.`, + ); + } + + const raw = fs.readFileSync(manifestPath, 'utf-8'); + return JSON.parse(raw) as ToolsManifest; +} + /** * Generate the complete TOOLS.md content */ -function generateToolsDocumentation(analysis: StaticAnalysisResult): string { - const { workflows, stats } = analysis; +function generateToolsDocumentation(manifest: ToolsManifest): string { + const { workflows, stats, tools } = manifest; // Sort workflows by display name for consistent ordering - const sortedWorkflows = workflows.sort((a, b) => a.displayName.localeCompare(b.displayName)); + const sortedWorkflows = [...workflows].sort((a, b) => a.displayName.localeCompare(b.displayName)); + const workflowMeta = new Map(workflows.map((workflow) => [workflow.name, workflow])); + const toolsByWorkflow = new Map(); + for (const tool of tools) { + const workflowKey = tool.workflow ?? ''; + const workflowTools = toolsByWorkflow.get(workflowKey) ?? []; + workflowTools.push(tool); + toolsByWorkflow.set(workflowKey, workflowTools); + } + const workflowSections = sortedWorkflows + .map((workflow) => { + const workflowTools = toolsByWorkflow.get(workflow.name) ?? []; + const docTools = workflowTools.map((tool) => { + const originWorkflow = tool.originWorkflow + ? workflowMeta.get(tool.originWorkflow)?.displayName ?? tool.originWorkflow + : undefined; + + return { + name: tool.name, + description: tool.description, + isCanonical: tool.isCanonical, + originWorkflowDisplayName: originWorkflow, + }; + }); + + return generateWorkflowSection( + { + name: workflow.name, + displayName: workflow.displayName, + description: workflow.description, + }, + docTools, + ); + }) + .join('\n'); - const content = `# XcodeBuildMCP Tools Reference + const lastUpdated = `${new Date(manifest.generatedAt).toISOString()} UTC`; -XcodeBuildMCP provides ${stats.canonicalTools} tools organized into ${stats.workflowCount} workflow groups for comprehensive Apple development workflows. + const content = `# XcodeBuildMCP MCP Tools Reference + +This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP provides ${stats.canonicalTools} canonical tools organized into ${stats.workflowCount} workflow groups for comprehensive Apple development workflows. ## Workflow Groups -${sortedWorkflows.map((workflow) => generateWorkflowSection(workflow)).join('')} +${workflowSections} ## Summary Statistics -- **Total Tools**: ${stats.canonicalTools} canonical tools + ${stats.reExportTools} re-exports = ${stats.totalTools} total +- **Canonical Tools**: ${stats.canonicalTools} +- **Total Tools**: ${stats.totalTools} - **Workflow Groups**: ${stats.workflowCount} --- -*This documentation is automatically generated by \`scripts/update-tools-docs.ts\` using static analysis. Last updated: ${new Date().toISOString().split('T')[0]}* +*This documentation is automatically generated by \`scripts/update-tools-docs.ts\` from the tools manifest. Last updated: ${lastUpdated}* `; return content; } +/** + * Generate CLI tools documentation content + */ +type CliDocumentationStats = { + toolCount: number; + canonicalToolCount: number; + workflowCount: number; +}; + +type CliDocumentationResult = { + content: string; + stats: CliDocumentationStats; +}; + +function generateCliToolsDocumentation(manifest: ToolsManifest): CliDocumentationResult { + const workflowMeta = new Map(manifest.workflows.map((workflow) => [workflow.name, workflow])); + const toolsByWorkflow = new Map(); + let canonicalToolCount = 0; + for (const tool of manifest.tools) { + if (cliExcludedWorkflows.has(tool.workflow)) { + continue; + } + + if (tool.isCanonical) { + canonicalToolCount++; + } + + const tools = toolsByWorkflow.get(tool.workflow) ?? []; + const originWorkflow = tool.originWorkflow + ? workflowMeta.get(tool.originWorkflow)?.displayName ?? tool.originWorkflow + : undefined; + + tools.push({ + name: tool.cliName ?? tool.name, + description: tool.description, + isCanonical: tool.isCanonical, + originWorkflowDisplayName: originWorkflow, + }); + toolsByWorkflow.set(tool.workflow, tools); + } + + const sortedWorkflows = [...manifest.workflows] + .filter((workflow) => toolsByWorkflow.has(workflow.name)) + .filter((workflow) => !cliExcludedWorkflows.has(workflow.name)) + .sort((a, b) => a.displayName.localeCompare(b.displayName)); + + const workflowSections = sortedWorkflows + .map((workflow) => { + const tools = toolsByWorkflow.get(workflow.name) ?? []; + const meta = workflowMeta.get(workflow.name); + return generateWorkflowSection( + { + name: workflow.name, + displayName: meta?.displayName ?? workflow.name, + description: meta?.description ?? `${workflow.name} related tools`, + }, + tools, + ); + }) + .join('\n'); + + const workflowCount = sortedWorkflows.length; + const totalTools = Array.from(toolsByWorkflow.values()).reduce( + (sum, tools) => sum + tools.length, + 0, + ); + + const lastUpdated = `${new Date(manifest.generatedAt).toISOString()} UTC`; + + const content = `# XcodeBuildMCP CLI Tools Reference + +This document lists CLI tool names as exposed by \`xcodebuildmcp \`. + +XcodeBuildMCP provides ${canonicalToolCount} canonical tools organized into ${workflowCount} workflow groups. + +## Workflow Groups + +${workflowSections} +## Summary Statistics + +- **Canonical Tools**: ${canonicalToolCount} +- **Total Tools**: ${totalTools} +- **Workflow Groups**: ${workflowCount} + +--- + +*This documentation is automatically generated by \`scripts/update-tools-docs.ts\` from the tools manifest. Last updated: ${lastUpdated}* +`; + + return { + content, + stats: { + toolCount: totalTools, + canonicalToolCount, + workflowCount, + }, + }; +} + /** * Compare old and new content to show what changed */ @@ -177,46 +370,66 @@ async function main(): Promise { console.log(`${colors.cyan}📊 Analyzing tools...${colors.reset}`); - // Get current tool analysis - const analysis = await getStaticToolAnalysis(); + const manifest = loadManifest(); if (options.verbose) { console.log( - `${colors.green}✓ Found ${analysis.stats.canonicalTools} canonical tools in ${analysis.stats.workflowCount} workflows${colors.reset}`, - ); - console.log( - `${colors.green}✓ Found ${analysis.stats.reExportTools} re-export files${colors.reset}`, + `${colors.green}✓ Found ${manifest.stats.canonicalTools} canonical tools in ${manifest.stats.workflowCount} workflows${colors.reset}`, ); } // Generate new documentation content console.log(`${colors.cyan}📝 Generating documentation...${colors.reset}`); - const newContent = generateToolsDocumentation(analysis); + const mcpContent = generateToolsDocumentation(manifest); + const cliDocumentation = generateCliToolsDocumentation(manifest); + const cliContent = cliDocumentation.content; + const cliStats = cliDocumentation.stats; - // Read current content for comparison - let oldContent = ''; - if (fs.existsSync(docsPath)) { - oldContent = fs.readFileSync(docsPath, 'utf-8'); - } + const targets = [ + { label: 'MCP tools', path: docsPath, content: mcpContent }, + { label: 'CLI tools', path: docsCliPath, content: cliContent }, + ]; + + const changes = targets.map((target) => { + const existing = fs.existsSync(target.path) + ? fs.readFileSync(target.path, 'utf-8') + : ''; + + const changed = existing !== target.content; + return { ...target, existing, changed }; + }); + + const changedTargets = changes.filter((target) => target.changed); // Check if content has changed - if (oldContent === newContent) { + if (changedTargets.length === 0) { console.log(`${colors.green}✅ Documentation is already up to date!${colors.reset}`); return; } // Show differences if verbose - if (oldContent && options.verbose) { - showDiff(oldContent, newContent); + if (options.verbose) { + for (const target of changedTargets) { + if (target.existing) { + console.log( + `${colors.bright}${colors.magenta}📄 ${target.label} content comparison:${colors.reset}`, + ); + showDiff(target.existing, target.content); + } + } } if (options.dryRun) { console.log( `${colors.yellow}📋 Dry run completed. Documentation would be updated with:${colors.reset}`, ); - console.log(` - ${analysis.stats.canonicalTools} canonical tools`); - console.log(` - ${analysis.stats.workflowCount} workflow groups`); - console.log(` - ${newContent.split('\n').length} lines total`); + for (const target of changedTargets) { + console.log(` - ${path.relative(projectRoot, target.path)} (${target.label})`); + } + console.log(` - MCP tools: ${manifest.stats.canonicalTools} canonical tools`); + console.log(` - CLI tools: ${cliStats.toolCount} tools across ${cliStats.workflowCount} workflows`); + console.log(` - MCP lines: ${mcpContent.split('\n').length}`); + console.log(` - CLI lines: ${cliContent.split('\n').length}`); if (!options.verbose) { console.log(`\n${colors.cyan}💡 Use --verbose to see detailed changes${colors.reset}`); @@ -227,20 +440,24 @@ async function main(): Promise { // Write new content console.log(`${colors.cyan}✏️ Writing updated documentation...${colors.reset}`); - fs.writeFileSync(docsPath, newContent, 'utf-8'); - - console.log( - `${colors.green}✅ Successfully updated ${path.relative(projectRoot, docsPath)}!${colors.reset}`, - ); + for (const target of changedTargets) { + fs.writeFileSync(target.path, target.content, 'utf-8'); + console.log( + `${colors.green}✅ Successfully updated ${path.relative(projectRoot, target.path)}!${colors.reset}`, + ); + } if (options.verbose) { console.log(`\n${colors.bright}📈 Update Summary:${colors.reset}`); console.log( - ` Tools: ${analysis.stats.canonicalTools} canonical + ${analysis.stats.reExportTools} re-exports = ${analysis.stats.totalTools} total`, + ` MCP tools: ${manifest.stats.canonicalTools} canonical (${manifest.stats.totalTools} total)`, ); - console.log(` Workflows: ${analysis.stats.workflowCount}`); - console.log(` File size: ${(newContent.length / 1024).toFixed(1)}KB`); - console.log(` Lines: ${newContent.split('\n').length}`); + console.log(` MCP workflows: ${manifest.stats.workflowCount}`); + console.log(` CLI tools: ${cliStats.toolCount} across ${cliStats.workflowCount} workflows`); + console.log(` MCP file size: ${(mcpContent.length / 1024).toFixed(1)}KB`); + console.log(` CLI file size: ${(cliContent.length / 1024).toFixed(1)}KB`); + console.log(` MCP lines: ${mcpContent.split('\n').length}`); + console.log(` CLI lines: ${cliContent.split('\n').length}`); } } catch (error) { console.error(`${colors.red}❌ Error: ${(error as Error).message}${colors.reset}`); diff --git a/skills/xcodebuilmcp-cli/SKILL.md b/skills/xcodebuilmcp-cli/SKILL.md new file mode 100644 index 00000000..01a92c25 --- /dev/null +++ b/skills/xcodebuilmcp-cli/SKILL.md @@ -0,0 +1,155 @@ +--- +name: xcodebuildmcp-cli +description: Official skill for the XcodeBuildMCP CLI. Use for direct terminal workflows (build/test/run/debug/log/UI automation) and for learning tool names and arguments. +--- + +# XcodeBuildMCP CLI + +Use this skill when you need to operate XcodeBuildMCP from the terminal (not via MCP tool calls). Prefer the CLI over raw `xcodebuild`, `xcrun`, or `simctl`. + +## How To Explore Commands + +Always use `--help` to discover workflows, tools, and arguments. + +```bash +xcodebuildmcp --help +xcodebuildmcp tools --help +xcodebuildmcp tools --json +xcodebuildmcp --help +xcodebuildmcp --help +``` + +Notes: +- Use `--json '{...}'` for complex arguments and `--output json` for machine-readable results. + +## Root Commands + +- `tools` - List CLI tools (supports `--json`, `--flat`, `--verbose`, `--workflow `). +- `daemon` - Manage the per-workspace daemon (`status`, `start`, `stop`, `restart`, `list`). +- `--help`, `--version` - CLI help/version. + +## Workflows And Tool Commands + +### device +- `build-device` +- `get-device-app-path` +- `install-app-device` +- `launch-app-device` +- `list-devices` +- `stop-app-device` +- `test-device` + +### simulator +- `boot-sim` +- `build-run-sim` +- `build-sim` +- `get-sim-app-path` +- `install-app-sim` +- `launch-app-logs-sim` +- `launch-app-sim` +- `list-sims` +- `open-sim` +- `record-sim-video` +- `stop-app-sim` +- `test-sim` + +### logging +- `start-device-log-cap` +- `start-sim-log-cap` +- `stop-device-log-cap` +- `stop-sim-log-cap` + +### macos +- `build-macos` +- `build-run-macos` +- `get-mac-app-path` +- `launch-mac-app` +- `stop-mac-app` +- `test-macos` + +### project-discovery +- `discover-projs` +- `get-app-bundle-id` +- `get-mac-bundle-id` +- `list-schemes` +- `show-build-settings` + +### project-scaffolding +- `scaffold-ios-project` +- `scaffold-macos-project` + +### utilities +- `clean` + +### debugging +- `debug-attach-sim` +- `debug-breakpoint-add` +- `debug-breakpoint-remove` +- `debug-continue` +- `debug-detach` +- `debug-lldb-command` +- `debug-stack` +- `debug-variables` + +### simulator-management +- `erase-sims` +- `reset-sim-location` +- `set-sim-appearance` +- `set-sim-location` +- `sim-statusbar` + +### swift-package +- `build` +- `clean` +- `list` +- `run` +- `stop` +- `test` + +### doctor +- `doctor` + +### ui-automation +- `button` +- `gesture` +- `key-press` +- `key-sequence` +- `long-press` +- `screenshot` +- `snapshot-ui` +- `swipe` +- `tap` +- `touch` +- `type-text` + +## Example Workflows + +```bash +# Build for simulator +xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj + +# List simulators +xcodebuildmcp simulator list-sims + +# Run tests +xcodebuildmcp simulator test-sim --scheme MyAppTests --simulator-name "iPhone 17 Pro" + +# Start/stop log capture +xcodebuildmcp logging start-sim-log-cap --simulator-id --bundle-id com.example.MyApp +xcodebuildmcp logging stop-sim-log-cap --session-id + +# SwiftPM build +xcodebuildmcp swift-package build --package-path ./MyPackage + +# UI snapshot +xcodebuildmcp ui-automation snapshot-ui --simulator-id +``` + +## Daemon Notes (Stateful Tools) + +Stateful tools (logs, debug, video recording, background run) go through a per-workspace daemon that auto-starts. Use: + +```bash +xcodebuildmcp daemon status +xcodebuildmcp daemon restart +``` diff --git a/src/cli/commands/tools.ts b/src/cli/commands/tools.ts index 1d7db12e..2980f15a 100644 --- a/src/cli/commands/tools.ts +++ b/src/cli/commands/tools.ts @@ -1,15 +1,105 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; import type { Argv } from 'yargs'; -import type { ToolCatalog } from '../../runtime/types.ts'; import { formatToolList } from '../output.ts'; +type ToolsManifestEntry = { + name: string; + cliName: string; + workflow: string; + description: string; + originWorkflow?: string; + isCanonical: boolean; + stateful: boolean; +}; + +type ToolsManifest = { + tools: ToolsManifestEntry[]; +}; + function writeLine(text: string): void { process.stdout.write(`${text}\n`); } +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const manifestPath = path.resolve(__dirname, 'tools-manifest.json'); +const CLI_EXCLUDED_WORKFLOWS = new Set(['session-management', 'workflow-discovery']); + +function loadManifest(): ToolsManifest { + if (!fs.existsSync(manifestPath)) { + throw new Error(`Missing tools manifest at ${manifestPath}. Run "npm run build" first.`); + } + + const raw = fs.readFileSync(manifestPath, 'utf-8'); + return JSON.parse(raw) as ToolsManifest; +} + +type ToolListItem = { + cliName: string; + command: string; + workflow: string; + description: string; + stateful: boolean; + isCanonical: boolean; + originWorkflow?: string; +}; + +type JsonToolBase = { + name: string; + command: string; + description: string; + stateful: boolean; +}; + +type JsonTool = JsonToolBase & { + canonicalWorkflow?: string; +}; + +type JsonToolWithWorkflow = JsonTool & { + workflow: string; +}; + +function toJsonToolBase(tool: ToolListItem): JsonToolBase { + return { + name: tool.cliName, + command: tool.command, + description: tool.description, + stateful: tool.stateful, + }; +} + +function withCanonicalWorkflow( + tool: ToolListItem, + base: T, +): T & { + canonicalWorkflow?: string; +} { + if (!tool.isCanonical && tool.originWorkflow) { + return { ...base, canonicalWorkflow: tool.originWorkflow }; + } + + return base; +} + +function toFlatJsonTool(tool: ToolListItem): JsonToolWithWorkflow { + const base = { + workflow: tool.workflow, + ...toJsonToolBase(tool), + }; + + return withCanonicalWorkflow(tool, base); +} + +function toGroupedJsonTool(tool: ToolListItem): JsonTool { + return withCanonicalWorkflow(tool, toJsonToolBase(tool)); +} + /** * Register the 'tools' command for listing available tools. */ -export function registerToolsCommand(app: Argv, catalog: ToolCatalog): void { +export function registerToolsCommand(app: Argv): void { app.command( 'tools', 'List available tools', @@ -39,22 +129,75 @@ export function registerToolsCommand(app: Argv, catalog: ToolCatalog): void { }); }, (argv) => { - let tools = catalog.tools.map((t) => ({ - cliName: t.cliName, - mcpName: t.mcpName, - workflow: t.workflow, - description: t.description, - stateful: t.stateful, - })); + const manifest = loadManifest(); + let tools: ToolListItem[] = manifest.tools + .filter((t) => !CLI_EXCLUDED_WORKFLOWS.has(t.workflow)) + .map((t) => ({ + cliName: t.cliName, + command: `${t.workflow} ${t.cliName}`, + workflow: t.workflow, + description: t.description, + stateful: t.stateful, + isCanonical: t.isCanonical, + originWorkflow: t.originWorkflow, + })); // Filter by workflow if specified if (argv.workflow) { const workflowFilter = (argv.workflow as string).toLowerCase(); - tools = tools.filter((t) => t.workflow.toLowerCase().includes(workflowFilter)); + tools = tools.filter((t) => t.workflow.toLowerCase() === workflowFilter); } if (argv.json) { - writeLine(JSON.stringify(tools, null, 2)); + if (argv.flat) { + const flatTools = [...tools] + .sort((a, b) => { + const aKey = `${a.workflow} ${a.cliName}`; + const bKey = `${b.workflow} ${b.cliName}`; + return aKey.localeCompare(bKey); + }) + .map((tool) => toFlatJsonTool(tool)); + + writeLine( + JSON.stringify( + { + toolCount: flatTools.length, + tools: flatTools, + }, + null, + 2, + ), + ); + return; + } + + const workflows = new Map(); + for (const tool of tools) { + const workflowTools = workflows.get(tool.workflow) ?? []; + workflowTools.push(tool); + workflows.set(tool.workflow, workflowTools); + } + + const grouped = Array.from(workflows.entries()) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([workflow, workflowTools]) => ({ + workflow, + tools: [...workflowTools] + .sort((a, b) => a.cliName.localeCompare(b.cliName)) + .map((tool) => toGroupedJsonTool(tool)), + })); + + writeLine( + JSON.stringify( + { + workflowCount: grouped.length, + toolCount: tools.length, + workflows: grouped, + }, + null, + 2, + ), + ); } else { const count = tools.length; writeLine(`Available tools (${count}):\n`); diff --git a/src/cli/yargs-app.ts b/src/cli/yargs-app.ts index 79610fc3..1fbe6753 100644 --- a/src/cli/yargs-app.ts +++ b/src/cli/yargs-app.ts @@ -85,7 +85,7 @@ export function buildYargsApp(opts: YargsAppOptions): ReturnType { workspaceRoot: opts.workspaceRoot, workspaceKey: opts.workspaceKey, }); - registerToolsCommand(app, opts.catalog); + registerToolsCommand(app); registerToolCommands(app, opts.catalog, { workspaceRoot: opts.workspaceRoot, enabledWorkflows: opts.enabledWorkflows, diff --git a/src/core/generated-plugins.ts b/src/core/generated-plugins.ts index 86c36af8..236a035d 100644 --- a/src/core/generated-plugins.ts +++ b/src/core/generated-plugins.ts @@ -225,7 +225,10 @@ export const WORKFLOW_LOADERS = { ); const tool_16 = await import('../mcp/tools/simulator/snapshot_ui.ts').then((m) => m.default); const tool_17 = await import('../mcp/tools/simulator/stop_app_sim.ts').then((m) => m.default); - const tool_18 = await import('../mcp/tools/simulator/test_sim.ts').then((m) => m.default); + const tool_18 = await import('../mcp/tools/simulator/stop_sim_log_cap.ts').then( + (m) => m.default, + ); + const tool_19 = await import('../mcp/tools/simulator/test_sim.ts').then((m) => m.default); return { workflow, @@ -247,7 +250,8 @@ export const WORKFLOW_LOADERS = { show_build_settings: tool_15, snapshot_ui: tool_16, stop_app_sim: tool_17, - test_sim: tool_18, + stop_sim_log_cap: tool_18, + test_sim: tool_19, }; }, 'simulator-management': async () => { diff --git a/src/mcp/tools/simulator/stop_sim_log_cap.ts b/src/mcp/tools/simulator/stop_sim_log_cap.ts new file mode 100644 index 00000000..d73b2a0f --- /dev/null +++ b/src/mcp/tools/simulator/stop_sim_log_cap.ts @@ -0,0 +1,2 @@ +// Re-export from logging to complete workflow +export { default } from '../logging/stop_sim_log_cap.ts'; From a24ece750e3b3aaf8ed930b379646fb0c584a69e Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 3 Feb 2026 14:34:11 +0000 Subject: [PATCH 2/5] Update SKILLs and SKILL installer --- README.md | 5 +- scripts/install-skill.sh | 167 ++++++++++++++++-- scripts/release.sh | 5 + skills/xcodebuildmcp/SKILL.md | 8 +- skills/xcodebuilmcp-cli/SKILL.md | 279 +++++++++++++++++-------------- 5 files changed, 325 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index 62f8aaab..d2d38c8f 100644 --- a/README.md +++ b/README.md @@ -160,9 +160,10 @@ When configuring a client manually, ensure the command includes the `mcp` subcom XcodeBuildMCP now includes an optional agent skill. Some clients (e.g., Cursor, Claude Code) hide MCP tool schemas behind search/progressive disclosure, which can reduce tool discovery and usage. The skill provides a concise overview of available tools to counter that. If your client already exposes tools up front, you likely don’t need it; only use it if your agent isn’t reaching for XcodeBuildMCP tools. -To install, replace `` with your client (cursor, claude, codex): +To install, download and run the installer in a terminal, then choose your client when prompted: ```bash -curl -fsSL https://raw.githubusercontent.com/cameroncooke/XcodeBuildMCP/main/scripts/install-skill.sh | bash -s -- -- +curl -fsSL https://raw.githubusercontent.com/cameroncooke/XcodeBuildMCP/main/scripts/install-skill.sh -o install-skill.sh +bash install-skill.sh ``` For further information on how to install the skill, see: [docs/SKILLS.md](docs/SKILLS.md) diff --git a/scripts/install-skill.sh b/scripts/install-skill.sh index 0579fa51..6a8b470a 100755 --- a/scripts/install-skill.sh +++ b/scripts/install-skill.sh @@ -3,15 +3,23 @@ set -euo pipefail usage() { cat <<'EOF' -Usage: install-skill.sh --codex|--claude|--cursor|--dest +Usage: install-skill.sh --codex|--claude|--cursor|--dest [--skill ] [--ref ] [--remove-conflict] -Installs (or replaces) the XcodeBuildMCP skill. +Installs (or replaces) the XcodeBuildMCP skill. If --skill is omitted, the +installer will ask which skill to install. +If the script is run from a local checkout, it installs the local skill file. +Otherwise it downloads the skill from the provided --ref or from main. -You must choose a destination with --codex, --claude, --cursor, or --dest. +If no destination is provided, the installer will prompt for a client. EOF } destination="" +skill_choice="" +skill_ref_override="" +remove_conflict="false" +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/.." && pwd)" while [[ $# -gt 0 ]]; do case "$1" in @@ -36,6 +44,28 @@ while [[ $# -gt 0 ]]; do destination="$2" shift 2 ;; + --skill) + if [[ $# -lt 2 ]]; then + echo "Missing value for --skill" >&2 + usage + exit 1 + fi + skill_choice="$2" + shift 2 + ;; + --ref) + if [[ $# -lt 2 ]]; then + echo "Missing value for --ref" >&2 + usage + exit 1 + fi + skill_ref_override="$2" + shift 2 + ;; + --remove-conflict) + remove_conflict="true" + shift + ;; -h|--help) usage exit 0 @@ -48,17 +78,132 @@ while [[ $# -gt 0 ]]; do esac done +prompt_for_destination() { + while true; do + printf "Which client should receive the skill?\n" + printf "1) Codex\n" + printf "2) Claude\n" + printf "3) Cursor\n" + read -r -p "Enter 1, 2, or 3: " selection + case "${selection}" in + 1) + destination="${HOME}/.codex/skills/public" + return 0 + ;; + 2) + destination="${HOME}/.claude/skills" + return 0 + ;; + 3) + destination="${HOME}/.cursor/skills" + return 0 + ;; + *) + echo "Invalid selection. Please enter 1, 2, or 3." + ;; + esac + done +} + +prompt_for_skill() { + while true; do + printf "Which skill would you like to install?\n" + printf "1) XcodeBuildMCP (MCP server)\n" + printf "2) XcodeBuildMCP CLI\n" + read -r -p "Enter 1 or 2: " selection + case "${selection}" in + 1) + skill_choice="mcp" + return 0 + ;; + 2) + skill_choice="cli" + return 0 + ;; + *) + echo "Invalid selection. Please enter 1 or 2." + ;; + esac + done +} + if [[ -z "${destination}" ]]; then - echo "Missing destination option." >&2 - usage - exit 1 + prompt_for_destination +fi + +if [[ -z "${skill_choice}" ]]; then + prompt_for_skill fi -skill_dir="${destination}/xcodebuildmcp" -skill_url="https://raw.githubusercontent.com/cameroncooke/XcodeBuildMCP/main/skills/xcodebuildmcp/SKILL.md" +case "${skill_choice}" in + mcp|server|xcodebuildmcp) + skill_dir_name="xcodebuildmcp" + skill_label="XcodeBuildMCP (MCP server)" + alt_dir_name="xcodebuilmcp-cli" + alt_label="XcodeBuildMCP CLI" + ;; + cli|xcodebuildmcp-cli) + skill_dir_name="xcodebuilmcp-cli" + skill_label="XcodeBuildMCP CLI" + alt_dir_name="xcodebuildmcp" + alt_label="XcodeBuildMCP (MCP server)" + ;; + *) + echo "Unknown skill: ${skill_choice}" >&2 + usage + exit 1 + ;; +esac -rm -rf "${skill_dir}" +skill_dir="${destination}/${skill_dir_name}" +alt_dir="${destination}/${alt_dir_name}" +skill_path="skills/${skill_dir_name}/SKILL.md" +skill_base_url="https://raw.githubusercontent.com/cameroncooke/XcodeBuildMCP" +skill_ref="main" + +if [[ -n "${skill_ref_override}" ]]; then + skill_ref="${skill_ref_override}" +fi + +if [[ -e "${alt_dir}" ]]; then + if [[ "${remove_conflict}" == "true" ]]; then + rm -r "${alt_dir}" + else + printf "%s\n" "Only one skill can be installed at a time because the MCP and CLI skills conflict." + read -r -p "Found ${alt_label} at ${alt_dir}. Remove it to continue? [y/N]: " confirm + case "${confirm}" in + y|Y|yes|YES) + rm -r "${alt_dir}" + ;; + *) + echo "Aborting to avoid installing both skills." + exit 1 + ;; + esac + fi +fi + +if [[ -e "${skill_dir}" ]]; then + rm -r "${skill_dir}" +fi mkdir -p "${skill_dir}" -curl -fsSL "${skill_url}" -o "${skill_dir}/SKILL.md" -printf 'Installed XcodeBuildMCP skill to %s\n' "${skill_dir}" +primary_url="${skill_base_url}/${skill_ref}/${skill_path}" +fallback_url="${skill_base_url}/main/${skill_path}" +local_skill_path="${repo_root}/${skill_path}" + +if [[ -f "${local_skill_path}" ]]; then + cp "${local_skill_path}" "${skill_dir}/SKILL.md" +else + if ! curl -fsSL "${primary_url}" -o "${skill_dir}/SKILL.md"; then + if [[ "${skill_ref}" != "main" ]]; then + printf "%s\n" "Release tag ${skill_ref} not found. Falling back to main." + curl -fsSL "${fallback_url}" -o "${skill_dir}/SKILL.md" + else + printf "%s\n" "Failed to download ${primary_url}." >&2 + exit 1 + fi + fi +fi + +printf 'Installed %s to %s\n' "${skill_label}" "${skill_dir}" diff --git a/scripts/release.sh b/scripts/release.sh index f19bcf9d..f229ddef 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -313,6 +313,11 @@ if [[ "$SKIP_VERSION_UPDATE" == "false" ]]; then README_URLENCODED_NPM_AT_SEMVER_REGEX='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]+)?' run sed_inplace "s/${README_URLENCODED_NPM_AT_SEMVER_REGEX}/npm%3Axcodebuildmcp%40${VERSION}/g" README.md + # Update skill installer URL and versioned ref in README.md + echo "📝 Updating skill installer URL in README.md..." + README_SKILL_INSTALL_URL_REGEX='https://raw.githubusercontent.com/cameroncooke/XcodeBuildMCP/[^/]+/scripts/install-skill.sh' + run sed_inplace "s#${README_SKILL_INSTALL_URL_REGEX}#https://raw.githubusercontent.com/cameroncooke/XcodeBuildMCP/v${VERSION}/scripts/install-skill.sh#g" README.md + # server.json update echo "" if [[ -f server.json ]]; then diff --git a/skills/xcodebuildmcp/SKILL.md b/skills/xcodebuildmcp/SKILL.md index a398e092..5fbf388e 100644 --- a/skills/xcodebuildmcp/SKILL.md +++ b/skills/xcodebuildmcp/SKILL.md @@ -1,19 +1,19 @@ --- name: xcodebuildmcp -description: Official skill for XcodeBuildMCP (preferred). Use when doing iOS/macOS/watchOS/tvOS/visionOS work (build, test, run, debug, log, UI automation). +description: Official skill for XcodeBuildMCP. Use when doing iOS/macOS/watchOS/tvOS/visionOS work (build, test, run, debug, log, UI automation). --- # XcodeBuildMCP -Prefer XcodeBuildMCP tools over shell commands for Apple platform tasks when available. +Prefer XcodeBuildMCP over raw `xcodebuild`, `xcrun`, or `simctl`. -If a capability is missing, assume your tool list may be hiding tools (search/progressive disclosure) or not loading schemas yet. Use your tool-search or “load tools” mechanism. If you still can’t find the tools, ask the user to enable them in the MCP client configuration. +If a capability is missing, assume your tool list may be hiding tools (search/progressive disclosure) or not loading tool schemas yet. Use your tool-search or “load tools” mechanism. If you still can’t find the tools, ask the user to enable them in the MCP client's configuration. ## Tools (exact names + official descriptions) ### Session defaults -Before you call any other tools, call `session_show_defaults` to show the current defaults, ensure you then fill in the appropriate missing defaults. You may need to call one or more discovery/list tools to obtain the values needed for certain defaults. +Before you call any other tools, you **must** call `session_show_defaults` to show the current defaults, ensure you then fill in the appropriate missing defaults. You may need to call one or more discovery/list tools to obtain the values needed. - `session_set_defaults` - Set the session defaults, should be called at least once to set tool defaults. diff --git a/skills/xcodebuilmcp-cli/SKILL.md b/skills/xcodebuilmcp-cli/SKILL.md index 01a92c25..e6208857 100644 --- a/skills/xcodebuilmcp-cli/SKILL.md +++ b/skills/xcodebuilmcp-cli/SKILL.md @@ -1,15 +1,22 @@ --- name: xcodebuildmcp-cli -description: Official skill for the XcodeBuildMCP CLI. Use for direct terminal workflows (build/test/run/debug/log/UI automation) and for learning tool names and arguments. +description: Official skill for the XcodeBuildMCP CLI. Use when doing iOS/macOS/watchOS/tvOS/visionOS work (build, test, run, debug, log, UI automation). --- # XcodeBuildMCP CLI -Use this skill when you need to operate XcodeBuildMCP from the terminal (not via MCP tool calls). Prefer the CLI over raw `xcodebuild`, `xcrun`, or `simctl`. +This skill is for AI agents. It positions the XcodeBuildMCP CLI as a low‑overhead alternative to MCP tool calls: agents can already run shell commands, and the CLI exposes the same tool surface without the schema‑exchange cost. Prefer the CLI over raw `xcodebuild`, `xcrun`, or `simctl`. -## How To Explore Commands +## When To Use This CLI (Capabilities And Workflows) -Always use `--help` to discover workflows, tools, and arguments. +- When you need build/test/run/debugging/logging/UI automation capabilities. +- When you want simulator/device management capabilities. +- When you want AI optimized tools and tool responses. +- When you need project discovery capabilities (schemes, bundle IDs, app paths). + +## Command Discovery + +Use `--help` to discover workflows, tools, and arguments. ```bash xcodebuildmcp --help @@ -20,136 +27,164 @@ xcodebuildmcp --help ``` Notes: -- Use `--json '{...}'` for complex arguments and `--output json` for machine-readable results. - -## Root Commands - -- `tools` - List CLI tools (supports `--json`, `--flat`, `--verbose`, `--workflow `). -- `daemon` - Manage the per-workspace daemon (`status`, `start`, `stop`, `restart`, `list`). -- `--help`, `--version` - CLI help/version. - -## Workflows And Tool Commands - -### device -- `build-device` -- `get-device-app-path` -- `install-app-device` -- `launch-app-device` -- `list-devices` -- `stop-app-device` -- `test-device` - -### simulator -- `boot-sim` -- `build-run-sim` -- `build-sim` -- `get-sim-app-path` -- `install-app-sim` -- `launch-app-logs-sim` -- `launch-app-sim` -- `list-sims` -- `open-sim` -- `record-sim-video` -- `stop-app-sim` -- `test-sim` - -### logging -- `start-device-log-cap` -- `start-sim-log-cap` -- `stop-device-log-cap` -- `stop-sim-log-cap` - -### macos -- `build-macos` -- `build-run-macos` -- `get-mac-app-path` -- `launch-mac-app` -- `stop-mac-app` -- `test-macos` - -### project-discovery -- `discover-projs` -- `get-app-bundle-id` -- `get-mac-bundle-id` -- `list-schemes` -- `show-build-settings` - -### project-scaffolding -- `scaffold-ios-project` -- `scaffold-macos-project` - -### utilities -- `clean` - -### debugging -- `debug-attach-sim` -- `debug-breakpoint-add` -- `debug-breakpoint-remove` -- `debug-continue` -- `debug-detach` -- `debug-lldb-command` -- `debug-stack` -- `debug-variables` - -### simulator-management -- `erase-sims` -- `reset-sim-location` -- `set-sim-appearance` -- `set-sim-location` -- `sim-statusbar` - -### swift-package -- `build` -- `clean` -- `list` -- `run` -- `stop` -- `test` - -### doctor -- `doctor` - -### ui-automation -- `button` -- `gesture` -- `key-press` -- `key-sequence` -- `long-press` -- `screenshot` -- `snapshot-ui` -- `swipe` -- `tap` -- `touch` -- `type-text` - -## Example Workflows - -```bash -# Build for simulator -xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj - -# List simulators +- Use `--json '{...}'` for complex arguments and `--output json` if you need machine-readable results (not recommended). + +## Common Workflows + +### Build And Run On Simulator + +1. List simulators and pick a device name or UDID. +2. Build and run. + +If app and project details are not known: +```bash +xcodebuildmcp simulator discover-projs --workspace-root . +xcodebuildmcp simulator list-schemes --project-path ./MyApp.xcodeproj xcodebuildmcp simulator list-sims +``` + +To build, install and launch the app in one command: +```bash +xcodebuildmcp simulator build-run-sim --scheme MyApp --project-path ./MyApp.xcodeproj --simulator-name "iPhone 17 Pro" +``` + +### Build only + +When you only need to check that there are no build errors, you can build without running the app. + +```bash +xcodebuildmcp simulator build-sim --scheme MyApp --project-path ./MyApp.xcodeproj --simulator-name "iPhone 17 Pro" +``` + +### Run Tests + +When you need to run tests, you can do so with the `test-sim` tool. -# Run tests -xcodebuildmcp simulator test-sim --scheme MyAppTests --simulator-name "iPhone 17 Pro" +```bash +xcodebuildmcp simulator test-sim --scheme MyAppTests --project-path ./MyApp.xcodeproj --simulator-name "iPhone 17 Pro" +``` -# Start/stop log capture -xcodebuildmcp logging start-sim-log-cap --simulator-id --bundle-id com.example.MyApp -xcodebuildmcp logging stop-sim-log-cap --session-id +### Install And Launch On Physical Device -# SwiftPM build +```bash +xcodebuildmcp device list-devices +xcodebuildmcp device build-device --scheme MyApp --project-path ./MyApp.xcodeproj +xcodebuildmcp device get-device-app-path --scheme MyApp --project-path ./MyApp.xcodeproj +xcodebuildmcp device get-app-bundle-id --app-path /path/to/MyApp.app +xcodebuildmcp device install-app-device --device-id DEVICE_UDID --app-path /path/to/MyApp.app +xcodebuildmcp device launch-app-device --device-id DEVICE_UDID --bundle-id com.example.MyApp --app-path /path/to/MyApp.app +``` + +### Capture Logs On Simulator + +```bash +xcodebuildmcp logging start-sim-log-cap --simulator-id SIMULATOR_UDID --bundle-id com.example.MyApp +xcodebuildmcp logging stop-sim-log-cap --log-session-id LOG_SESSION_ID +``` + +### Debug A Running App (Simulator) + +1. Launch the app. +2. Attach the debugger after the app is fully launched. + +Launch if not already running: +```bash +xcodebuildmcp simulator launch-app-sim --bundle-id com.example.MyApp --simulator-id SIMULATOR_UDID +``` + +Attach the debugger: + +It's generally a good idea to wait for 1-2s for the app to fully launch before attaching the debugger. + +```bash +xcodebuildmcp debugging debug-attach-sim --bundle-id com.example.MyApp --simulator-id SIMULATOR_UDID +``` + +To add/remove breakpoints, inspect stack/variables, and issue arbitrary LLDB commands, view debugging help: +```bash +xcodebuildmcp debugging --help +``` + + +### Inspect UI And Automate Input + +Snapshot UI accessibility tree, tap/swipe/type, and capture screenshots: + +```bash +xcodebuildmcp ui-automation snapshot-ui --simulator-id SIMULATOR_UDID +xcodebuildmcp ui-automation tap --simulator-id SIMULATOR_UDID --x 200 --y 400 +xcodebuildmcp ui-automation type-text --simulator-id SIMULATOR_UDID --text "hello" +xcodebuildmcp ui-automation screenshot --simulator-id SIMULATOR_UDID --return-format path +``` + +To see all UI automation tools, view UI automation help: +```bash +xcodebuildmcp ui-automation --help +``` + +### macOS App Build/Run + +```bash +xcodebuildmcp macos build-macos --scheme MyMacApp --project-path ./MyMacApp.xcodeproj +xcodebuildmcp macos build-run-macos --scheme MyMacApp --project-path ./MyMacApp.xcodeproj +``` + +To see all macOS tools, view macOS help: +```bash +xcodebuildmcp macos --help +``` + +### SwiftPM Package Workflows + +```bash +xcodebuildmcp swift-package list --package-path ./MyPackage xcodebuildmcp swift-package build --package-path ./MyPackage +xcodebuildmcp swift-package test --package-path ./MyPackage +``` + +To see all SwiftPM tools, view SwiftPM help: +```bash +xcodebuildmcp swift-package --help +``` + +### Project Discovery -# UI snapshot -xcodebuildmcp ui-automation snapshot-ui --simulator-id +```bash +xcodebuildmcp project-discovery discover-projs --workspace-root . +xcodebuildmcp project-discovery list-schemes --project-path ./MyApp.xcodeproj +xcodebuildmcp project-discovery get-app-bundle-id --app-path ./Build/MyApp.app +``` + +To see all project discovery tools, view project discovery help: +```bash +xcodebuildmcp project-discovery --help +``` + +### Scaffolding new projects + +It's worth viewing the --help for the scaffolding tools to see the available options. +Here are some minimal examples: + +```bash +xcodebuildmcp project-scaffolding scaffold-ios-project --project-name MyApp --output-path ./Projects +xcodebuildmcp project-scaffolding scaffold-macos-project --project-name MyMacApp --output-path ./Projects +``` + +To see all project scaffolding tools, view project scaffolding help: +```bash +xcodebuildmcp project-scaffolding --help ``` ## Daemon Notes (Stateful Tools) -Stateful tools (logs, debug, video recording, background run) go through a per-workspace daemon that auto-starts. Use: +Stateful tools (logs, debug, video recording, background run) go through a per-workspace daemon that auto-starts, if you find you are getting errors with the stateful tools, you can manage the daemon process manually. ```bash xcodebuildmcp daemon status xcodebuildmcp daemon restart ``` + +To see all daemon commands, view daemon help: +```bash +xcodebuildmcp daemon --help +``` \ No newline at end of file From cedacbfde7f8d88f6bac1935839e9147e4616e13 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Feb 2026 14:43:36 +0000 Subject: [PATCH 3/5] fix: remove redundant 'CLI interface' and add missing article in CLI docs Co-authored-by: web --- docs/CLI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CLI.md b/docs/CLI.md index a1d7eb3d..89f4ca7a 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,6 +1,6 @@ # XcodeBuildMCP CLI -`xcodebuildmcp` is a unified command-line interface that provides both an MCP server and direct tool access via first-class CLI interface. +`xcodebuildmcp` is a unified command-line interface that provides both an MCP server and direct tool access via a first-class CLI. Use `xcodebuildmcp` CLI to invoke tools or start the MCP server by passing the `mcp` argument. From 3785fbaa41db704f0e6c35cd84d66f959d46c6bc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Feb 2026 14:50:00 +0000 Subject: [PATCH 4/5] fix: rename xcodebuilmcp-cli to xcodebuildmcp-cli and update installer script Co-authored-by: web --- scripts/install-skill.sh | 4 ++-- skills/{xcodebuilmcp-cli => xcodebuildmcp-cli}/SKILL.md | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename skills/{xcodebuilmcp-cli => xcodebuildmcp-cli}/SKILL.md (100%) diff --git a/scripts/install-skill.sh b/scripts/install-skill.sh index 6a8b470a..87c3b3a8 100755 --- a/scripts/install-skill.sh +++ b/scripts/install-skill.sh @@ -139,11 +139,11 @@ case "${skill_choice}" in mcp|server|xcodebuildmcp) skill_dir_name="xcodebuildmcp" skill_label="XcodeBuildMCP (MCP server)" - alt_dir_name="xcodebuilmcp-cli" + alt_dir_name="xcodebuildmcp-cli" alt_label="XcodeBuildMCP CLI" ;; cli|xcodebuildmcp-cli) - skill_dir_name="xcodebuilmcp-cli" + skill_dir_name="xcodebuildmcp-cli" skill_label="XcodeBuildMCP CLI" alt_dir_name="xcodebuildmcp" alt_label="XcodeBuildMCP (MCP server)" diff --git a/skills/xcodebuilmcp-cli/SKILL.md b/skills/xcodebuildmcp-cli/SKILL.md similarity index 100% rename from skills/xcodebuilmcp-cli/SKILL.md rename to skills/xcodebuildmcp-cli/SKILL.md From c1836d1a3fd4546f2195b9f0b91fa2d6f044b9ab Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Feb 2026 14:51:59 +0000 Subject: [PATCH 5/5] refactor: remove unused getCanonicalTools and getToolsByWorkflow functions Co-authored-by: web --- scripts/analysis/tools-analysis.ts | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/scripts/analysis/tools-analysis.ts b/scripts/analysis/tools-analysis.ts index 8f210d64..036d5ad3 100644 --- a/scripts/analysis/tools-analysis.ts +++ b/scripts/analysis/tools-analysis.ts @@ -518,32 +518,6 @@ export async function getStaticToolAnalysis(): Promise { }; } -/** - * Get only canonical tools (excluding re-exports) for documentation generation - */ -export async function getCanonicalTools(): Promise { - const analysis = await getStaticToolAnalysis(); - return analysis.tools.filter((tool) => tool.isCanonical); -} - -/** - * Get tools grouped by workflow for documentation generation - */ -export async function getToolsByWorkflow(): Promise> { - const analysis = await getStaticToolAnalysis(); - const workflowMap = new Map(); - - for (const workflow of analysis.workflows) { - // Only include canonical tools for documentation - const canonicalTools = workflow.tools.filter((tool) => tool.isCanonical); - if (canonicalTools.length > 0) { - workflowMap.set(workflow.name, canonicalTools); - } - } - - return workflowMap; -} - // CLI support - if run directly, perform analysis and output results if (import.meta.url === `file://${process.argv[1]}`) { async function main(): Promise {