This is page 1 of 14. Use http://codebase.md/cameroncooke/xcodebuildmcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .axe-version ├── .claude │ └── agents │ └── xcodebuild-mcp-qa-tester.md ├── .cursor │ ├── BUGBOT.md │ └── environment.json ├── .cursorrules ├── .github │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ └── workflows │ ├── ci.yml │ ├── claude-code-review.yml │ ├── claude-dispatch.yml │ ├── claude.yml │ ├── droid-code-review.yml │ ├── README.md │ ├── release.yml │ └── sentry.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── mcp.json │ ├── settings.json │ └── tasks.json ├── AGENTS.md ├── banner.png ├── build-plugins │ ├── plugin-discovery.js │ ├── plugin-discovery.ts │ └── tsconfig.json ├── CHANGELOG.md ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── docs │ ├── ARCHITECTURE.md │ ├── CODE_QUALITY.md │ ├── CONTRIBUTING.md │ ├── ESLINT_TYPE_SAFETY.md │ ├── MANUAL_TESTING.md │ ├── NODEJS_2025.md │ ├── PLUGIN_DEVELOPMENT.md │ ├── RELEASE_PROCESS.md │ ├── RELOADEROO_FOR_XCODEBUILDMCP.md │ ├── RELOADEROO_XCODEBUILDMCP_PRIMER.md │ ├── RELOADEROO.md │ ├── session_management_plan.md │ ├── session-aware-migration-todo.md │ ├── TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md │ ├── TESTING.md │ └── TOOLS.md ├── eslint.config.js ├── example_projects │ ├── .vscode │ │ └── launch.json │ ├── iOS │ │ ├── .cursor │ │ │ └── rules │ │ │ └── errors.mdc │ │ ├── .vscode │ │ │ └── settings.json │ │ ├── Makefile │ │ ├── MCPTest │ │ │ ├── Assets.xcassets │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── MCPTestApp.swift │ │ │ └── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ ├── MCPTest.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── MCPTest.xcscheme │ │ └── MCPTestUITests │ │ └── MCPTestUITests.swift │ ├── iOS_Calculator │ │ ├── CalculatorApp │ │ │ ├── Assets.xcassets │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── CalculatorApp.swift │ │ │ └── CalculatorApp.xctestplan │ │ ├── CalculatorApp.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── CalculatorApp.xcscheme │ │ ├── CalculatorApp.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── CalculatorAppPackage │ │ │ ├── .gitignore │ │ │ ├── Package.swift │ │ │ ├── Sources │ │ │ │ └── CalculatorAppFeature │ │ │ │ ├── BackgroundEffect.swift │ │ │ │ ├── CalculatorButton.swift │ │ │ │ ├── CalculatorDisplay.swift │ │ │ │ ├── CalculatorInputHandler.swift │ │ │ │ ├── CalculatorService.swift │ │ │ │ └── ContentView.swift │ │ │ └── Tests │ │ │ └── CalculatorAppFeatureTests │ │ │ └── CalculatorServiceTests.swift │ │ ├── CalculatorAppTests │ │ │ └── CalculatorAppTests.swift │ │ └── Config │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Shared.xcconfig │ │ └── Tests.xcconfig │ ├── macOS │ │ ├── MCPTest │ │ │ ├── Assets.xcassets │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── MCPTest.entitlements │ │ │ ├── MCPTestApp.swift │ │ │ └── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── MCPTest.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ └── xcschemes │ │ └── MCPTest.xcscheme │ └── spm │ ├── .gitignore │ ├── Package.resolved │ ├── Package.swift │ ├── Sources │ │ ├── long-server │ │ │ └── main.swift │ │ ├── quick-task │ │ │ └── main.swift │ │ ├── spm │ │ │ └── main.swift │ │ └── TestLib │ │ └── TaskManager.swift │ └── Tests │ └── TestLibTests │ └── SimpleTests.swift ├── LICENSE ├── mcp-install-dark.png ├── package-lock.json ├── package.json ├── README.md ├── scripts │ ├── analysis │ │ └── tools-analysis.ts │ ├── bundle-axe.sh │ ├── check-code-patterns.js │ ├── release.sh │ ├── tools-cli.ts │ └── update-tools-docs.ts ├── server.json ├── smithery.yaml ├── src │ ├── core │ │ ├── __tests__ │ │ │ └── resources.test.ts │ │ ├── dynamic-tools.ts │ │ ├── plugin-registry.ts │ │ ├── plugin-types.ts │ │ └── resources.ts │ ├── doctor-cli.ts │ ├── index.ts │ ├── mcp │ │ ├── resources │ │ │ ├── __tests__ │ │ │ │ ├── devices.test.ts │ │ │ │ ├── doctor.test.ts │ │ │ │ └── simulators.test.ts │ │ │ ├── devices.ts │ │ │ ├── doctor.ts │ │ │ └── simulators.ts │ │ └── tools │ │ ├── device │ │ │ ├── __tests__ │ │ │ │ ├── build_device.test.ts │ │ │ │ ├── get_device_app_path.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── install_app_device.test.ts │ │ │ │ ├── launch_app_device.test.ts │ │ │ │ ├── list_devices.test.ts │ │ │ │ ├── re-exports.test.ts │ │ │ │ ├── stop_app_device.test.ts │ │ │ │ └── test_device.test.ts │ │ │ ├── build_device.ts │ │ │ ├── clean.ts │ │ │ ├── discover_projs.ts │ │ │ ├── get_app_bundle_id.ts │ │ │ ├── get_device_app_path.ts │ │ │ ├── index.ts │ │ │ ├── install_app_device.ts │ │ │ ├── launch_app_device.ts │ │ │ ├── list_devices.ts │ │ │ ├── list_schemes.ts │ │ │ ├── show_build_settings.ts │ │ │ ├── start_device_log_cap.ts │ │ │ ├── stop_app_device.ts │ │ │ ├── stop_device_log_cap.ts │ │ │ └── test_device.ts │ │ ├── discovery │ │ │ ├── __tests__ │ │ │ │ └── discover_tools.test.ts │ │ │ ├── discover_tools.ts │ │ │ └── index.ts │ │ ├── doctor │ │ │ ├── __tests__ │ │ │ │ ├── doctor.test.ts │ │ │ │ └── index.test.ts │ │ │ ├── doctor.ts │ │ │ ├── index.ts │ │ │ └── lib │ │ │ └── doctor.deps.ts │ │ ├── logging │ │ │ ├── __tests__ │ │ │ │ ├── index.test.ts │ │ │ │ ├── start_device_log_cap.test.ts │ │ │ │ ├── start_sim_log_cap.test.ts │ │ │ │ ├── stop_device_log_cap.test.ts │ │ │ │ └── stop_sim_log_cap.test.ts │ │ │ ├── index.ts │ │ │ ├── start_device_log_cap.ts │ │ │ ├── start_sim_log_cap.ts │ │ │ ├── stop_device_log_cap.ts │ │ │ └── stop_sim_log_cap.ts │ │ ├── macos │ │ │ ├── __tests__ │ │ │ │ ├── build_macos.test.ts │ │ │ │ ├── build_run_macos.test.ts │ │ │ │ ├── get_mac_app_path.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── launch_mac_app.test.ts │ │ │ │ ├── re-exports.test.ts │ │ │ │ ├── stop_mac_app.test.ts │ │ │ │ └── test_macos.test.ts │ │ │ ├── build_macos.ts │ │ │ ├── build_run_macos.ts │ │ │ ├── clean.ts │ │ │ ├── discover_projs.ts │ │ │ ├── get_mac_app_path.ts │ │ │ ├── get_mac_bundle_id.ts │ │ │ ├── index.ts │ │ │ ├── launch_mac_app.ts │ │ │ ├── list_schemes.ts │ │ │ ├── show_build_settings.ts │ │ │ ├── stop_mac_app.ts │ │ │ └── test_macos.ts │ │ ├── project-discovery │ │ │ ├── __tests__ │ │ │ │ ├── discover_projs.test.ts │ │ │ │ ├── get_app_bundle_id.test.ts │ │ │ │ ├── get_mac_bundle_id.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── list_schemes.test.ts │ │ │ │ └── show_build_settings.test.ts │ │ │ ├── discover_projs.ts │ │ │ ├── get_app_bundle_id.ts │ │ │ ├── get_mac_bundle_id.ts │ │ │ ├── index.ts │ │ │ ├── list_schemes.ts │ │ │ └── show_build_settings.ts │ │ ├── project-scaffolding │ │ │ ├── __tests__ │ │ │ │ ├── index.test.ts │ │ │ │ ├── scaffold_ios_project.test.ts │ │ │ │ └── scaffold_macos_project.test.ts │ │ │ ├── index.ts │ │ │ ├── scaffold_ios_project.ts │ │ │ └── scaffold_macos_project.ts │ │ ├── session-management │ │ │ ├── __tests__ │ │ │ │ ├── index.test.ts │ │ │ │ ├── session_clear_defaults.test.ts │ │ │ │ ├── session_set_defaults.test.ts │ │ │ │ └── session_show_defaults.test.ts │ │ │ ├── index.ts │ │ │ ├── session_clear_defaults.ts │ │ │ ├── session_set_defaults.ts │ │ │ └── session_show_defaults.ts │ │ ├── simulator │ │ │ ├── __tests__ │ │ │ │ ├── boot_sim.test.ts │ │ │ │ ├── build_run_sim.test.ts │ │ │ │ ├── build_sim.test.ts │ │ │ │ ├── get_sim_app_path.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── install_app_sim.test.ts │ │ │ │ ├── launch_app_logs_sim.test.ts │ │ │ │ ├── launch_app_sim.test.ts │ │ │ │ ├── list_sims.test.ts │ │ │ │ ├── open_sim.test.ts │ │ │ │ ├── record_sim_video.test.ts │ │ │ │ ├── screenshot.test.ts │ │ │ │ ├── stop_app_sim.test.ts │ │ │ │ └── test_sim.test.ts │ │ │ ├── boot_sim.ts │ │ │ ├── build_run_sim.ts │ │ │ ├── build_sim.ts │ │ │ ├── clean.ts │ │ │ ├── describe_ui.ts │ │ │ ├── discover_projs.ts │ │ │ ├── get_app_bundle_id.ts │ │ │ ├── get_sim_app_path.ts │ │ │ ├── index.ts │ │ │ ├── install_app_sim.ts │ │ │ ├── launch_app_logs_sim.ts │ │ │ ├── launch_app_sim.ts │ │ │ ├── list_schemes.ts │ │ │ ├── list_sims.ts │ │ │ ├── open_sim.ts │ │ │ ├── record_sim_video.ts │ │ │ ├── screenshot.ts │ │ │ ├── show_build_settings.ts │ │ │ ├── stop_app_sim.ts │ │ │ └── test_sim.ts │ │ ├── simulator-management │ │ │ ├── __tests__ │ │ │ │ ├── erase_sims.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── reset_sim_location.test.ts │ │ │ │ ├── set_sim_appearance.test.ts │ │ │ │ ├── set_sim_location.test.ts │ │ │ │ └── sim_statusbar.test.ts │ │ │ ├── boot_sim.ts │ │ │ ├── erase_sims.ts │ │ │ ├── index.ts │ │ │ ├── list_sims.ts │ │ │ ├── open_sim.ts │ │ │ ├── reset_sim_location.ts │ │ │ ├── set_sim_appearance.ts │ │ │ ├── set_sim_location.ts │ │ │ └── sim_statusbar.ts │ │ ├── swift-package │ │ │ ├── __tests__ │ │ │ │ ├── active-processes.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── swift_package_build.test.ts │ │ │ │ ├── swift_package_clean.test.ts │ │ │ │ ├── swift_package_list.test.ts │ │ │ │ ├── swift_package_run.test.ts │ │ │ │ ├── swift_package_stop.test.ts │ │ │ │ └── swift_package_test.test.ts │ │ │ ├── active-processes.ts │ │ │ ├── index.ts │ │ │ ├── swift_package_build.ts │ │ │ ├── swift_package_clean.ts │ │ │ ├── swift_package_list.ts │ │ │ ├── swift_package_run.ts │ │ │ ├── swift_package_stop.ts │ │ │ └── swift_package_test.ts │ │ ├── ui-testing │ │ │ ├── __tests__ │ │ │ │ ├── button.test.ts │ │ │ │ ├── describe_ui.test.ts │ │ │ │ ├── gesture.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── key_press.test.ts │ │ │ │ ├── key_sequence.test.ts │ │ │ │ ├── long_press.test.ts │ │ │ │ ├── screenshot.test.ts │ │ │ │ ├── swipe.test.ts │ │ │ │ ├── tap.test.ts │ │ │ │ ├── touch.test.ts │ │ │ │ └── type_text.test.ts │ │ │ ├── button.ts │ │ │ ├── describe_ui.ts │ │ │ ├── gesture.ts │ │ │ ├── index.ts │ │ │ ├── key_press.ts │ │ │ ├── key_sequence.ts │ │ │ ├── long_press.ts │ │ │ ├── screenshot.ts │ │ │ ├── swipe.ts │ │ │ ├── tap.ts │ │ │ ├── touch.ts │ │ │ └── type_text.ts │ │ └── utilities │ │ ├── __tests__ │ │ │ ├── clean.test.ts │ │ │ └── index.test.ts │ │ ├── clean.ts │ │ └── index.ts │ ├── server │ │ └── server.ts │ ├── test-utils │ │ └── mock-executors.ts │ ├── types │ │ └── common.ts │ └── utils │ ├── __tests__ │ │ ├── build-utils.test.ts │ │ ├── environment.test.ts │ │ ├── session-aware-tool-factory.test.ts │ │ ├── session-store.test.ts │ │ ├── simulator-utils.test.ts │ │ ├── test-runner-env-integration.test.ts │ │ └── typed-tool-factory.test.ts │ ├── axe │ │ └── index.ts │ ├── axe-helpers.ts │ ├── build │ │ └── index.ts │ ├── build-utils.ts │ ├── capabilities.ts │ ├── command.ts │ ├── CommandExecutor.ts │ ├── environment.ts │ ├── errors.ts │ ├── execution │ │ └── index.ts │ ├── FileSystemExecutor.ts │ ├── log_capture.ts │ ├── log-capture │ │ └── index.ts │ ├── logger.ts │ ├── logging │ │ └── index.ts │ ├── plugin-registry │ │ └── index.ts │ ├── responses │ │ └── index.ts │ ├── schema-helpers.ts │ ├── sentry.ts │ ├── session-store.ts │ ├── simulator-utils.ts │ ├── template │ │ └── index.ts │ ├── template-manager.ts │ ├── test │ │ └── index.ts │ ├── test-common.ts │ ├── tool-registry.ts │ ├── typed-tool-factory.ts │ ├── validation │ │ └── index.ts │ ├── validation.ts │ ├── version │ │ └── index.ts │ ├── video_capture.ts │ ├── video-capture │ │ └── index.ts │ ├── xcode.ts │ ├── xcodemake │ │ └── index.ts │ └── xcodemake.ts ├── tsconfig.json ├── tsconfig.test.json ├── tsup.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /.axe-version: -------------------------------------------------------------------------------- ``` 1 | 1.1.1 2 | ``` -------------------------------------------------------------------------------- /.cursorrules: -------------------------------------------------------------------------------- ``` 1 | AGENTS.md ``` -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- ``` 1 | node_modules 2 | build 3 | dist 4 | coverage 5 | *.json 6 | *.md 7 | ``` -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- ```javascript 1 | export default { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 100, 6 | tabWidth: 2, 7 | endOfLine: 'auto', 8 | }; 9 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorAppPackage/.gitignore: -------------------------------------------------------------------------------- ``` 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | ``` -------------------------------------------------------------------------------- /example_projects/spm/.gitignore: -------------------------------------------------------------------------------- ``` 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # TypeScript build output 8 | dist/ 9 | /build/ 10 | *.tsbuildinfo 11 | 12 | # Auto-generated files 13 | src/version.ts 14 | src/core/generated-plugins.ts 15 | src/core/generated-resources.ts 16 | 17 | # IDE and editor files 18 | .idea/ 19 | .vscode/* 20 | !.vscode/mcp.json 21 | !.vscode/launch.json 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/extensions.json 25 | *.swp 26 | *.swo 27 | .DS_Store 28 | .env 29 | .env.local 30 | .env.*.local 31 | 32 | # Logs 33 | logs/ 34 | *.log 35 | 36 | # Test coverage 37 | coverage/ 38 | 39 | # macOS specific 40 | .DS_Store 41 | .AppleDouble 42 | .LSOverride 43 | Icon 44 | ._* 45 | .DocumentRevisions-V100 46 | .fseventsd 47 | .Spotlight-V100 48 | .TemporaryItems 49 | .Trashes 50 | .VolumeIcon.icns 51 | .com.apple.timemachine.donotpresent 52 | 53 | # Xcode 54 | *.xcodeproj/* 55 | !*.xcodeproj/project.pbxproj 56 | !*.xcodeproj/xcshareddata/ 57 | !*.xcworkspace/contents.xcworkspacedata 58 | **/xcshareddata/WorkspaceSettings.xcsettings 59 | *.xcuserstate 60 | project.xcworkspace/ 61 | xcuserdata/ 62 | 63 | # Debug files 64 | .nyc_output/ 65 | *.map 66 | 67 | # Optional npm cache directory 68 | .npm 69 | 70 | # Optional eslint cache 71 | .eslintcache 72 | 73 | # Optional REPL history 74 | .node_repl_history 75 | 76 | # Output of 'npm pack' 77 | *.tgz 78 | 79 | # Yarn Integrity file 80 | .yarn-integrity 81 | 82 | # dotenv environment variable files 83 | .env 84 | .env.test 85 | .env.production 86 | 87 | # parcel-bundler cache 88 | .cache 89 | .parcel-cache 90 | 91 | # Windsurf 92 | .windsurfrules 93 | 94 | # Sentry Config File 95 | .sentryclirc 96 | 97 | # Claude Config File 98 | **/.claude/settings.local.json 99 | 100 | # incremental builds 101 | Makefile 102 | buildServer.json 103 | 104 | # Bundled AXe artifacts (generated during build) 105 | bundled/ 106 | 107 | /.mcpregistry_github_token 108 | /.mcpregistry_registry_token 109 | /key.pem 110 | .mcpli 111 | .factory 112 | ``` -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Test workflow trigger 2 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | <img src="banner.png" alt="XcodeBuild MCP" width="600"/> 2 | 3 | A Model Context Protocol (MCP) server that provides Xcode-related tools for integration with AI assistants and other MCP clients. 4 | 5 | [](https://github.com/cameroncooke/XcodeBuildMCP/actions/workflows/ci.yml) 6 | [](https://badge.fury.io/js/xcodebuildmcp) [](https://opensource.org/licenses/MIT) [](https://nodejs.org/) [](https://developer.apple.com/xcode/) [](https://www.apple.com/macos/) [](https://modelcontextprotocol.io/) [](https://deepwiki.com/cameroncooke/XcodeBuildMCP) 7 | 8 | ## Table of contents 9 | 10 | - [Overview](#overview) 11 | - [Why?](#why) 12 | - [Features](#features) 13 | - [Xcode project management](#xcode-project-management) 14 | - [Swift Package Manager](#swift-package-manager) 15 | - [Simulator management](#simulator-management) 16 | - [Device management](#device-management) 17 | - [App utilities](#app-utilities) 18 | - [MCP Resources](#mcp-resources) 19 | - [Getting started](#getting-started) 20 | - [Prerequisites](#prerequisites) 21 | - [Configure your MCP client](#configure-your-mcp-client) 22 | - [One click install](#one-click-install) 23 | - [General installation](#general-installation) 24 | - [Specific client installation instructions](#specific-client-installation-instructions) 25 | - [OpenAI Codex CLI](#openai-codex-cli) 26 | - [Claude Code CLI](#claude-code-cli) 27 | - [Smithery](#smithery) 28 | - [MCP Compatibility](#mcp-compatibility) 29 | - [Incremental build support](#incremental-build-support) 30 | - [Dynamic Tools](#dynamic-tools) 31 | - [What is Dynamic Tools?](#what-is-dynamic-tools) 32 | - [How to Enable Dynamic Tools](#how-to-enable-dynamic-tools) 33 | - [Usage Example](#usage-example) 34 | - [Client Compatibility](#client-compatibility) 35 | - [Selective Workflow Loading (Static Mode)](#selective-workflow-loading-static-mode) 36 | - [Code Signing for Device Deployment](#code-signing-for-device-deployment) 37 | - [Troubleshooting](#troubleshooting) 38 | - [Doctor Tool](#doctor-tool) 39 | - [Privacy](#privacy) 40 | - [What is sent to Sentry?](#what-is-sent-to-sentry) 41 | - [Opting Out of Sentry](#opting-out-of-sentry) 42 | - [Demos](#demos) 43 | - [Autonomously fixing build errors in Cursor](#autonomously-fixing-build-errors-in-cursor) 44 | - [Utilising the new UI automation and screen capture features](#utilising-the-new-ui-automation-and-screen-capture-features) 45 | - [Building and running iOS app in Claude Desktop](#building-and-running-ios-app-in-claude-desktop) 46 | - [Contributing](#contributing) 47 | - [Licence](#licence) 48 | 49 | ## Overview 50 | 51 | XcodeBuildMCP is a Model Context Protocol (MCP) server that exposes Xcode operations as tools and resources for AI assistants and other MCP clients. Built with a modern plugin architecture, it provides a comprehensive set of self-contained tools organized into workflow-based directories, plus MCP resources for efficient data access, enabling programmatic interaction with Xcode projects, simulators, devices, and Swift packages through a standardized interface. 52 | 53 |  54 | <caption>Using Cursor to build, install, and launch an app on the iOS simulator while capturing logs at run-time.</caption> 55 | 56 | ## Why? 57 | 58 | The XcodeBuild MCP tool exists primarily to streamline and standardise interaction between AI agents and Xcode projects. By providing dedicated tools for common Xcode operations, it removes reliance on manual or potentially incorrect command-line invocations. 59 | 60 | This ensures a reliable and efficient development process, allowing agents to seamlessly leverage Xcode's capabilities while reducing the risk of configuration errors. 61 | 62 | Critically, this MCP enables AI agents to independently validate code changes by building projects, inspecting errors, and iterating autonomously. In contrast to user-driven tools like Sweetpad, XcodeBuild MCP empowers agents to automate these workflows effectively. 63 | 64 | ## Features 65 | 66 | The XcodeBuildMCP server provides the following tool capabilities: 67 | 68 | ### Xcode project management 69 | - **Discover Projects**: Xcode projects and workspaces discovery 70 | - **Build Operations**: Platform-specific build tools for macOS, iOS simulator, and iOS device targets 71 | - **Project Information**: Tools to list schemes and show build settings for Xcode projects and workspaces 72 | - **Clean Operations**: Clean build products using xcodebuild's native clean action 73 | - **Incremental build support**: Lightning fast builds using incremental build support (experimental, opt-in required) 74 | - **Project Scaffolding**: Create new iOS and macOS projects from modern templates with workspace + SPM package architecture, customizable bundle identifiers, deployment targets, and device families 75 | 76 | ### Swift Package Manager 77 | - **Build Packages**: Build Swift packages with configuration and architecture options 78 | - **Run Tests**: Execute Swift package test suites with filtering and parallel execution 79 | - **Run Executables**: Execute package binaries with timeout handling and background execution support 80 | - **Process Management**: List and stop long-running executables started with Swift Package tools 81 | - **Clean Artifacts**: Remove build artifacts and derived data for fresh builds 82 | 83 | ### Simulator management 84 | - **Simulator Control**: List, boot, and open simulators 85 | - **App Lifecycle**: Complete app management - install, launch, and stop apps on simulators 86 | - **Log Capture**: Capture run-time logs from a simulator 87 | - **UI Automation**: Interact with simulator UI elements 88 | - **Screenshot**: Capture screenshots from a simulator 89 | - **Video Capture**: Start/stop simulator video capture to MP4 (AXe v1.1.0+) 90 | 91 | ### Device management 92 | - **Device Discovery**: List connected physical Apple devices over USB or Wi-Fi 93 | - **App Lifecycle**: Complete app management - build, install, launch, and stop apps on physical devices 94 | - **Testing**: Run test suites on physical devices with detailed results and cross-platform support 95 | - **Log Capture**: Capture console output from apps running on physical Apple devices 96 | - **Wireless Connectivity**: Support for devices connected over Wi-Fi networks 97 | 98 | ### App utilities 99 | - **Bundle ID Extraction**: Extract bundle identifiers from app bundles across all Apple platforms 100 | - **App Lifecycle Management**: Complete app lifecycle control across all platforms 101 | - Launch apps on simulators, physical devices, and macOS 102 | - Stop running apps with process ID or bundle ID management 103 | - Process monitoring and control for comprehensive app management 104 | 105 | ### MCP Resources 106 | 107 | For clients that support MCP resources XcodeBuildMCP provides efficient URI-based data access: 108 | 109 | - **Simulators Resource** (`xcodebuildmcp://simulators`): Direct access to available iOS simulators with UUIDs and states 110 | - **Devices Resource** (`xcodebuildmcp://devices`): Direct access to connected physical Apple devices with UDIDs and states 111 | - **Doctor Resource** (`xcodebuildmcp://doctor`): Direct access to environment information such as Xcode version, macOS version, and Node.js version 112 | 113 | ## Getting started 114 | 115 | ### Prerequisites 116 | 117 | - macOS 14.5 or later 118 | - Xcode 16.x or later 119 | - Node 18.x or later 120 | 121 | > Video capture requires the bundled AXe binary (v1.1.0+). Run `npm run bundle:axe` once locally before using `record_sim_video`. This is not required for unit tests. 122 | 123 | Configure your MCP client 124 | 125 | #### One click install 126 | 127 | For a quick install, you can use the following links: 128 | 129 | [](https://cursor.com/en/install-mcp?name=XcodeBuildMCP&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IC15IHhjb2RlYnVpbGRtY3BAbGF0ZXN0IiwiZW52Ijp7IklOQ1JFTUVOVEFMX0JVSUxEU19FTkFCTEVEIjoiZmFsc2UiLCJYQ09ERUJVSUxETUNQX1NFTlRSWV9ESVNBQkxFRCI6ImZhbHNlIn19) 130 | 131 | [<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://insiders.vscode.dev/redirect/mcp/install?name=XcodeBuildMCP&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40latest%22%5D%7D) 132 | 133 | [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect/mcp/install?name=XcodeBuildMCP&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40latest%22%5D%7D&quality=insiders) 134 | 135 | #### General installation 136 | 137 | Most MCP clients (Cursor, VS Code, Windsurf, Claude Desktop etc) have standardised on the following JSON configuration format, just add the the following to your client's JSON configuration's `mcpServers` object: 138 | 139 | ```json 140 | "XcodeBuildMCP": { 141 | "command": "npx", 142 | "args": [ 143 | "-y", 144 | "xcodebuildmcp@latest" 145 | ] 146 | } 147 | ``` 148 | 149 | #### Specific client installation instructions 150 | 151 | ##### OpenAI Codex CLI 152 | 153 | Codex uses a toml configuration file to configure MCP servers. To configure XcodeBuildMCP with [OpenAI's Codex CLI](https://github.com/openai/codex), add the following configuration to your Codex CLI config file: 154 | 155 | ```toml 156 | [mcp_servers.XcodeBuildMCP] 157 | command = "npx" 158 | args = ["-y", "xcodebuildmcp@latest"] 159 | env = { "INCREMENTAL_BUILDS_ENABLED" = "false", "XCODEBUILDMCP_SENTRY_DISABLED" = "false" } 160 | ``` 161 | 162 | For more information see [OpenAI Codex MCP Server Configuration](https://github.com/openai/codex/blob/main/codex-rs/config.md#mcp_servers) documentation. 163 | 164 | ##### Claude Code CLI 165 | 166 | To use XcodeBuildMCP with [Claude Code](https://code.anthropic.com), you can add it via the command line: 167 | 168 | ```bash 169 | # Add XcodeBuildMCP server to Claude Code 170 | claude mcp add XcodeBuildMCP npx xcodebuildmcp@latest 171 | 172 | # Or with environment variables 173 | claude mcp add XcodeBuildMCP npx xcodebuildmcp@latest -e INCREMENTAL_BUILDS_ENABLED=false -e XCODEBUILDMCP_SENTRY_DISABLED=false 174 | ``` 175 | 176 | ##### Smithery 177 | 178 | To install XcodeBuildMCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@cameroncooke/XcodeBuildMCP): 179 | 180 | ```bash 181 | npx -y @smithery/cli install @cameroncooke/XcodeBuildMCP --client claude 182 | ``` 183 | 184 | > [!IMPORTANT] 185 | > Please note that XcodeBuildMCP will request xcodebuild to skip macro validation. This is to avoid errors when building projects that use Swift Macros. 186 | 187 | #### MCP Compatibility 188 | 189 | XcodeBuildMCP supports both MCP tools, resources and sampling. At time of writing the following editors have varying levels of MCP feature support: 190 | 191 | | Editor | Tools | Resources | Samplng | 192 | |--------|-------|-----------|---------| 193 | | **VS Code** | ✅ | ✅ | ✅ | 194 | | **Cursor** | ✅ | ❌ | ❌ | 195 | | **Windsurf** | ✅ | ❌ | ❌ | 196 | | **Claude Code** | ✅ | ✅ | ❌ | 197 | | **Claude Desktop** | ✅ | ✅ | ❌ | 198 | 199 | ## Incremental build support 200 | 201 | XcodeBuildMCP includes experimental support for incremental builds. This feature is disabled by default and can be enabled by setting the `INCREMENTAL_BUILDS_ENABLED` environment variable to `true`: 202 | 203 | To enable incremental builds, set the `INCREMENTAL_BUILDS_ENABLED` environment variable to `true`: 204 | 205 | Example MCP configuration: 206 | ```json 207 | "XcodeBuildMCP": { 208 | ... 209 | "env": { 210 | "INCREMENTAL_BUILDS_ENABLED": "true" 211 | } 212 | } 213 | ``` 214 | 215 | > [!IMPORTANT] 216 | > Please note that incremental builds support is currently highly experimental and your mileage may vary. Please report any issues you encounter to the [issue tracker](https://github.com/cameroncooke/XcodeBuildMCP/issues). 217 | 218 | ## Dynamic Tools 219 | 220 | XcodeBuildMCP supports dynamic tool loading to optimize context window usage in AI assistants. This feature is particularly useful for managing the extensive toolset that XcodeBuildMCP provides. 221 | 222 | ### What is Dynamic Tools? 223 | 224 | By default, XcodeBuildMCP loads all available tools at startup (Static Mode), which provides immediate access to the complete toolset but uses a larger context window. Dynamic Tools mode solves this by: 225 | 226 | 1. **Starting minimal**: Only essential tools like `discover_tools` and `discover_projs` are available initially 227 | 2. **AI-powered discovery**: When an AI agent identifies XcodeBuildMCP can help with development tasks, it automatically uses the `discover_tools` tool 228 | 3. **Intelligent loading**: The server uses an LLM call to identify the most relevant workflow group and dynamically loads only those tools 229 | 4. **Context efficiency**: Reduces the initial context footprint from the entire list of tools to just 2 discovery tools while maintaining full functionality 230 | 231 | ### How to Enable Dynamic Tools 232 | 233 | To enable dynamic tools, set the `XCODEBUILDMCP_DYNAMIC_TOOLS` environment variable to `true`: 234 | 235 | Example MCP client configuration: 236 | ```json 237 | "XcodeBuildMCP": { 238 | ... 239 | "env": { 240 | "XCODEBUILDMCP_DYNAMIC_TOOLS": "true" 241 | } 242 | } 243 | ``` 244 | 245 | ### Usage Example 246 | 247 | Once enabled, AI agents automatically discover and load relevant tools based on context. For example, when you mention working on an iOS app or the agent detects iOS development tasks in your workspace, it will automatically use the `discover_tools` tool to load the appropriate simulator and project tools needed for your workflow. 248 | 249 | ### Client Compatibility 250 | 251 | Dynamic Tools requires MCP clients that support **MCP Sampling** for the AI-powered tool discovery to function: 252 | 253 | | Editor | Dynamic Tools Support | 254 | |--------|----------------------| 255 | | **VS Code** | ✅ | 256 | | **Cursor** | ❌ (No MCP Sampling) | 257 | | **Windsurf** | ❌ (No MCP Sampling) | 258 | | **Claude Code** | ❌ (No MCP Sampling) | 259 | | **Claude Desktop** | ❌ (No MCP Sampling) | 260 | 261 | > [!NOTE] 262 | > For clients that don't support MCP Sampling, XcodeBuildMCP will automatically fall back to Static Mode, loading all tools at startup regardless of the `XCODEBUILDMCP_DYNAMIC_TOOLS` setting. 263 | 264 | ### Selective Workflow Loading (Static Mode) 265 | 266 | For clients that don't support MCP Sampling but still want to reduce context window usage, you can selectively load only specific workflows using the `XCODEBUILDMCP_ENABLED_WORKFLOWS` environment variable: 267 | 268 | ```json 269 | "XcodeBuildMCP": { 270 | ... 271 | "env": { 272 | "XCODEBUILDMCP_ENABLED_WORKFLOWS": "simulator,device,project-discovery" 273 | } 274 | } 275 | ``` 276 | 277 | **Available Workflows:** 278 | - `device` (14 tools) - iOS Device Development 279 | - `simulator` (18 tools) - iOS Simulator Development 280 | - `simulator-management` (8 tools) - Simulator Management 281 | - `swift-package` (6 tools) - Swift Package Manager 282 | - `project-discovery` (5 tools) - Project Discovery 283 | - `macos` (11 tools) - macOS Development 284 | - `ui-testing` (11 tools) - UI Testing & Automation 285 | - `logging` (4 tools) - Log Capture & Management 286 | - `project-scaffolding` (2 tools) - Project Scaffolding 287 | - `utilities` (1 tool) - Project Utilities 288 | - `doctor` (1 tool) - System Doctor 289 | - `discovery` (1 tool) - Dynamic Tool Discovery 290 | 291 | > [!NOTE] 292 | > The `XCODEBUILDMCP_ENABLED_WORKFLOWS` setting only works in Static Mode. If `XCODEBUILDMCP_DYNAMIC_TOOLS=true` is set, the selective workflow setting will be ignored. 293 | 294 | ## Code Signing for Device Deployment 295 | 296 | For device deployment features to work, code signing must be properly configured in Xcode **before** using XcodeBuildMCP device tools: 297 | 298 | 1. Open your project in Xcode 299 | 2. Select your project target 300 | 3. Go to "Signing & Capabilities" tab 301 | 4. Configure "Automatically manage signing" and select your development team 302 | 5. Ensure a valid provisioning profile is selected 303 | 304 | > **Note**: XcodeBuildMCP cannot configure code signing automatically. This initial setup must be done once in Xcode, after which the MCP device tools can build, install, and test apps on physical devices. 305 | 306 | ## Troubleshooting 307 | 308 | If you encounter issues with XcodeBuildMCP, the doctor tool can help identify the problem by providing detailed information about your environment and dependencies. 309 | 310 | ### Doctor Tool 311 | 312 | The doctor tool is a standalone utility that checks your system configuration and reports on the status of all dependencies required by XcodeBuildMCP. It's particularly useful when reporting issues. 313 | 314 | ```bash 315 | # Run the doctor tool using npx 316 | npx --package xcodebuildmcp@latest xcodebuildmcp-doctor 317 | ``` 318 | 319 | The doctor tool will output comprehensive information about: 320 | 321 | - System and Node.js environment 322 | - Xcode installation and configuration 323 | - Required dependencies (xcodebuild, AXe, etc.) 324 | - Environment variables affecting XcodeBuildMCP 325 | - Feature availability status 326 | 327 | When reporting issues on GitHub, please include the full output from the doctor tool to help with troubleshooting. 328 | 329 | ## Privacy 330 | 331 | This project uses [Sentry](https://sentry.io/) for error monitoring and diagnostics. Sentry helps us track issues, crashes, and unexpected errors to improve the reliability and stability of XcodeBuildMCP. 332 | 333 | ### What is sent to Sentry? 334 | - Only error-level logs and diagnostic information are sent to Sentry by default. 335 | - Error logs may include details such as error messages, stack traces, and (in some cases) file paths or project names. You can review the sources in this repository to see exactly what is logged. 336 | 337 | ### Opting Out of Sentry 338 | - If you do not wish to send error logs to Sentry, you can opt out by setting the environment variable `XCODEBUILDMCP_SENTRY_DISABLED=true`. 339 | 340 | Example MCP client configuration: 341 | ```json 342 | "XcodeBuildMCP": { 343 | ... 344 | "env": { 345 | "XCODEBUILDMCP_SENTRY_DISABLED": "true" 346 | } 347 | } 348 | ``` 349 | 350 | ## Demos 351 | 352 | ### Autonomously fixing build errors in Cursor 353 |  354 | 355 | ### Utilising the new UI automation and screen capture features 356 | 357 |  358 | 359 | ### Building and running iOS app in Claude Desktop 360 | https://github.com/user-attachments/assets/e3c08d75-8be6-4857-b4d0-9350b26ef086 361 | 362 | ## Contributing 363 | 364 | [](https://www.typescriptlang.org/) [](https://nodejs.org/) 365 | 366 | Contributions are welcome! Here's how you can help improve XcodeBuildMCP. 367 | 368 | See our documentation for development: 369 | - [CONTRIBUTING](docs/CONTRIBUTING.md) - Contribution guidelines and development setup 370 | - [CODE_QUALITY](docs/CODE_QUALITY.md) - Code quality standards, linting, and architectural rules 371 | - [TESTING](docs/TESTING.md) - Testing principles and patterns 372 | - [ARCHITECTURE](docs/ARCHITECTURE.md) - System architecture and design principles 373 | 374 | ## Licence 375 | 376 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 377 | ``` -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- ```markdown 1 | AGENTS.md ``` -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- ```markdown 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [email protected]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | ``` -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- ```markdown 1 | This file provides guidance to AI assisants (Claude Code, Cursor etc) when working with code in this repository. 2 | 3 | ## Project Overview 4 | 5 | XcodeBuildMCP is a Model Context Protocol (MCP) server providing standardized tools for AI assistants to interact with Xcode projects, iOS simulators, devices, and Apple development workflows. It's a TypeScript/Node.js project that runs as a stdio-based MCP server. 6 | 7 | ## Common Commands 8 | 9 | ### Build & Development 10 | ```bash 11 | npm run build # Compile TypeScript with tsup, generates version info 12 | npm run dev # Watch mode development 13 | npm run bundle:axe # Bundle axe CLI tool for simulator automation (needed when using local MCP server) 14 | npm run test # Run complete Vitest test suite 15 | npm run test:watch # Watch mode testing 16 | npm run lint # ESLint code checking 17 | npm run lint:fix # ESLint code checking and fixing 18 | npm run format:check # Prettier code checking 19 | npm run format # Prettier code formatting 20 | npm run typecheck # TypeScript type checking 21 | npm run inspect # Run interactive MCP protocol inspector 22 | npm run doctor # Doctor CLI 23 | ``` 24 | 25 | ### Development with Reloaderoo 26 | 27 | **Reloaderoo** (v1.1.2+) provides CLI-based testing and hot-reload capabilities for XcodeBuildMCP without requiring MCP client configuration. 28 | 29 | #### Quick Start 30 | 31 | **CLI Mode (Testing & Development):** 32 | ```bash 33 | # List all tools 34 | npx reloaderoo inspect list-tools -- node build/index.js 35 | 36 | # Call any tool 37 | npx reloaderoo inspect call-tool list_devices --params '{}' -- node build/index.js 38 | 39 | # Get server information 40 | npx reloaderoo inspect server-info -- node build/index.js 41 | 42 | # List and read resources 43 | npx reloaderoo inspect list-resources -- node build/index.js 44 | npx reloaderoo inspect read-resource "xcodebuildmcp://devices" -- node build/index.js 45 | ``` 46 | 47 | **Proxy Mode (MCP Client Integration):** 48 | ```bash 49 | # Start persistent server for MCP clients 50 | npx reloaderoo proxy -- node build/index.js 51 | 52 | # With debug logging 53 | npx reloaderoo proxy --log-level debug -- node build/index.js 54 | 55 | # Then ask AI: "Please restart the MCP server to load my changes" 56 | ``` 57 | 58 | #### All CLI Inspect Commands 59 | 60 | Reloaderoo provides 8 inspect subcommands for comprehensive MCP server testing: 61 | 62 | ```bash 63 | # Server capabilities and information 64 | npx reloaderoo inspect server-info -- node build/index.js 65 | 66 | # Tool management 67 | npx reloaderoo inspect list-tools -- node build/index.js 68 | npx reloaderoo inspect call-tool <tool_name> --params '<json>' -- node build/index.js 69 | 70 | # Resource access 71 | npx reloaderoo inspect list-resources -- node build/index.js 72 | npx reloaderoo inspect read-resource "<uri>" -- node build/index.js 73 | 74 | # Prompt management 75 | npx reloaderoo inspect list-prompts -- node build/index.js 76 | npx reloaderoo inspect get-prompt <name> --args '<json>' -- node build/index.js 77 | 78 | # Connectivity testing 79 | npx reloaderoo inspect ping -- node build/index.js 80 | ``` 81 | 82 | #### Advanced Options 83 | 84 | ```bash 85 | # Custom working directory 86 | npx reloaderoo inspect list-tools --working-dir /custom/path -- node build/index.js 87 | 88 | # Timeout configuration 89 | npx reloaderoo inspect call-tool slow_tool --timeout 60000 --params '{}' -- node build/index.js 90 | 91 | # Use timeout configuration if needed 92 | npx reloaderoo inspect server-info --timeout 60000 -- node build/index.js 93 | 94 | # Debug logging (use proxy mode for detailed logging) 95 | npx reloaderoo proxy --log-level debug -- node build/index.js 96 | ``` 97 | 98 | #### Key Benefits 99 | 100 | - ✅ **No MCP Client Setup**: Direct CLI access to all 84+ tools 101 | - ✅ **Raw JSON Output**: Perfect for AI agents and programmatic use 102 | - ✅ **Hot-Reload Support**: `restart_server` tool for MCP client development 103 | - ✅ **Claude Code Compatible**: Automatic content block consolidation 104 | - ✅ **8 Inspect Commands**: Complete MCP protocol testing capabilities 105 | - ✅ **Universal Compatibility**: Works on any system via npx 106 | 107 | For complete documentation, examples, and troubleshooting, see @docs/RELOADEROO.md 108 | 109 | ## Architecture Overview 110 | 111 | ### Plugin-Based MCP architecture 112 | 113 | XcodeBuildMCP uses the concept of configuration by convention for MCP exposing and running MCP capabilities like tools and resources. This means to add a new tool or resource, you simply create a new file in the appropriate directory and it will be automatically loaded and exposed to MCP clients. 114 | 115 | #### Tools 116 | 117 | Tools are the core of the MCP server and are the primary way to interact with the server. They are organized into directories by their functionality and are automatically loaded and exposed to MCP clients. 118 | 119 | For more information see @docs/PLUGIN_DEVELOPMENT.md 120 | 121 | #### Resources 122 | 123 | Resources are the secondary way to interact with the server. They are used to provide data to tools and are organized into directories by their functionality and are automatically loaded and exposed to MCP clients. 124 | 125 | For more information see @docs/PLUGIN_DEVELOPMENT.md 126 | 127 | ### Operating Modes 128 | 129 | XcodeBuildMCP has two modes to manage its extensive toolset, controlled by the `XCODEBUILDMCP_DYNAMIC_TOOLS` environment variable. 130 | 131 | #### Static Mode (Default) 132 | - **Environment**: `XCODEBUILDMCP_DYNAMIC_TOOLS=false` or unset. 133 | - **Behavior**: All tools are loaded at startup. This provides immediate access to the full toolset but uses a larger context window. 134 | 135 | #### Dynamic Mode (AI-Powered) 136 | - **Environment**: `XCODEBUILDMCP_DYNAMIC_TOOLS=true`. 137 | - **Behavior**: Only the `discover_tools` tool is available initially. You can use this tool by providing a natural language task description. The server then uses an LLM call (via MCP Sampling) to identify the most relevant workflow group and dynamically loads only those tools. This conserves context window space. 138 | 139 | #### Claude Code Compatibility Workaround 140 | - **Detection**: Automatic detection when running under Claude Code. 141 | - **Purpose**: Workaround for Claude Code's MCP specification violation where it only displays the first content block in tool responses. 142 | - **Behavior**: When Claude Code is detected, multiple content blocks are automatically consolidated into a single text response, separated by `---` dividers. This ensures all information (including test results and stderr warnings) is visible to Claude Code users. 143 | 144 | ### Core Architecture Layers 145 | 1. **MCP Transport**: stdio protocol communication 146 | 2. **Plugin Discovery**: Automatic tool AND resource registration system 147 | 3. **MCP Resources**: URI-based data access (e.g., `xcodebuildmcp://simulators`) 148 | 4. **Tool Implementation**: Self-contained workflow modules 149 | 5. **Shared Utilities**: Command execution, build management, validation 150 | 6. **Types**: Shared interfaces and Zod schemas 151 | 152 | For more information see @docs/ARCHITECTURE.md 153 | 154 | ## Testing 155 | 156 | The project enforces a strict **Dependency Injection (DI)** testing philosophy. 157 | 158 | - **NO Vitest Mocking**: The use of `vi.mock()`, `vi.fn()`, `vi.spyOn()`, etc., is **completely banned**. 159 | - **Executors**: All external interactions (like running commands or accessing the file system) are handled through injectable "executors". 160 | - `CommandExecutor`: For running shell commands. 161 | - `FileSystemExecutor`: For file system operations. 162 | - **Testing Logic**: Tests import the core `...Logic` function from a tool file and pass in a mock executor (`createMockExecutor` or `createMockFileSystemExecutor`) to simulate different outcomes. 163 | 164 | This approach ensures that tests are robust, easy to maintain, and verify the actual integration between components without being tightly coupled to implementation details. 165 | 166 | For complete guidelines, refer to @docs/TESTING.md. 167 | 168 | ## TypeScript Import Standards 169 | 170 | This project uses **TypeScript file extensions** (`.ts`) for all relative imports to ensure compatibility with native TypeScript runtimes. 171 | 172 | ### Import Rules 173 | 174 | - ✅ **Use `.ts` extensions**: `import { tool } from './tool.ts'` 175 | - ✅ **Use `.ts` for re-exports**: `export { default } from '../shared/tool.ts'` 176 | - ✅ **External packages use `.js`**: `import { McpServer } from '@camsoft/mcp-sdk/server/mcp.js'` 177 | - ❌ **Never use `.js` for internal files**: `import { tool } from './tool.js'` ← ESLint error 178 | 179 | ### Benefits 180 | 181 | 1. **Future-proof**: Compatible with native TypeScript runtimes (Bun, Deno, Node.js --loader) 182 | 2. **IDE Experience**: Direct navigation to source TypeScript files 183 | 3. **Consistency**: Import path matches the actual file you're editing 184 | 4. **Modern Standard**: Aligns with TypeScript 4.7+ `allowImportingTsExtensions` 185 | 186 | ### ESLint Enforcement 187 | 188 | The project automatically enforces this standard: 189 | 190 | ```bash 191 | npm run lint # Will catch .js imports for internal files 192 | ``` 193 | 194 | This ensures all new code follows the `.ts` import pattern and maintains compatibility with both current and future TypeScript execution environments. 195 | 196 | ## Release Process 197 | 198 | Follow standardized development workflow with feature branches, structured pull requests, and linear commit history. **Never push to main directly or force push without permission.** 199 | 200 | For complete guidelines, refer to @docs/RELEASE_PROCESS.md 201 | 202 | ## Useful external resources 203 | 204 | ### Model Context Protocol 205 | 206 | https://modelcontextprotocol.io/llms-full.txt 207 | 208 | ### MCP Specification 209 | 210 | https://modelcontextprotocol.io/specification 211 | 212 | ### MCP Inspector 213 | 214 | https://github.com/modelcontextprotocol/inspector 215 | 216 | ### MCP Client SDKs 217 | 218 | https://github.com/modelcontextprotocol/typescript-sdk ``` -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- ```markdown 1 | # Contributing 2 | 3 | Contributions are welcome! Here's how you can help improve XcodeBuildMCP. 4 | 5 | ## Local development setup 6 | 7 | ### Prerequisites 8 | 9 | In addition to the prerequisites mentioned in the [Getting started](README.md/#getting-started) section of the README, you will also need: 10 | 11 | - Node.js (v18 or later) 12 | - npm 13 | 14 | #### Optional: Enabling UI Automation 15 | 16 | When running locally, you'll need to install AXe for UI automation: 17 | 18 | ```bash 19 | # Install axe (required for UI automation) 20 | brew tap cameroncooke/axe 21 | brew install axe 22 | ``` 23 | 24 | ### Installation 25 | 26 | 1. Clone the repository 27 | 2. Install dependencies: 28 | ``` 29 | npm install 30 | ``` 31 | 3. Build the project: 32 | ``` 33 | npm run build 34 | ``` 35 | 4. Start the server: 36 | ``` 37 | node build/index.js 38 | ``` 39 | 40 | ### Configure your MCP client 41 | 42 | Most MCP clients (Cursor, VS Code, Windsurf, Claude Desktop etc) have standardised on the following JSON configuration format, just add the the following to your client's JSON configuration's `mcpServers` object: 43 | 44 | ```json 45 | { 46 | "mcpServers": { 47 | "XcodeBuildMCP": { 48 | "command": "node", 49 | "args": [ 50 | "/path_to/XcodeBuildMCP/build/index.js" 51 | ] 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ### Developing using VS Code 58 | 59 | VS Code is especially good for developing XcodeBuildMCP as it has a built-in way to view MCP client/server logs as well as the ability to configure MCP servers at a project level. It probably has the most comprehensive support for MCP development. 60 | 61 | To make your development workflow in VS Code more efficient: 62 | 63 | 1. **Start the MCP Server**: Open the `.vscode/mcp.json` file. You can start the `xcodebuildmcp-dev` server either by clicking the `Start` CodeLens that appears above the server definition, or by opening the Command Palette (`Cmd+Shift+P` or `Ctrl+Shift+P`), running `Mcp: List Servers`, selecting `xcodebuildmcp-dev`, and starting the server. 64 | 2. **Launch the Debugger**: Press `F5` to attach the Node.js debugger. 65 | 66 | Once these steps are completed, you can utilize the tools from the MCP server you are developing within this repository in agent mode. 67 | For more details on how to work with MCP servers in VS Code see: https://code.visualstudio.com/docs/copilot/chat/mcp-servers 68 | 69 | ### Debugging 70 | 71 | #### MCP Inspector (Basic Debugging) 72 | 73 | You can use MCP Inspector for basic debugging via: 74 | 75 | ```bash 76 | npm run inspect 77 | ``` 78 | 79 | or if you prefer the explicit command: 80 | 81 | ```bash 82 | npx @modelcontextprotocol/inspector node build/index.js 83 | ``` 84 | 85 | #### Reloaderoo (Advanced Debugging) - **RECOMMENDED** 86 | 87 | For development and debugging, we strongly recommend using **Reloaderoo**, which provides hot-reloading capabilities and advanced debugging features for MCP servers. 88 | 89 | Reloaderoo operates in two modes: 90 | 91 | ##### 1. Proxy Mode (Hot-Reloading) 92 | Provides transparent hot-reloading without disconnecting your MCP client: 93 | 94 | ```bash 95 | # Install reloaderoo globally 96 | npm install -g reloaderoo 97 | 98 | # Start XcodeBuildMCP through reloaderoo proxy 99 | reloaderoo -- node build/index.js 100 | ``` 101 | 102 | **Benefits**: 103 | - 🔄 Hot-reload server without restarting client 104 | - 🛠️ Automatic `restart_server` tool added to toolset 105 | - 🌊 Transparent MCP protocol forwarding 106 | - 📡 Full protocol support (tools, resources, prompts) 107 | 108 | **MCP Client Configuration for Proxy Mode**: 109 | ```json 110 | "XcodeBuildMCP": { 111 | "command": "reloaderoo", 112 | "args": ["--", "node", "/path/to/XcodeBuildMCP/build/index.js"], 113 | "env": { 114 | "XCODEBUILDMCP_DYNAMIC_TOOLS": "true", 115 | "XCODEBUILDMCP_DEBUG": "true" 116 | } 117 | } 118 | ``` 119 | 120 | ##### 2. Inspection Mode (Raw MCP Debugging) 121 | Exposes debug tools for making raw MCP protocol calls and inspecting server responses: 122 | 123 | ```bash 124 | # Start reloaderoo in inspection mode 125 | reloaderoo inspect mcp -- node build/index.js 126 | ``` 127 | 128 | **Available Debug Tools**: 129 | - `list_tools` - List all server tools 130 | - `call_tool` - Execute any server tool with parameters 131 | - `list_resources` - List all server resources 132 | - `read_resource` - Read any server resource 133 | - `list_prompts` - List all server prompts 134 | - `get_prompt` - Get any server prompt 135 | - `get_server_info` - Get comprehensive server information 136 | - `ping` - Test server connectivity 137 | 138 | **MCP Client Configuration for Inspection Mode**: 139 | ```json 140 | "XcodeBuildMCP": { 141 | "command": "node", 142 | "args": [ 143 | "/path/to/reloaderoo/dist/bin/reloaderoo.js", 144 | "inspect", "mcp", 145 | "--working-dir", "/path/to/XcodeBuildMCP", 146 | "--", 147 | "node", "/path/to/XcodeBuildMCP/build/index.js" 148 | ], 149 | "env": { 150 | "XCODEBUILDMCP_DYNAMIC_TOOLS": "true", 151 | "XCODEBUILDMCP_DEBUG": "true" 152 | } 153 | } 154 | ``` 155 | 156 | #### Operating Mode Testing 157 | 158 | Test both static and dynamic modes during development: 159 | 160 | ```bash 161 | # Test static mode (all tools loaded immediately) 162 | XCODEBUILDMCP_DYNAMIC_TOOLS=false reloaderoo inspect mcp -- node build/index.js 163 | 164 | # Test dynamic mode (only discover_tools initially available) 165 | XCODEBUILDMCP_DYNAMIC_TOOLS=true reloaderoo inspect mcp -- node build/index.js 166 | ``` 167 | 168 | **Key Differences to Test**: 169 | - **Static Mode**: 50+ tools available immediately via `list_tools` 170 | - **Dynamic Mode**: Only 2 tools (`discover_tools` and `discover_projs`) available initially 171 | - **Dynamic Discovery**: After calling `discover_tools`, additional workflow tools become available 172 | 173 | #### Using XcodeBuildMCP doctor tool 174 | 175 | Running the XcodeBuildMCP server with the environmental variable `XCODEBUILDMCP_DEBUG=true` will expose a new doctor MCP tool called `doctor` which your agent can call to get information about the server's environment, available tools, and configuration status. 176 | 177 | > [!NOTE] 178 | > You can also call the doctor tool directly using the following command but be advised that the output may vary from that of the MCP tool call due to environmental differences: 179 | > ```bash 180 | > npm run doctor 181 | > ``` 182 | 183 | #### Development Workflow with Reloaderoo 184 | 185 | 1. **Start Development Session**: 186 | ```bash 187 | # Terminal 1: Start in hot-reload mode 188 | reloaderoo -- node build/index.js 189 | 190 | # Terminal 2: Start build watcher 191 | npm run build:watch 192 | ``` 193 | 194 | 2. **Make Changes**: Edit source code in `src/` 195 | 196 | 3. **Test Changes**: Ask your AI client to restart the server: 197 | ``` 198 | "Please restart the MCP server to load my changes" 199 | ``` 200 | The AI will automatically call the `restart_server` tool provided by reloaderoo. 201 | 202 | 4. **Verify Changes**: New functionality immediately available without reconnecting client 203 | 204 | ## Architecture and Code Standards 205 | 206 | Before making changes, please familiarize yourself with: 207 | - [ARCHITECTURE.md](ARCHITECTURE.md) - Comprehensive architectural overview 208 | - [CLAUDE.md](CLAUDE.md) - AI assistant guidelines and testing principles 209 | - [TOOLS.md](TOOLS.md) - Complete tool documentation 210 | - [TOOL_OPTIONS.md](TOOL_OPTIONS.md) - Tool configuration options 211 | 212 | ### Code Quality Requirements 213 | 214 | 1. **Follow existing code patterns and structure** 215 | 2. **Use TypeScript strictly** - no `any` types, proper typing throughout 216 | 3. **Add proper error handling and logging** - all failures must set `isError: true` 217 | 4. **Update documentation for new features** 218 | 5. **Test with example projects before submitting** 219 | 220 | ### Testing Standards 221 | 222 | All contributions must adhere to the testing standards outlined in the [**XcodeBuildMCP Plugin Testing Guidelines (docs/TESTING.md)**](docs/TESTING.md). This is the canonical source of truth for all testing practices. 223 | 224 | **Key Principles (Summary):** 225 | - **No Vitest Mocking**: All forms of `vi.mock`, `vi.fn`, `vi.spyOn`, etc., are strictly forbidden. 226 | - **Dependency Injection**: All external dependencies (command execution, file system access) must be injected into tool logic functions using the `CommandExecutor` and `FileSystemExecutor` patterns. 227 | - **Test Production Code**: Tests must import and execute the actual tool logic, not mock implementations. 228 | - **Comprehensive Coverage**: Tests must cover input validation, command generation, and output processing. 229 | 230 | Please read [docs/TESTING.md](docs/TESTING.md) in its entirety before writing tests. 231 | 232 | ### Pre-Commit Checklist 233 | 234 | **MANDATORY**: Run these commands before any commit and ensure they all pass: 235 | 236 | ```bash 237 | # 1. Run linting (must pass with 0 errors) 238 | npm run lint 239 | 240 | # 2. Run formatting (must format all files) 241 | npm run format 242 | 243 | # 3. Run build (must compile successfully) 244 | npm run build 245 | 246 | # 4. Run tests (all tests must pass) 247 | npm test 248 | ``` 249 | 250 | **NO EXCEPTIONS**: Code that fails any of these commands cannot be committed. 251 | 252 | ## Making changes 253 | 254 | 1. Fork the repository and create a new branch 255 | 2. Follow the TypeScript best practices and existing code style 256 | 3. Add proper parameter validation and error handling 257 | 258 | ## Plugin Development 259 | 260 | For comprehensive instructions on creating new tools and workflow groups, see our dedicated [Plugin Development Guide](docs/PLUGIN_DEVELOPMENT.md). 261 | 262 | The plugin development guide covers: 263 | - Auto-discovery system architecture 264 | - Tool creation with dependency injection patterns 265 | - Workflow group organization 266 | - Testing guidelines and patterns 267 | - Integration with dynamic tool discovery 268 | 269 | ### Quick Plugin Development Checklist 270 | 271 | 1. Choose appropriate workflow directory in `src/mcp/tools/` 272 | 2. Follow naming conventions: `{action}_{target}_{specifier}_{projectType}` 273 | 3. Use dependency injection pattern with separate logic functions 274 | 4. Create comprehensive tests using `createMockExecutor()` 275 | 5. Add workflow metadata if creating new workflow group 276 | 277 | See [PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for complete details. 278 | 279 | ### Working with Project Templates 280 | 281 | XcodeBuildMCP uses external template repositories for the iOS and macOS project scaffolding features. These templates are maintained separately to allow independent versioning and updates. 282 | 283 | #### Template Repositories 284 | 285 | - **iOS Template**: [XcodeBuildMCP-iOS-Template](https://github.com/cameroncooke/XcodeBuildMCP-iOS-Template) 286 | - **macOS Template**: [XcodeBuildMCP-macOS-Template](https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template) 287 | 288 | #### Local Template Development 289 | 290 | When developing or testing changes to the templates: 291 | 292 | 1. Clone the template repository you want to work on: 293 | ```bash 294 | git clone https://github.com/cameroncooke/XcodeBuildMCP-iOS-Template.git 295 | git clone https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template.git 296 | ``` 297 | 298 | 2. Set the appropriate environment variable to use your local template: 299 | ```bash 300 | # For iOS template development 301 | export XCODEBUILDMCP_IOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-iOS-Template 302 | 303 | # For macOS template development 304 | export XCODEBUILDMCP_MACOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-macOS-Template 305 | ``` 306 | 307 | 3. When using MCP clients, add these environment variables to your MCP configuration: 308 | ```json 309 | "XcodeBuildMCP": { 310 | "command": "node", 311 | "args": ["/path_to/XcodeBuildMCP/build/index.js"], 312 | "env": { 313 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-iOS-Template", 314 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-macOS-Template" 315 | } 316 | } 317 | ``` 318 | 319 | 4. The scaffold tools will use your local templates instead of downloading from GitHub releases. 320 | 321 | #### Template Versioning 322 | 323 | - Templates are versioned independently from XcodeBuildMCP 324 | - The default template version is specified in `package.json` under `templateVersion` 325 | - You can override the template version with `XCODEBUILD_MCP_TEMPLATE_VERSION` environment variable 326 | - To update the default template version: 327 | 1. Update `templateVersion` in `package.json` 328 | 2. Run `npm run build` to regenerate version.ts 329 | 3. Create a new XcodeBuildMCP release 330 | 331 | #### Testing Template Changes 332 | 333 | 1. Make changes to your local template 334 | 2. Test scaffolding with your changes using the local override 335 | 3. Verify the scaffolded project builds and runs correctly 336 | 4. Once satisfied, create a PR in the template repository 337 | 5. After merging, create a new release in the template repository using the release script 338 | 339 | ## Testing 340 | 341 | 1. Build the project with `npm run build` 342 | 2. Test your changes with MCP Inspector 343 | 3. Verify tools work correctly with different MCP clients 344 | 345 | ## Submitting 346 | 347 | 1. Run `npm run lint` to check for linting issues (use `npm run lint:fix` to auto-fix) 348 | 2. Run `npm run format:check` to verify formatting (use `npm run format` to fix) 349 | 3. Update documentation if you've added or modified features 350 | 4. Add your changes to the CHANGELOG.md file 351 | 5. Push your changes and create a pull request with a clear description 352 | 6. Link any related issues 353 | 354 | For major changes or new features, please open an issue first to discuss your proposed changes. 355 | 356 | ## Code of Conduct 357 | 358 | Please follow our [Code of Conduct](CODE_OF_CONDUCT.md) and community guidelines. 359 | ``` -------------------------------------------------------------------------------- /src/utils/capabilities.ts: -------------------------------------------------------------------------------- ```typescript 1 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/.vscode/settings.json: -------------------------------------------------------------------------------- ```json 1 | {} ``` -------------------------------------------------------------------------------- /example_projects/spm/Sources/spm/main.swift: -------------------------------------------------------------------------------- ```swift 1 | print("Hello, world!") 2 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- ```yaml 1 | blank_issues_enabled: false ``` -------------------------------------------------------------------------------- /.cursor/environment.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "agentCanUpdateSnapshot": true 3 | } ``` -------------------------------------------------------------------------------- /src/utils/version/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { version } from '../../version.ts'; 2 | ``` -------------------------------------------------------------------------------- /src/utils/test/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { handleTestLogic } from '../test-common.ts'; 2 | ``` -------------------------------------------------------------------------------- /src/utils/template/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { TemplateManager } from '../template-manager.ts'; 2 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTest/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | ``` -------------------------------------------------------------------------------- /example_projects/macOS/MCPTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | ``` -------------------------------------------------------------------------------- /example_projects/macOS/MCPTest/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | ``` -------------------------------------------------------------------------------- /src/utils/log-capture/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { startLogCapture, stopLogCapture } from '../log_capture.ts'; 2 | ``` -------------------------------------------------------------------------------- /src/utils/xcodemake/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { isXcodemakeEnabled, isXcodemakeAvailable, doesMakefileExist } from '../xcodemake.ts'; 2 | ``` -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- ```yaml 1 | # These are supported funding model platforms 2 | github: cameroncooke 3 | buy_me_a_coffee: cameroncooke 4 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator-management/boot_sim.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from simulator to avoid duplication 2 | export { default } from '../simulator/boot_sim.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator-management/open_sim.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from simulator to avoid duplication 2 | export { default } from '../simulator/open_sim.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator-management/list_sims.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from simulator to avoid duplication 2 | export { default } from '../simulator/list_sims.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/screenshot.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from ui-testing to avoid duplication 2 | export { default } from '../ui-testing/screenshot.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/describe_ui.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from ui-testing to avoid duplication 2 | export { default } from '../ui-testing/describe_ui.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/stop_device_log_cap.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from logging to complete workflow 2 | export { default } from '../logging/stop_device_log_cap.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/macos/clean.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified clean tool for macos-project workflow 2 | export { default } from '../utilities/clean.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/clean.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified clean tool for device-project workflow 2 | export { default } from '../utilities/clean.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/start_device_log_cap.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from logging to complete workflow 2 | export { default } from '../logging/start_device_log_cap.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/clean.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified clean tool for simulator-project workflow 2 | export { default } from '../utilities/clean.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/utils/video-capture/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { 2 | startSimulatorVideoCapture, 3 | stopSimulatorVideoCapture, 4 | type AxeHelpers, 5 | } from '../video_capture.ts'; 6 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTest/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | ``` -------------------------------------------------------------------------------- /example_projects/macOS/MCPTest/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/discover_projs.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from project-discovery to complete workflow 2 | export { default } from '../project-discovery/discover_projs.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/macos/discover_projs.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from project-discovery to complete workflow 2 | export { default } from '../project-discovery/discover_projs.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/discover_projs.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from project-discovery to complete workflow 2 | export { default } from '../project-discovery/discover_projs.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/macos/show_build_settings.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified tool for macos-project workflow 2 | export { default } from '../project-discovery/show_build_settings.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/show_build_settings.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified tool for device-project workflow 2 | export { default } from '../project-discovery/show_build_settings.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/get_app_bundle_id.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from project-discovery to complete workflow 2 | export { default } from '../project-discovery/get_app_bundle_id.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/macos/get_mac_bundle_id.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from project-discovery to complete workflow 2 | export { default } from '../project-discovery/get_mac_bundle_id.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/get_app_bundle_id.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export from project-discovery to complete workflow 2 | export { default } from '../project-discovery/get_app_bundle_id.ts'; 3 | ``` -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ], 5 | "unwantedRecommendations": [ 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | 10 | 11 | 12 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/show_build_settings.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified tool for simulator-project workflow 2 | export { default } from '../project-discovery/show_build_settings.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/macos/list_schemes.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified list_schemes tool for macos-project workflow 2 | export { default } from '../project-discovery/list_schemes.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/list_schemes.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified list_schemes tool for device-project workflow 2 | export { default } from '../project-discovery/list_schemes.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/list_schemes.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Re-export unified list_schemes tool for simulator-project workflow 2 | export { default } from '../project-discovery/list_schemes.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/utils/plugin-registry/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { loadWorkflowGroups, loadPlugins } from '../../core/plugin-registry.ts'; 2 | export { getEnabledWorkflows } from '../../core/dynamic-tools.ts'; 3 | ``` -------------------------------------------------------------------------------- /src/utils/logging/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Focused logging facade. 3 | * Prefer importing from 'utils/logging/index.js' instead of the legacy utils barrel. 4 | */ 5 | export { log } from '../logger.ts'; 6 | ``` -------------------------------------------------------------------------------- /src/utils/axe/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export { 2 | createAxeNotAvailableResponse, 3 | getAxePath, 4 | getBundledAxeEnvironment, 5 | areAxeToolsAvailable, 6 | isAxeAtLeastVersion, 7 | } from '../axe-helpers.ts'; 8 | ``` -------------------------------------------------------------------------------- /src/utils/validation/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Focused validation facade. 3 | * Prefer importing from 'utils/validation/index.js' instead of the legacy utils barrel. 4 | */ 5 | export * from '../validation.ts'; 6 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorApp/CalculatorApp.swift: -------------------------------------------------------------------------------- ```swift 1 | import SwiftUI 2 | import CalculatorAppFeature 3 | 4 | @main 5 | struct CalculatorApp: App { 6 | var body: some Scene { 7 | WindowGroup { 8 | ContentView() 9 | } 10 | } 11 | } 12 | 13 | #Preview { 14 | ContentView() 15 | } 16 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTest/MCPTestApp.swift: -------------------------------------------------------------------------------- ```swift 1 | // 2 | // MCPTestApp.swift 3 | // MCPTest 4 | // 5 | // Created by Cameron on 16/02/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct MCPTestApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | ``` -------------------------------------------------------------------------------- /example_projects/macOS/MCPTest/MCPTestApp.swift: -------------------------------------------------------------------------------- ```swift 1 | // 2 | // MCPTestApp.swift 3 | // MCPTest 4 | // 5 | // Created by Cameron on 16/02/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct MCPTestApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | ``` -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vitest/globals", "node"], 5 | "allowJs": true, 6 | "noEmit": true 7 | }, 8 | "include": ["src/**/*.test.ts", "tests-vitest/**/*"], 9 | "exclude": ["node_modules", "dist"] 10 | } ``` -------------------------------------------------------------------------------- /build-plugins/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "target": "ES2022", 6 | "outDir": "../build-plugins-dist", 7 | "rootDir": ".", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true 10 | }, 11 | "include": ["**/*.ts"], 12 | "exclude": ["node_modules", "dist"] 13 | } ``` -------------------------------------------------------------------------------- /src/mcp/tools/doctor/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'System Doctor', 3 | description: 4 | 'Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability.', 5 | platforms: ['system'], 6 | capabilities: [ 7 | 'doctor', 8 | 'server-diagnostics', 9 | 'troubleshooting', 10 | 'system-analysis', 11 | 'environment-validation', 12 | ], 13 | }; 14 | ``` -------------------------------------------------------------------------------- /src/utils/responses/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Focused responses facade. 3 | * Prefer importing from 'utils/responses/index.js' instead of the legacy utils barrel. 4 | */ 5 | export { createTextResponse } from '../validation.ts'; 6 | export { 7 | createErrorResponse, 8 | DependencyError, 9 | AxeError, 10 | SystemError, 11 | ValidationError, 12 | } from '../errors.ts'; 13 | 14 | // Types 15 | export type { ToolResponse } from '../../types/common.ts'; 16 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/macos/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'macOS Development', 3 | description: 4 | 'Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications.', 5 | platforms: ['macOS'], 6 | targets: ['native'], 7 | projectTypes: ['project', 'workspace'], 8 | capabilities: ['build', 'test', 'deploy', 'debug', 'app-management'], 9 | }; 10 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/project-discovery/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'Project Discovery', 3 | description: 4 | 'Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information.', 5 | platforms: ['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS'], 6 | capabilities: ['project-analysis', 'scheme-discovery', 'build-settings', 'bundle-inspection'], 7 | }; 8 | ``` -------------------------------------------------------------------------------- /example_projects/macOS/MCPTest/ContentView.swift: -------------------------------------------------------------------------------- ```swift 1 | // 2 | // ContentView.swift 3 | // MCPTest 4 | // 5 | // Created by Cameron on 16/02/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundStyle(.tint) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | 22 | #Preview { 23 | ContentView() 24 | } 25 | 26 | ``` -------------------------------------------------------------------------------- /src/utils/execution/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Focused execution facade. 3 | * Prefer importing from 'utils/execution/index.js' instead of the legacy utils barrel. 4 | */ 5 | export { getDefaultCommandExecutor, getDefaultFileSystemExecutor } from '../command.ts'; 6 | 7 | // Types 8 | export type { CommandExecutor, CommandResponse, CommandExecOptions } from '../CommandExecutor.ts'; 9 | export type { FileSystemExecutor } from '../FileSystemExecutor.ts'; 10 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/logging/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'Log Capture & Management', 3 | description: 4 | 'Log capture and management tools for iOS simulators and physical devices. Start, stop, and analyze application and system logs during development and testing.', 5 | platforms: ['iOS', 'watchOS', 'tvOS', 'visionOS'], 6 | targets: ['simulator', 'device'], 7 | capabilities: ['log-capture', 'log-analysis', 'debugging', 'monitoring'], 8 | }; 9 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'iOS Simulator Development', 3 | description: 4 | 'Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators.', 5 | platforms: ['iOS'], 6 | targets: ['simulator'], 7 | projectTypes: ['project', 'workspace'], 8 | capabilities: ['build', 'test', 'deploy', 'debug', 'ui-automation'], 9 | }; 10 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/utilities/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'Project Utilities', 3 | description: 4 | 'Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files.', 5 | platforms: ['iOS', 'macOS'], 6 | targets: ['simulator', 'device', 'mac'], 7 | projectTypes: ['project', 'workspace'], 8 | capabilities: ['project-cleaning', 'project-maintenance'], 9 | }; 10 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/ui-testing/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'UI Testing & Automation', 3 | description: 4 | 'UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows.', 5 | platforms: ['iOS'], 6 | targets: ['simulator'], 7 | capabilities: [ 8 | 'ui-automation', 9 | 'gesture-simulation', 10 | 'screenshot-capture', 11 | 'accessibility-testing', 12 | 'ui-analysis', 13 | ], 14 | }; 15 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/session-management/session_show_defaults.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { sessionStore } from '../../../utils/session-store.ts'; 2 | import type { ToolResponse } from '../../../types/common.ts'; 3 | 4 | export default { 5 | name: 'session-show-defaults', 6 | description: 'Show current session defaults.', 7 | schema: {}, 8 | handler: async (): Promise<ToolResponse> => { 9 | const current = sessionStore.getAll(); 10 | return { content: [{ type: 'text', text: JSON.stringify(current, null, 2) }], isError: false }; 11 | }, 12 | }; 13 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/swift-package/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'Swift Package Manager', 3 | description: 4 | 'Swift Package Manager operations for building, testing, running, and managing Swift packages and dependencies. Complete SPM workflow support.', 5 | platforms: ['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS', 'Linux'], 6 | targets: ['package'], 7 | projectTypes: ['swift-package'], 8 | capabilities: ['build', 'test', 'run', 'clean', 'dependency-management', 'package-management'], 9 | }; 10 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | description: Configuration for XcodeBuildMCP (no options needed) 9 | commandFunction: 10 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 11 | |- 12 | (config) => ({ command: 'xcodebuildmcp', args: [], env: {} }) 13 | exampleConfig: {} 14 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/session-management/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'session-management', 3 | description: 4 | 'Manage session defaults for projectPath/workspacePath, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS and arch. These defaults are required by many tools and must be set before attempting to call tools that would depend on these values.', 5 | platforms: ['iOS', 'macOS', 'tvOS', 'watchOS', 'visionOS'], 6 | targets: ['simulator', 'device'], 7 | capabilities: ['configuration', 'state-management'], 8 | }; 9 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/device/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const workflow = { 2 | name: 'iOS Device Development', 3 | description: 4 | '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.', 5 | platforms: ['iOS', 'watchOS', 'tvOS', 'visionOS'], 6 | targets: ['device'], 7 | projectTypes: ['project', 'workspace'], 8 | capabilities: ['build', 'test', 'deploy', 'debug', 'log-capture', 'device-management'], 9 | }; 10 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/discovery/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Discovery Workflow 3 | * 4 | * Dynamic tool discovery and workflow recommendation system 5 | */ 6 | 7 | export const workflow = { 8 | name: 'Dynamic Tool Discovery', 9 | description: 10 | 'Intelligent discovery and recommendation of appropriate development workflows based on project structure and requirements', 11 | platforms: ['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS'], 12 | targets: ['simulator', 'device'], 13 | projectTypes: ['project', 'workspace', 'package'], 14 | capabilities: ['discovery', 'recommendation', 'workflow-analysis'], 15 | }; 16 | ``` -------------------------------------------------------------------------------- /src/utils/CommandExecutor.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ChildProcess } from 'child_process'; 2 | 3 | export interface CommandExecOptions { 4 | env?: Record<string, string>; 5 | cwd?: string; 6 | } 7 | 8 | /** 9 | * Command executor function type for dependency injection 10 | */ 11 | export type CommandExecutor = ( 12 | command: string[], 13 | logPrefix?: string, 14 | useShell?: boolean, 15 | opts?: CommandExecOptions, 16 | detached?: boolean, 17 | ) => Promise<CommandResponse>; 18 | /** 19 | * Command execution response interface 20 | */ 21 | 22 | export interface CommandResponse { 23 | success: boolean; 24 | output: string; 25 | error?: string; 26 | process: ChildProcess; 27 | exitCode?: number; 28 | } 29 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/project-scaffolding/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Project Scaffolding workflow 3 | * 4 | * Provides tools for creating new iOS and macOS projects from templates. 5 | * These tools are used at project inception to bootstrap new applications 6 | * with best practices and standard configurations. 7 | */ 8 | 9 | export const workflow = { 10 | name: 'Project Scaffolding', 11 | description: 12 | 'Tools for creating new iOS and macOS projects from templates. Bootstrap new applications with best practices, standard configurations, and modern project structures.', 13 | platforms: ['iOS', 'macOS'], 14 | targets: ['simulator', 'device', 'mac'], 15 | projectTypes: ['project'], 16 | capabilities: ['project-creation', 'template-generation', 'project-initialization'], 17 | }; 18 | ``` -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [24.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Build 30 | run: npm run build 31 | 32 | - name: Lint 33 | run: npm run lint 34 | 35 | - name: Check formatting 36 | run: npm run format:check 37 | 38 | - name: Type check 39 | run: npm run typecheck 40 | 41 | - name: Run tests 42 | run: npm test 43 | 44 | - run: npx pkg-pr-new publish 45 | ``` -------------------------------------------------------------------------------- /src/utils/FileSystemExecutor.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * File system executor interface for dependency injection 3 | */ 4 | 5 | export interface FileSystemExecutor { 6 | mkdir(path: string, options?: { recursive?: boolean }): Promise<void>; 7 | readFile(path: string, encoding?: BufferEncoding): Promise<string>; 8 | writeFile(path: string, content: string, encoding?: BufferEncoding): Promise<void>; 9 | cp(source: string, destination: string, options?: { recursive?: boolean }): Promise<void>; 10 | readdir(path: string, options?: { withFileTypes?: boolean }): Promise<unknown[]>; 11 | rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>; 12 | existsSync(path: string): boolean; 13 | stat(path: string): Promise<{ isDirectory(): boolean }>; 14 | mkdtemp(prefix: string): Promise<string>; 15 | tmpdir(): string; 16 | } 17 | ``` -------------------------------------------------------------------------------- /src/utils/schema-helpers.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Schema Helper Utilities 3 | * 4 | * Shared utility functions for schema validation and preprocessing. 5 | */ 6 | 7 | /** 8 | * Convert empty strings to undefined in an object (shallow transformation) 9 | * Used for preprocessing Zod schemas with optional fields 10 | * 11 | * @param value - The value to process 12 | * @returns The processed value with empty strings converted to undefined 13 | */ 14 | export function nullifyEmptyStrings(value: unknown): unknown { 15 | if (value && typeof value === 'object' && !Array.isArray(value)) { 16 | const copy: Record<string, unknown> = { ...(value as Record<string, unknown>) }; 17 | for (const key of Object.keys(copy)) { 18 | const v = copy[key]; 19 | if (typeof v === 'string' && v.trim() === '') copy[key] = undefined; 20 | } 21 | return copy; 22 | } 23 | return value; 24 | } 25 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "sourceMap": true, 13 | "inlineSources": true, 14 | 15 | // Set `sourceRoot` to "/" to strip the build path prefix 16 | // from generated source code references. 17 | // This improves issue grouping in Sentry. 18 | "sourceRoot": "/", 19 | "allowImportingTsExtensions": true, 20 | "noEmit": true 21 | }, 22 | "include": ["src/**/*"], 23 | "exclude": [ 24 | "node_modules", 25 | "**/*.test.ts", 26 | "tests-vitest/**/*", 27 | "plugins/**/*", 28 | "src/core/generated-plugins.ts", 29 | "src/core/generated-resources.ts" 30 | ] 31 | } 32 | ``` -------------------------------------------------------------------------------- /src/core/plugin-types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import { ToolResponse } from '../types/common.ts'; 3 | 4 | export interface PluginMeta { 5 | readonly name: string; // Verb used by MCP 6 | readonly schema: Record<string, z.ZodTypeAny>; // Zod validation schema (object schema) 7 | readonly description?: string; // One-liner shown in help 8 | handler(params: Record<string, unknown>): Promise<ToolResponse>; 9 | } 10 | 11 | export interface WorkflowMeta { 12 | readonly name: string; 13 | readonly description: string; 14 | readonly platforms?: string[]; 15 | readonly targets?: string[]; 16 | readonly projectTypes?: string[]; 17 | readonly capabilities?: string[]; 18 | } 19 | 20 | export interface WorkflowGroup { 21 | readonly workflow: WorkflowMeta; 22 | readonly tools: PluginMeta[]; 23 | readonly directoryName: string; 24 | } 25 | 26 | export const defineTool = (meta: PluginMeta): PluginMeta => meta; 27 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator-management/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simulator Management workflow 3 | * 4 | * Provides tools for working with simulators like booting and opening simulators, launching apps, 5 | * listing sims, stopping apps, erasing simulator content and settings, and setting sim environment 6 | * options like location, network, statusbar and appearance. 7 | */ 8 | 9 | export const workflow = { 10 | name: 'Simulator Management', 11 | description: 12 | '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.', 13 | platforms: ['iOS'], 14 | targets: ['simulator'], 15 | projectTypes: ['project', 'workspace'], 16 | capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar', 'erase'], 17 | }; 18 | ``` -------------------------------------------------------------------------------- /example_projects/macOS/MCPTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | ``` -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineConfig } from 'tsup'; 2 | import { chmodSync, existsSync } from 'fs'; 3 | import { createPluginDiscoveryPlugin } from './build-plugins/plugin-discovery.js'; 4 | 5 | export default defineConfig({ 6 | entry: { 7 | index: 'src/index.ts', 8 | 'doctor-cli': 'src/doctor-cli.ts', 9 | }, 10 | format: ['esm'], 11 | target: 'node18', 12 | platform: 'node', 13 | outDir: 'build', 14 | clean: true, 15 | sourcemap: true, // Enable source maps for debugging 16 | dts: { 17 | entry: { 18 | index: 'src/index.ts', 19 | }, 20 | }, 21 | splitting: false, 22 | shims: false, 23 | treeshake: true, 24 | minify: false, 25 | esbuildPlugins: [createPluginDiscoveryPlugin()], 26 | onSuccess: async () => { 27 | console.log('✅ Build complete!'); 28 | 29 | // Set executable permissions for built files 30 | if (existsSync('build/index.js')) { 31 | chmodSync('build/index.js', '755'); 32 | } 33 | if (existsSync('build/doctor-cli.js')) { 34 | chmodSync('build/doctor-cli.js', '755'); 35 | } 36 | }, 37 | }); ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config 2 | # Use a small base image with Node.js LTS 3 | FROM node:lts-alpine AS build 4 | 5 | # Install dependencies needed for building 6 | RUN apk add --no-cache python3 g++ make git 7 | 8 | # Create app directory 9 | WORKDIR /usr/src/app 10 | 11 | # Copy package manifests 12 | COPY package.json package-lock.json tsconfig.json eslint.config.js ./ 13 | 14 | # Copy source 15 | COPY src ./src 16 | 17 | # Install dependencies ignoring any prepare scripts, then build 18 | RUN npm ci --ignore-scripts 19 | RUN npm run prebuild && npm run build 20 | 21 | # Stage for runtime 22 | FROM node:lts-alpine 23 | WORKDIR /usr/src/app 24 | 25 | # Install minimal runtime dependencies 26 | # No build tools needed, install production deps 27 | COPY package.json package-lock.json ./ 28 | RUN npm ci --omit=dev --ignore-scripts 29 | 30 | # Copy built files 31 | COPY --from=build /usr/src/app/build ./build 32 | 33 | # Symlink binary 34 | RUN npm link 35 | 36 | # Default command 37 | ENTRYPOINT ["xcodebuildmcp"] 38 | CMD [] 39 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/swift-package/active-processes.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared process state management for Swift Package tools 3 | * This module provides a centralized way to manage active processes 4 | * between swift_package_run and swift_package_stop tools 5 | */ 6 | 7 | export interface ProcessInfo { 8 | process: { 9 | kill: (signal?: string) => void; 10 | on: (event: string, callback: () => void) => void; 11 | pid?: number; 12 | }; 13 | startedAt: Date; 14 | } 15 | 16 | // Global map to track active processes 17 | export const activeProcesses = new Map<number, ProcessInfo>(); 18 | 19 | // Helper functions for process management 20 | export const getProcess = (pid: number): ProcessInfo | undefined => { 21 | return activeProcesses.get(pid); 22 | }; 23 | 24 | export const addProcess = (pid: number, processInfo: ProcessInfo): void => { 25 | activeProcesses.set(pid, processInfo); 26 | }; 27 | 28 | export const removeProcess = (pid: number): boolean => { 29 | return activeProcesses.delete(pid); 30 | }; 31 | 32 | export const clearAllProcesses = (): void => { 33 | activeProcesses.clear(); 34 | }; 35 | ``` -------------------------------------------------------------------------------- /.github/workflows/sentry.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Sentry Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: Install dependencies 15 | run: npm ci 16 | 17 | - name: Build project 18 | run: npm run build 19 | 20 | - name: Run tests 21 | run: npm test 22 | 23 | - name: Extract version from build/version.js 24 | id: get_version 25 | run: echo "MCP_VERSION=$(grep -oE "'[0-9]+\.[0-9]+\.[0-9]+'" build/version.js | tr -d "'")" >> $GITHUB_OUTPUT 26 | 27 | - name: Create Sentry release 28 | uses: getsentry/action-release@v3 29 | env: 30 | SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 31 | SENTRY_ORG: ${{ secrets.SENTRY_ORG }} 32 | SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} 33 | with: 34 | environment: production 35 | sourcemaps: "./build" 36 | version: ${{ steps.get_version.outputs.MCP_VERSION }} ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorAppPackage/Package.swift: -------------------------------------------------------------------------------- ```swift 1 | // swift-tools-version: 5.10 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CalculatorAppFeature", 8 | platforms: [.iOS(.v17)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "CalculatorAppFeature", 13 | targets: ["CalculatorAppFeature"] 14 | ), 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package, defining a module or a test suite. 18 | // Targets can depend on other targets in this package and products from dependencies. 19 | .target( 20 | name: "CalculatorAppFeature" 21 | ), 22 | .testTarget( 23 | name: "CalculatorAppFeatureTests", 24 | dependencies: [ 25 | "CalculatorAppFeature" 26 | ] 27 | ), 28 | ] 29 | ) 30 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/BackgroundEffect.swift: -------------------------------------------------------------------------------- ```swift 1 | import SwiftUI 2 | 3 | // MARK: - Background State Management 4 | enum BackgroundState { 5 | case normal, calculated, error 6 | 7 | var colors: [Color] { 8 | switch self { 9 | case .normal: 10 | return [Color.blue.opacity(0.8), Color.purple.opacity(0.8), Color.indigo.opacity(0.9)] 11 | case .calculated: 12 | return [Color.green.opacity(0.7), Color.mint.opacity(0.8), Color.teal.opacity(0.9)] 13 | case .error: 14 | return [Color.red.opacity(0.7), Color.pink.opacity(0.8), Color.orange.opacity(0.9)] 15 | } 16 | } 17 | } 18 | 19 | // MARK: - Animated Background Component 20 | struct AnimatedBackground: View { 21 | let backgroundGradient: BackgroundState 22 | 23 | var body: some View { 24 | AngularGradient( 25 | colors: backgroundGradient.colors, 26 | center: .topLeading, 27 | angle: .degrees(45) 28 | ) 29 | .ignoresSafeArea() 30 | .animation(.easeInOut(duration: 0.8), value: backgroundGradient) 31 | } 32 | } ``` -------------------------------------------------------------------------------- /example_projects/spm/Tests/TestLibTests/SimpleTests.swift: -------------------------------------------------------------------------------- ```swift 1 | import Testing 2 | 3 | @Test("Basic truth assertions") 4 | func basicTruthTest() { 5 | #expect(true == true) 6 | #expect(false == false) 7 | #expect(true != false) 8 | } 9 | 10 | @Test("Basic math operations") 11 | func basicMathTest() { 12 | #expect(2 + 2 == 4) 13 | #expect(5 - 3 == 2) 14 | #expect(3 * 4 == 12) 15 | #expect(10 / 2 == 5) 16 | } 17 | 18 | @Test("String operations") 19 | func stringTest() { 20 | let greeting = "Hello" 21 | let world = "World" 22 | #expect(greeting + " " + world == "Hello World") 23 | #expect(greeting.count == 5) 24 | #expect(world.isEmpty == false) 25 | } 26 | 27 | @Test("Array operations") 28 | func arrayTest() { 29 | let numbers = [1, 2, 3, 4, 5] 30 | #expect(numbers.count == 5) 31 | #expect(numbers.first == 1) 32 | #expect(numbers.last == 5) 33 | #expect(numbers.contains(3) == true) 34 | } 35 | 36 | @Test("Optional handling") 37 | func optionalTest() { 38 | let someValue: Int? = 42 39 | let nilValue: Int? = nil 40 | 41 | #expect(someValue != nil) 42 | #expect(nilValue == nil) 43 | #expect(someValue! == 42) 44 | } 45 | ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/CalculatorInputHandler.swift: -------------------------------------------------------------------------------- ```swift 1 | import Foundation 2 | 3 | // MARK: - Input Handling 4 | /// Handles input parsing and routing to the calculator service 5 | struct CalculatorInputHandler { 6 | private let service: CalculatorService 7 | 8 | init(service: CalculatorService) { 9 | self.service = service 10 | } 11 | 12 | func handleInput(_ input: String) { 13 | switch input { 14 | case "C": 15 | service.clear() 16 | case "±": 17 | service.toggleSign() 18 | case "%": 19 | service.percentage() 20 | case "+", "-", "×", "÷": 21 | if let operation = CalculatorService.Operation(rawValue: input) { 22 | service.setOperation(operation) 23 | } 24 | case "=": 25 | service.calculate() 26 | case ".": 27 | service.inputDecimal() 28 | case "0"..."9": 29 | service.inputNumber(input) 30 | default: 31 | break // Ignore unknown inputs 32 | } 33 | } 34 | 35 | func deleteLastDigit() { 36 | service.deleteLastDigit() 37 | } 38 | } 39 | ``` -------------------------------------------------------------------------------- /example_projects/spm/Sources/quick-task/main.swift: -------------------------------------------------------------------------------- ```swift 1 | import Foundation 2 | import TestLib 3 | import ArgumentParser 4 | 5 | @main 6 | struct QuickTask: AsyncParsableCommand { 7 | static let configuration = CommandConfiguration( 8 | commandName: "quick-task", 9 | abstract: "A quick task that finishes within 5 seconds", 10 | version: "1.0.0" 11 | ) 12 | 13 | @Option(name: .shortAndLong, help: "Number of seconds to work (default: 3)") 14 | var duration: Int = 3 15 | 16 | @Flag(name: .shortAndLong, help: "Enable verbose output") 17 | var verbose: Bool = false 18 | 19 | @Option(name: .shortAndLong, help: "Task name to display") 20 | var taskName: String = "DefaultTask" 21 | 22 | func run() async throws { 23 | let taskManager = TaskManager() 24 | 25 | if verbose { 26 | print("🚀 Starting quick task: \(taskName)") 27 | print("⏱️ Duration: \(duration) seconds") 28 | } 29 | 30 | await taskManager.executeQuickTask(name: taskName, duration: duration, verbose: verbose) 31 | 32 | if verbose { 33 | print("✅ Quick task completed successfully!") 34 | } 35 | } 36 | } ``` -------------------------------------------------------------------------------- /example_projects/spm/Package.swift: -------------------------------------------------------------------------------- ```swift 1 | // swift-tools-version: 6.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "spm", 8 | platforms: [ 9 | .macOS(.v15), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.1"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "spm" 17 | ), 18 | .executableTarget( 19 | name: "quick-task", 20 | dependencies: [ 21 | "TestLib", 22 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 23 | ] 24 | ), 25 | .executableTarget( 26 | name: "long-server", 27 | dependencies: [ 28 | "TestLib", 29 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 30 | ] 31 | ), 32 | .target( 33 | name: "TestLib" 34 | ), 35 | .testTarget( 36 | name: "TestLibTests", 37 | dependencies: ["TestLib"] 38 | ), 39 | ] 40 | ) 41 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTest/ContentView.swift: -------------------------------------------------------------------------------- ```swift 1 | // 2 | // ContentView.swift 3 | // MCPTest 4 | // 5 | // Created by Cameron on 16/02/2025. 6 | // 7 | 8 | import SwiftUI 9 | import OSLog 10 | 11 | struct ContentView: View { 12 | @State private var text: String = "" 13 | 14 | var body: some View { 15 | VStack { 16 | Image(systemName: "globe") 17 | .imageScale(.large) 18 | .foregroundStyle(.tint) 19 | TextField("Enter text", text: $text) 20 | .textFieldStyle(RoundedBorderTextFieldStyle()) 21 | .padding(.horizontal) 22 | Text(text) 23 | 24 | Button("Log something") { 25 | let message = ProcessInfo.processInfo.environment.map { "\($0.key): \($0.value)" }.joined(separator: "\n") 26 | Logger.myApp.debug("Environment: \(message)") 27 | debugPrint("Button was pressed.") 28 | 29 | text = "You just pressed the button!" 30 | } 31 | } 32 | .padding() 33 | } 34 | } 35 | 36 | #Preview { 37 | ContentView() 38 | } 39 | 40 | // OS Log Extension 41 | extension Logger { 42 | static let myApp = Logger( 43 | subsystem: "com.cameroncooke.MCPTest", 44 | category: "default" 45 | ) 46 | } 47 | 48 | ``` -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | globals: true, 7 | include: [ 8 | 'src/**/__tests__/**/*.test.ts' // Only __tests__ directories 9 | ], 10 | exclude: [ 11 | 'node_modules/**', 12 | 'build/**', 13 | 'coverage/**', 14 | 'bundled/**', 15 | 'example_projects/**', 16 | '.git/**', 17 | '**/*.d.ts', 18 | '**/temp_*', 19 | '**/full-output.txt', 20 | '**/experiments/**', 21 | '**/__pycache__/**', 22 | '**/dist/**' 23 | ], 24 | pool: 'threads', 25 | poolOptions: { 26 | threads: { 27 | maxThreads: 4 28 | } 29 | }, 30 | env: { 31 | NODE_OPTIONS: '--max-old-space-size=4096' 32 | }, 33 | testTimeout: 30000, 34 | hookTimeout: 10000, 35 | teardownTimeout: 5000, 36 | coverage: { 37 | provider: 'v8', 38 | reporter: ['text', 'json', 'html'], 39 | exclude: [ 40 | 'node_modules/**', 41 | 'build/**', 42 | 'tests/**', 43 | 'example_projects/**', 44 | '**/*.config.*', 45 | '**/*.d.ts' 46 | ] 47 | } 48 | }, 49 | resolve: { 50 | alias: { 51 | // Handle .js imports in TypeScript files 52 | '^(\\.{1,2}/.*)\\.js$': '$1' 53 | } 54 | } 55 | }); ``` -------------------------------------------------------------------------------- /src/mcp/tools/session-management/session_clear_defaults.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import { sessionStore } from '../../../utils/session-store.ts'; 3 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; 4 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; 5 | import type { ToolResponse } from '../../../types/common.ts'; 6 | 7 | const keys = [ 8 | 'projectPath', 9 | 'workspacePath', 10 | 'scheme', 11 | 'configuration', 12 | 'simulatorName', 13 | 'simulatorId', 14 | 'deviceId', 15 | 'useLatestOS', 16 | 'arch', 17 | ] as const; 18 | 19 | const schemaObj = z.object({ 20 | keys: z.array(z.enum(keys)).optional(), 21 | all: z.boolean().optional(), 22 | }); 23 | 24 | type Params = z.infer<typeof schemaObj>; 25 | 26 | export async function sessionClearDefaultsLogic(params: Params): Promise<ToolResponse> { 27 | if (params.all || !params.keys) sessionStore.clear(); 28 | else sessionStore.clear(params.keys); 29 | return { content: [{ type: 'text', text: 'Session defaults cleared' }], isError: false }; 30 | } 31 | 32 | export default { 33 | name: 'session-clear-defaults', 34 | description: 'Clear selected or all session defaults.', 35 | schema: schemaObj.shape, 36 | handler: createTypedTool(schemaObj, sessionClearDefaultsLogic, getDefaultCommandExecutor), 37 | }; 38 | ``` -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "servers": { 3 | "XcodeBuildMCP": { 4 | "type": "stdio", 5 | "command": "npx", 6 | "args": [ 7 | "-y", 8 | "xcodebuildmcp@latest" 9 | ], 10 | "env": { 11 | "XCODEBUILDMCP_DEBUG": "true", 12 | "XCODEBUILDMCP_DYNAMIC_TOOLS": "true", 13 | "INCREMENTAL_BUILDS_ENABLED": "false", 14 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/Volumes/Developer/XcodeBuildMCP-iOS-Template", 15 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/Volumes/Developer/XcodeBuildMCP-macOS-Template" 16 | } 17 | }, 18 | "XcodeBuildMCP-Dev": { 19 | "type": "stdio", 20 | "command": "node", 21 | "args": [ 22 | "--inspect-brk=9999", 23 | "--trace-warnings", 24 | "/Users/cameroncooke/Developer/XcodeBuildMCP/build/index.js" 25 | ], 26 | "env": { 27 | "XCODEBUILDMCP_DEBUG": "true", 28 | "XCODEBUILDMCP_DYNAMIC_TOOLS": "true", 29 | "INCREMENTAL_BUILDS_ENABLED": "false", 30 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/Volumes/Developer/XcodeBuildMCP-iOS-Template", 31 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/Volumes/Developer/XcodeBuildMCP-macOS-Template" 32 | } 33 | }, 34 | } 35 | } ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Feature Request 2 | description: Suggest a new feature for XcodeBuildMCP 3 | title: "[Feature]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for suggesting a new feature for XcodeBuildMCP! 10 | 11 | - type: textarea 12 | id: feature-description 13 | attributes: 14 | label: Feature Description 15 | description: Describe the new capability you'd like to add to XcodeBuildMCP 16 | placeholder: I would like the AI assistant to be able to... 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | id: use-cases 22 | attributes: 23 | label: Use Cases 24 | description: Describe specific scenarios where this feature would be useful 25 | placeholder: | 26 | - Building and testing iOS apps with custom schemes 27 | - Managing multiple simulator configurations 28 | - Automating complex Xcode workflows 29 | validations: 30 | required: false 31 | 32 | - type: textarea 33 | id: example-interactions 34 | attributes: 35 | label: Example Interactions 36 | description: Provide examples of how you envision using this feature 37 | placeholder: | 38 | You: [Example request to the AI] 39 | AI: [Desired response/action] 40 | validations: 41 | required: false 42 | ``` -------------------------------------------------------------------------------- /src/utils/session-store.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { log } from './logger.ts'; 2 | 3 | export type SessionDefaults = { 4 | projectPath?: string; 5 | workspacePath?: string; 6 | scheme?: string; 7 | configuration?: string; 8 | simulatorName?: string; 9 | simulatorId?: string; 10 | deviceId?: string; 11 | useLatestOS?: boolean; 12 | arch?: 'arm64' | 'x86_64'; 13 | }; 14 | 15 | class SessionStore { 16 | private defaults: SessionDefaults = {}; 17 | 18 | setDefaults(partial: Partial<SessionDefaults>): void { 19 | this.defaults = { ...this.defaults, ...partial }; 20 | log('info', `[Session] Defaults updated: ${Object.keys(partial).join(', ')}`); 21 | } 22 | 23 | clear(keys?: (keyof SessionDefaults)[]): void { 24 | if (keys == null) { 25 | this.defaults = {}; 26 | log('info', '[Session] All defaults cleared'); 27 | return; 28 | } 29 | if (keys.length === 0) { 30 | // No-op when an empty array is provided (e.g., empty UI selection) 31 | log('info', '[Session] No keys provided to clear; no changes made'); 32 | return; 33 | } 34 | for (const k of keys) delete this.defaults[k]; 35 | log('info', `[Session] Defaults cleared: ${keys.join(', ')}`); 36 | } 37 | 38 | get<K extends keyof SessionDefaults>(key: K): SessionDefaults[K] { 39 | return this.defaults[key]; 40 | } 41 | 42 | getAll(): SessionDefaults { 43 | return { ...this.defaults }; 44 | } 45 | } 46 | 47 | export const sessionStore = new SessionStore(); 48 | ``` -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "eslint.useFlatConfig": true, 3 | "eslint.validate": [ 4 | "javascript", 5 | "typescript" 6 | ], 7 | "eslint.runtime": "/opt/homebrew/bin/node", 8 | "eslint.format.enable": true, 9 | "eslint.nodePath": "${workspaceFolder}/node_modules", 10 | "eslint.workingDirectories": [ 11 | { 12 | "directory": "${workspaceFolder}", 13 | "changeProcessCWD": true 14 | } 15 | ], 16 | "typescript.tsdk": "node_modules/typescript/lib", 17 | "typescript.enablePromptUseWorkspaceTsdk": true, 18 | "typescript.tsserver.maxTsServerMemory": 4096, 19 | "javascript.validate.enable": false, 20 | "typescript.validate.enable": false, 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.eslint": "explicit" 23 | }, 24 | "editor.defaultFormatter": "vscode.typescript-language-features", 25 | "[typescript]": { 26 | "editor.defaultFormatter": "vscode.typescript-language-features" 27 | }, 28 | "[javascript]": { 29 | "editor.defaultFormatter": "vscode.typescript-language-features" 30 | }, 31 | "terminal.integrated.shellIntegration.decorationsEnabled": "never", 32 | "vitest.nodeExecutable": "/opt/homebrew/bin/node", 33 | "[json]": { 34 | "editor.defaultFormatter": "vscode.json-language-features" 35 | }, 36 | "[jsonc]": { 37 | "editor.defaultFormatter": "vscode.json-language-features" 38 | }, 39 | "chat.mcp.serverSampling": { 40 | "XcodeBuildMCP/.vscode/mcp.json: XcodeBuildMCP-Dev": { 41 | "allowedDuringChat": true 42 | } 43 | }, 44 | } ``` -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "Wait for MCP Server to Start", 8 | "port": 9999, 9 | "address": "localhost", 10 | "restart": true, 11 | "skipFiles": [ 12 | "<node_internals>/**" 13 | ], 14 | "sourceMaps": true, 15 | "outFiles": [ 16 | "${workspaceFolder}/build/**/*.js" 17 | ], 18 | "cwd": "${workspaceFolder}", 19 | "sourceMapPathOverrides": { 20 | "/*": "${workspaceFolder}/src/*" 21 | }, 22 | "timeout": 60000, 23 | "localRoot": "${workspaceFolder}", 24 | "remoteRoot": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "node", 28 | "request": "launch", 29 | "name": "Launch MCP Server Dev", 30 | "program": "${workspaceFolder}/build/index.js", 31 | "cwd": "${workspaceFolder}", 32 | "runtimeArgs": [ 33 | "--inspect=9999" 34 | ], 35 | "env": { 36 | "XCODEBUILDMCP_DEBUG": "true", 37 | "XCODEBUILDMCP_DYNAMIC_TOOLS": "true", 38 | "INCREMENTAL_BUILDS_ENABLED": "false", 39 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/Volumes/Developer/XcodeBuildMCP-iOS-Template", 40 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/Volumes/Developer/XcodeBuildMCP-macOS-Template" 41 | }, 42 | "sourceMaps": true, 43 | "outFiles": [ 44 | "${workspaceFolder}/build/**/*.js" 45 | ], 46 | "skipFiles": [ 47 | "<node_internals>/**" 48 | ], 49 | "sourceMapPathOverrides": { 50 | "/*": "${workspaceFolder}/src/*" 51 | } 52 | } 53 | ] 54 | } 55 | ``` -------------------------------------------------------------------------------- /.github/workflows/claude-dispatch.yml: -------------------------------------------------------------------------------- ```yaml 1 | # IMPORTANT: Do not move this file in your repo! Make sure it's located at .github/workflows/claude-dispatch.yml 2 | name: Claude Code Dispatch 3 | 4 | # IMPORTANT: Do not modify this `on` section! 5 | on: 6 | repository_dispatch: 7 | types: [claude-dispatch] 8 | 9 | jobs: 10 | claude-dispatch: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | issues: write 16 | id-token: write 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 1 22 | 23 | # - name: Preliminary Setup 24 | # run: | 25 | # echo "Setting up environment..." 26 | # Add any preliminary setup commands here to setup Claude's dev environment 27 | # e.g., npm install, etc. 28 | 29 | - name: Run Claude Code 30 | id: claude 31 | uses: anthropics/claude-code-action@eap 32 | with: 33 | mode: 'remote-agent' 34 | 35 | # Optional: Specify an API key, otherwise we'll use your Claude account automatically 36 | # anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} 37 | 38 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 39 | # model: "claude-opus-4-20250514" 40 | 41 | # Optional: Allow Claude to run specific commands 42 | # allowed_tools: | 43 | # Bash(npm run lint) 44 | # Bash(npm run test) 45 | # Bash(npm run build) 46 | 47 | # Optional: Custom environment variables for Claude 48 | # claude_env: | 49 | # NODE_ENV: test ``` -------------------------------------------------------------------------------- /src/mcp/tools/session-management/__tests__/session_show_defaults.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, it, expect, beforeEach, afterEach } from 'vitest'; 2 | import { sessionStore } from '../../../../utils/session-store.ts'; 3 | import plugin from '../session_show_defaults.ts'; 4 | 5 | describe('session-show-defaults tool', () => { 6 | beforeEach(() => { 7 | sessionStore.clear(); 8 | }); 9 | 10 | afterEach(() => { 11 | sessionStore.clear(); 12 | }); 13 | 14 | describe('Export Field Validation (Literal)', () => { 15 | it('should have correct name', () => { 16 | expect(plugin.name).toBe('session-show-defaults'); 17 | }); 18 | 19 | it('should have correct description', () => { 20 | expect(plugin.description).toBe('Show current session defaults.'); 21 | }); 22 | 23 | it('should have handler function', () => { 24 | expect(typeof plugin.handler).toBe('function'); 25 | }); 26 | 27 | it('should have empty schema', () => { 28 | expect(plugin.schema).toEqual({}); 29 | }); 30 | }); 31 | 32 | describe('Handler Behavior', () => { 33 | it('should return empty defaults when none set', async () => { 34 | const result = await plugin.handler({}); 35 | expect(result.isError).toBe(false); 36 | const parsed = JSON.parse(result.content[0].text); 37 | expect(parsed).toEqual({}); 38 | }); 39 | 40 | it('should return current defaults when set', async () => { 41 | sessionStore.setDefaults({ scheme: 'MyScheme', simulatorId: 'SIM-123' }); 42 | const result = await plugin.handler({}); 43 | expect(result.isError).toBe(false); 44 | const parsed = JSON.parse(result.content[0].text); 45 | expect(parsed.scheme).toBe('MyScheme'); 46 | expect(parsed.simulatorId).toBe('SIM-123'); 47 | }); 48 | }); 49 | }); 50 | ``` -------------------------------------------------------------------------------- /src/utils/__tests__/session-store.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, it, expect, beforeEach } from 'vitest'; 2 | import { sessionStore } from '../session-store.ts'; 3 | 4 | describe('SessionStore', () => { 5 | beforeEach(() => { 6 | sessionStore.clear(); 7 | }); 8 | 9 | it('should set and get defaults', () => { 10 | sessionStore.setDefaults({ scheme: 'App', useLatestOS: true }); 11 | expect(sessionStore.get('scheme')).toBe('App'); 12 | expect(sessionStore.get('useLatestOS')).toBe(true); 13 | }); 14 | 15 | it('should merge defaults on set', () => { 16 | sessionStore.setDefaults({ scheme: 'App' }); 17 | sessionStore.setDefaults({ simulatorName: 'iPhone 16' }); 18 | const all = sessionStore.getAll(); 19 | expect(all.scheme).toBe('App'); 20 | expect(all.simulatorName).toBe('iPhone 16'); 21 | }); 22 | 23 | it('should clear specific keys', () => { 24 | sessionStore.setDefaults({ scheme: 'App', simulatorId: 'SIM-1', deviceId: 'DEV-1' }); 25 | sessionStore.clear(['simulatorId']); 26 | const all = sessionStore.getAll(); 27 | expect(all.scheme).toBe('App'); 28 | expect(all.simulatorId).toBeUndefined(); 29 | expect(all.deviceId).toBe('DEV-1'); 30 | }); 31 | 32 | it('should clear all when no keys provided', () => { 33 | sessionStore.setDefaults({ scheme: 'App', simulatorId: 'SIM-1' }); 34 | sessionStore.clear(); 35 | const all = sessionStore.getAll(); 36 | expect(Object.keys(all).length).toBe(0); 37 | }); 38 | 39 | it('should be a no-op when empty keys array provided', () => { 40 | sessionStore.setDefaults({ scheme: 'App', simulatorId: 'SIM-1' }); 41 | sessionStore.clear([]); 42 | const all = sessionStore.getAll(); 43 | expect(all.scheme).toBe('App'); 44 | expect(all.simulatorId).toBe('SIM-1'); 45 | }); 46 | }); 47 | ``` -------------------------------------------------------------------------------- /example_projects/spm/Sources/long-server/main.swift: -------------------------------------------------------------------------------- ```swift 1 | import Foundation 2 | import TestLib 3 | import ArgumentParser 4 | 5 | @main 6 | struct LongServer: AsyncParsableCommand { 7 | static let configuration = CommandConfiguration( 8 | commandName: "long-server", 9 | abstract: "A long-running server that runs indefinitely until stopped" 10 | ) 11 | 12 | @Option(name: .shortAndLong, help: "Port to listen on (default: 8080)") 13 | var port: Int = 8080 14 | 15 | @Flag(name: .shortAndLong, help: "Enable verbose logging") 16 | var verbose: Bool = false 17 | 18 | @Option(name: .shortAndLong, help: "Auto-shutdown after N seconds (0 = run forever)") 19 | var autoShutdown: Int = 0 20 | 21 | func run() async throws { 22 | let taskManager = TaskManager() 23 | 24 | if verbose { 25 | print("🚀 Starting long-running server...") 26 | print("🌐 Port: \(port)") 27 | if autoShutdown > 0 { 28 | print("⏰ Auto-shutdown: \(autoShutdown) seconds") 29 | } else { 30 | print("♾️ Running indefinitely (use SIGTERM to stop)") 31 | } 32 | } 33 | 34 | // Set up signal handling for graceful shutdown 35 | let signalSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: .main) 36 | signalSource.setEventHandler { 37 | if verbose { 38 | print("\n🛑 Received SIGTERM, shutting down gracefully...") 39 | } 40 | taskManager.stopServer() 41 | } 42 | signalSource.resume() 43 | signal(SIGTERM, SIG_IGN) 44 | 45 | await taskManager.startLongRunningServer( 46 | port: port, 47 | verbose: verbose, 48 | autoShutdown: autoShutdown 49 | ) 50 | } 51 | } ``` -------------------------------------------------------------------------------- /example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/CalculatorDisplay.swift: -------------------------------------------------------------------------------- ```swift 1 | import SwiftUI 2 | 3 | // MARK: - Calculator Display Component 4 | struct CalculatorDisplay: View { 5 | let expressionDisplay: String 6 | let display: String 7 | var onDeleteLastDigit: (() -> Void)? = nil 8 | 9 | var body: some View { 10 | VStack(alignment: .trailing, spacing: 8) { 11 | // Expression display (smaller, secondary) 12 | Text(expressionDisplay) 13 | .font(.title2) 14 | .foregroundColor(.white.opacity(0.7)) 15 | .frame(maxWidth: .infinity, alignment: .trailing) 16 | .lineLimit(1) 17 | .minimumScaleFactor(0.5) 18 | 19 | // Main result display 20 | Text(display) 21 | .font(.system(size: 56, weight: .light, design: .rounded)) 22 | .foregroundColor(.white) 23 | .frame(maxWidth: .infinity, alignment: .trailing) 24 | .lineLimit(1) 25 | .minimumScaleFactor(0.3) 26 | .gesture(DragGesture(minimumDistance: 20, coordinateSpace: .local) 27 | .onEnded { value in 28 | if value.translation.width < -20 || value.translation.width > 20 { 29 | onDeleteLastDigit?() 30 | } 31 | } 32 | ) 33 | } 34 | .padding(.horizontal, 24) 35 | .padding(.bottom, 30) 36 | .frame(height: 140) 37 | } 38 | } 39 | 40 | struct CalculatorDisplay_Previews: PreviewProvider { 41 | static var previews: some View { 42 | CalculatorDisplay(expressionDisplay: "12 + 7", display: "19", onDeleteLastDigit: nil) 43 | .background(Color.black) 44 | .previewLayout(.sizeThatFits) 45 | } 46 | } 47 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/logging/stop_sim_log_cap.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Logging Plugin: Stop Simulator Log Capture 3 | * 4 | * Stops an active simulator log capture session and returns the captured logs. 5 | */ 6 | 7 | import { z } from 'zod'; 8 | import { stopLogCapture as _stopLogCapture } from '../../../utils/log-capture/index.ts'; 9 | import { ToolResponse, createTextContent } from '../../../types/common.ts'; 10 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; 11 | import { getDefaultCommandExecutor } from '../../../utils/command.ts'; 12 | 13 | // Define schema as ZodObject 14 | const stopSimLogCapSchema = z.object({ 15 | logSessionId: z.string().describe('The session ID returned by start_sim_log_cap.'), 16 | }); 17 | 18 | // Use z.infer for type safety 19 | type StopSimLogCapParams = z.infer<typeof stopSimLogCapSchema>; 20 | 21 | /** 22 | * Business logic for stopping simulator log capture session 23 | */ 24 | export async function stop_sim_log_capLogic(params: StopSimLogCapParams): Promise<ToolResponse> { 25 | const { logContent, error } = await _stopLogCapture(params.logSessionId); 26 | if (error) { 27 | return { 28 | content: [ 29 | createTextContent(`Error stopping log capture session ${params.logSessionId}: ${error}`), 30 | ], 31 | isError: true, 32 | }; 33 | } 34 | return { 35 | content: [ 36 | createTextContent( 37 | `Log capture session ${params.logSessionId} stopped successfully. Log content follows:\n\n${logContent}`, 38 | ), 39 | ], 40 | }; 41 | } 42 | 43 | export default { 44 | name: 'stop_sim_log_cap', 45 | description: 'Stops an active simulator log capture session and returns the captured logs.', 46 | schema: stopSimLogCapSchema.shape, // MCP SDK compatibility 47 | handler: createTypedTool(stopSimLogCapSchema, stop_sim_log_capLogic, getDefaultCommandExecutor), 48 | }; 49 | ``` -------------------------------------------------------------------------------- /src/doctor-cli.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * XcodeBuildMCP Doctor CLI 5 | * 6 | * This standalone script runs the doctor tool and outputs the results 7 | * to the console. It's designed to be run directly via npx or mise. 8 | */ 9 | 10 | import { version } from './version.ts'; 11 | import { doctorLogic } from './mcp/tools/doctor/doctor.ts'; 12 | import { getDefaultCommandExecutor } from './utils/execution/index.ts'; 13 | 14 | async function runDoctor(): Promise<void> { 15 | try { 16 | // Using console.error to avoid linting issues as it's allowed by the project's linting rules 17 | console.error(`Running XcodeBuildMCP Doctor (v${version})...`); 18 | console.error('Collecting system information and checking dependencies...\n'); 19 | 20 | // Run the doctor tool logic directly with CLI flag enabled 21 | const executor = getDefaultCommandExecutor(); 22 | const result = await doctorLogic({}, executor, true); // showAsciiLogo = true for CLI 23 | 24 | // Output the doctor information 25 | if (result.content && result.content.length > 0) { 26 | const textContent = result.content.find((item) => item.type === 'text'); 27 | if (textContent && textContent.type === 'text') { 28 | // eslint-disable-next-line no-console 29 | console.log(textContent.text); 30 | } else { 31 | console.error('Error: Unexpected doctor result format'); 32 | } 33 | } else { 34 | console.error('Error: No doctor information returned'); 35 | } 36 | 37 | console.error('\nDoctor run complete. Please include this output when reporting issues.'); 38 | } catch (error) { 39 | console.error('Error running doctor:', error); 40 | process.exit(1); 41 | } 42 | } 43 | 44 | // Run the doctor 45 | runDoctor().catch((error) => { 46 | console.error('Unhandled exception:', error); 47 | process.exit(1); 48 | }); 49 | ``` -------------------------------------------------------------------------------- /example_projects/iOS/MCPTestUITests/MCPTestUITests.swift: -------------------------------------------------------------------------------- ```swift 1 | import XCTest 2 | 3 | /// Reproduction tests for TEST_RUNNER_ environment variable passthrough. 4 | /// GitHub Issue: https://github.com/cameroncooke/XcodeBuildMCP/issues/101 5 | /// 6 | /// Expected behavior: 7 | /// - When invoking xcodebuild test with TEST_RUNNER_USE_DEV_MODE=YES, 8 | /// the test runner environment should contain USE_DEV_MODE=YES 9 | /// (the TEST_RUNNER_ prefix is stripped by xcodebuild). 10 | /// 11 | /// Current behavior (before implementation in Node layer): 12 | /// - Running via XcodeBuildMCP test tools does not yet pass TEST_RUNNER_ 13 | /// variables through, so this test will fail and serve as a repro. 14 | final class MCPTestUITests: XCTestCase { 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | /// Verifies that USE_DEV_MODE=YES is present in the test runner environment. 21 | /// This proves TEST_RUNNER_USE_DEV_MODE=YES was passed to xcodebuild. 22 | func testEnvironmentVariablePassthrough() throws { 23 | let env = ProcessInfo.processInfo.environment 24 | let value = env["USE_DEV_MODE"] ?? "<nil>" 25 | XCTAssertEqual( 26 | value, 27 | "YES", 28 | "Expected USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE. Actual: \(value)" 29 | ) 30 | } 31 | 32 | /// Example of how a project might use the env var to alter behavior in dev mode. 33 | /// This does not change test runner configuration; it simply demonstrates conditional logic. 34 | func testDevModeBehaviorPlaceholder() throws { 35 | let isDevMode = ProcessInfo.processInfo.environment["USE_DEV_MODE"] == "YES" 36 | if isDevMode { 37 | XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs") 38 | } 39 | XCTAssertTrue(true) 40 | } 41 | } ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator-management/__tests__/index.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tests for simulator-management workflow metadata 3 | */ 4 | import { describe, it, expect } from 'vitest'; 5 | import { workflow } from '../index.ts'; 6 | 7 | describe('simulator-management workflow metadata', () => { 8 | describe('Workflow Structure', () => { 9 | it('should export workflow object with required properties', () => { 10 | expect(workflow).toHaveProperty('name'); 11 | expect(workflow).toHaveProperty('description'); 12 | expect(workflow).toHaveProperty('platforms'); 13 | expect(workflow).toHaveProperty('targets'); 14 | expect(workflow).toHaveProperty('projectTypes'); 15 | expect(workflow).toHaveProperty('capabilities'); 16 | }); 17 | 18 | it('should have correct workflow name', () => { 19 | expect(workflow.name).toBe('Simulator Management'); 20 | }); 21 | 22 | it('should have correct description', () => { 23 | expect(workflow.description).toBe( 24 | '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.', 25 | ); 26 | }); 27 | 28 | it('should have correct platforms array', () => { 29 | expect(workflow.platforms).toEqual(['iOS']); 30 | }); 31 | 32 | it('should have correct targets array', () => { 33 | expect(workflow.targets).toEqual(['simulator']); 34 | }); 35 | 36 | it('should have correct projectTypes array', () => { 37 | expect(workflow.projectTypes).toEqual(['project', 'workspace']); 38 | }); 39 | 40 | it('should have correct capabilities array', () => { 41 | expect(workflow.capabilities).toEqual([ 42 | 'boot', 43 | 'open', 44 | 'list', 45 | 'appearance', 46 | 'location', 47 | 'network', 48 | 'statusbar', 49 | 'erase', 50 | ]); 51 | }); 52 | }); 53 | }); 54 | ``` -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Server Configuration - MCP Server setup and lifecycle management 3 | * 4 | * This module handles the creation, configuration, and lifecycle management of the 5 | * Model Context Protocol (MCP) server. It provides the foundation for all tool 6 | * registrations and server capabilities. 7 | * 8 | * Responsibilities: 9 | * - Creating and configuring the MCP server instance 10 | * - Setting up server capabilities and options 11 | * - Managing server lifecycle (start/stop) 12 | * - Handling transport configuration (stdio) 13 | */ 14 | 15 | import { McpServer } from '@camsoft/mcp-sdk/server/mcp.js'; 16 | import { StdioServerTransport } from '@camsoft/mcp-sdk/server/stdio.js'; 17 | import { log } from '../utils/logger.ts'; 18 | import { version } from '../version.ts'; 19 | import * as Sentry from '@sentry/node'; 20 | 21 | /** 22 | * Create and configure the MCP server 23 | * @returns Configured MCP server instance 24 | */ 25 | export function createServer(): McpServer { 26 | // Create server instance 27 | const baseServer = new McpServer( 28 | { 29 | name: 'xcodebuildmcp', 30 | version, 31 | }, 32 | { 33 | capabilities: { 34 | tools: { 35 | listChanged: true, 36 | }, 37 | resources: { 38 | subscribe: true, 39 | listChanged: true, 40 | }, 41 | logging: {}, 42 | }, 43 | }, 44 | ); 45 | 46 | // Wrap server with Sentry for MCP instrumentation 47 | const server = Sentry.wrapMcpServerWithSentry(baseServer); 48 | 49 | // Log server initialization 50 | log('info', `Server initialized with Sentry MCP instrumentation (version ${version})`); 51 | 52 | return server; 53 | } 54 | 55 | /** 56 | * Start the MCP server with stdio transport 57 | * @param server The MCP server instance to start 58 | */ 59 | export async function startServer(server: McpServer): Promise<void> { 60 | const transport = new StdioServerTransport(); 61 | await server.connect(transport); 62 | log('info', 'XcodeBuildMCP Server running on stdio'); 63 | } 64 | ``` -------------------------------------------------------------------------------- /src/mcp/resources/devices.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Devices Resource Plugin 3 | * 4 | * Provides access to connected Apple devices through MCP resource system. 5 | * This resource reuses the existing list_devices tool logic to maintain consistency. 6 | */ 7 | 8 | import { log } from '../../utils/logging/index.ts'; 9 | import type { CommandExecutor } from '../../utils/execution/index.ts'; 10 | import { getDefaultCommandExecutor } from '../../utils/execution/index.ts'; 11 | import { list_devicesLogic } from '../tools/device/list_devices.ts'; 12 | 13 | // Testable resource logic separated from MCP handler 14 | export async function devicesResourceLogic( 15 | executor: CommandExecutor = getDefaultCommandExecutor(), 16 | ): Promise<{ contents: Array<{ text: string }> }> { 17 | try { 18 | log('info', 'Processing devices resource request'); 19 | const result = await list_devicesLogic({}, executor); 20 | 21 | if (result.isError) { 22 | const errorText = result.content[0]?.text; 23 | throw new Error(typeof errorText === 'string' ? errorText : 'Failed to retrieve device data'); 24 | } 25 | 26 | return { 27 | contents: [ 28 | { 29 | text: 30 | typeof result.content[0]?.text === 'string' 31 | ? result.content[0].text 32 | : 'No device data available', 33 | }, 34 | ], 35 | }; 36 | } catch (error) { 37 | const errorMessage = error instanceof Error ? error.message : String(error); 38 | log('error', `Error in devices resource handler: ${errorMessage}`); 39 | 40 | return { 41 | contents: [ 42 | { 43 | text: `Error retrieving device data: ${errorMessage}`, 44 | }, 45 | ], 46 | }; 47 | } 48 | } 49 | 50 | export default { 51 | uri: 'xcodebuildmcp://devices', 52 | name: 'devices', 53 | description: 'Connected physical Apple devices with their UUIDs, names, and connection status', 54 | mimeType: 'text/plain', 55 | async handler(): Promise<{ contents: Array<{ text: string }> }> { 56 | return devicesResourceLogic(); 57 | }, 58 | }; 59 | ``` -------------------------------------------------------------------------------- /src/mcp/resources/simulators.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simulator Resource Plugin 3 | * 4 | * Provides access to available iOS simulators through MCP resource system. 5 | * This resource reuses the existing list_sims tool logic to maintain consistency. 6 | */ 7 | 8 | import { log } from '../../utils/logging/index.ts'; 9 | import { getDefaultCommandExecutor } from '../../utils/execution/index.ts'; 10 | import type { CommandExecutor } from '../../utils/execution/index.ts'; 11 | import { list_simsLogic } from '../tools/simulator/list_sims.ts'; 12 | 13 | // Testable resource logic separated from MCP handler 14 | export async function simulatorsResourceLogic( 15 | executor: CommandExecutor = getDefaultCommandExecutor(), 16 | ): Promise<{ contents: Array<{ text: string }> }> { 17 | try { 18 | log('info', 'Processing simulators resource request'); 19 | const result = await list_simsLogic({}, executor); 20 | 21 | if (result.isError) { 22 | const errorText = result.content[0]?.text; 23 | throw new Error( 24 | typeof errorText === 'string' ? errorText : 'Failed to retrieve simulator data', 25 | ); 26 | } 27 | 28 | return { 29 | contents: [ 30 | { 31 | text: 32 | typeof result.content[0]?.text === 'string' 33 | ? result.content[0].text 34 | : 'No simulator data available', 35 | }, 36 | ], 37 | }; 38 | } catch (error) { 39 | const errorMessage = error instanceof Error ? error.message : String(error); 40 | log('error', `Error in simulators resource handler: ${errorMessage}`); 41 | 42 | return { 43 | contents: [ 44 | { 45 | text: `Error retrieving simulator data: ${errorMessage}`, 46 | }, 47 | ], 48 | }; 49 | } 50 | } 51 | 52 | export default { 53 | uri: 'xcodebuildmcp://simulators', 54 | name: 'simulators', 55 | description: 'Available iOS simulators with their UUIDs and states', 56 | mimeType: 'text/plain', 57 | async handler(): Promise<{ contents: Array<{ text: string }> }> { 58 | return simulatorsResourceLogic(); 59 | }, 60 | }; 61 | ``` -------------------------------------------------------------------------------- /src/mcp/resources/doctor.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Doctor Resource Plugin 3 | * 4 | * Provides access to development environment doctor information through MCP resource system. 5 | * This resource reuses the existing doctor tool logic to maintain consistency. 6 | */ 7 | 8 | import { log } from '../../utils/logging/index.ts'; 9 | import { getDefaultCommandExecutor, CommandExecutor } from '../../utils/execution/index.ts'; 10 | import { doctorLogic } from '../tools/doctor/doctor.ts'; 11 | 12 | // Testable resource logic separated from MCP handler 13 | export async function doctorResourceLogic( 14 | executor: CommandExecutor = getDefaultCommandExecutor(), 15 | ): Promise<{ contents: Array<{ text: string }> }> { 16 | try { 17 | log('info', 'Processing doctor resource request'); 18 | const result = await doctorLogic({}, executor); 19 | 20 | if (result.isError) { 21 | const textItem = result.content.find((i) => i.type === 'text') as 22 | | { type: 'text'; text: string } 23 | | undefined; 24 | const errorText = textItem?.text; 25 | const errorMessage = 26 | typeof errorText === 'string' ? errorText : 'Failed to retrieve doctor data'; 27 | log('error', `Error in doctor resource handler: ${errorMessage}`); 28 | return { 29 | contents: [ 30 | { 31 | text: `Error retrieving doctor data: ${errorMessage}`, 32 | }, 33 | ], 34 | }; 35 | } 36 | 37 | const okTextItem = result.content.find((i) => i.type === 'text') as 38 | | { type: 'text'; text: string } 39 | | undefined; 40 | return { 41 | contents: [ 42 | { 43 | text: okTextItem?.text ?? 'No doctor data available', 44 | }, 45 | ], 46 | }; 47 | } catch (error) { 48 | const errorMessage = error instanceof Error ? error.message : String(error); 49 | log('error', `Error in doctor resource handler: ${errorMessage}`); 50 | 51 | return { 52 | contents: [ 53 | { 54 | text: `Error retrieving doctor data: ${errorMessage}`, 55 | }, 56 | ], 57 | }; 58 | } 59 | } 60 | 61 | export default { 62 | uri: 'xcodebuildmcp://doctor', 63 | name: 'doctor', 64 | description: 65 | 'Comprehensive development environment diagnostic information and configuration status', 66 | mimeType: 'text/plain', 67 | async handler(): Promise<{ contents: Array<{ text: string }> }> { 68 | return doctorResourceLogic(); 69 | }, 70 | }; 71 | ``` -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", 3 | "name": "com.xcodebuildmcp/XcodeBuildMCP", 4 | "description": "XcodeBuildMCP provides tools for Xcode project management, simulator management, and app utilities.", 5 | "status": "active", 6 | "repository": { 7 | "url": "https://github.com/cameroncooke/XcodeBuildMCP", 8 | "source": "github", 9 | "id": "945551361" 10 | }, 11 | "version": "1.14.1", 12 | "packages": [ 13 | { 14 | "registryType": "npm", 15 | "registryBaseUrl": "https://registry.npmjs.org", 16 | "identifier": "xcodebuildmcp", 17 | "version": "1.14.1", 18 | "transport": { 19 | "type": "stdio" 20 | }, 21 | "runtimeHint": "npx", 22 | "environmentVariables": [ 23 | { 24 | "name": "INCREMENTAL_BUILDS_ENABLED", 25 | "description": "Enable experimental xcodemake incremental builds (true/false or 1/0).", 26 | "format": "boolean", 27 | "default": "false", 28 | "choices": [ 29 | "true", 30 | "false", 31 | "1", 32 | "0" 33 | ] 34 | }, 35 | { 36 | "name": "XCODEBUILDMCP_DYNAMIC_TOOLS", 37 | "description": "Enable AI-powered dynamic tool discovery to load only relevant workflows.", 38 | "format": "boolean", 39 | "default": "false", 40 | "choices": [ 41 | "true", 42 | "false" 43 | ] 44 | }, 45 | { 46 | "name": "XCODEBUILDMCP_ENABLED_WORKFLOWS", 47 | "description": "Comma-separated list of workflows to load in Static Mode (e.g., 'simulator,device,project-discovery').", 48 | "format": "string", 49 | "default": "" 50 | }, 51 | { 52 | "name": "XCODEBUILDMCP_SENTRY_DISABLED", 53 | "description": "Disable Sentry error reporting (preferred flag).", 54 | "format": "boolean", 55 | "default": "false", 56 | "choices": [ 57 | "true", 58 | "false" 59 | ] 60 | }, 61 | { 62 | "name": "XCODEBUILDMCP_DEBUG", 63 | "description": "Enable verbose debug logging from the server.", 64 | "format": "boolean", 65 | "default": "false", 66 | "choices": [ 67 | "true", 68 | "false" 69 | ] 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | ``` -------------------------------------------------------------------------------- /example_projects/.vscode/launch.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "configurations": [ 3 | { 4 | "type": "swift", 5 | "request": "launch", 6 | "args": [], 7 | "cwd": "${workspaceFolder:example_projects}/spm", 8 | "name": "Debug spm (spm)", 9 | "program": "${workspaceFolder:example_projects}/spm/.build/debug/spm", 10 | "preLaunchTask": "swift: Build Debug spm (spm)" 11 | }, 12 | { 13 | "type": "swift", 14 | "request": "launch", 15 | "args": [], 16 | "cwd": "${workspaceFolder:example_projects}/spm", 17 | "name": "Release spm (spm)", 18 | "program": "${workspaceFolder:example_projects}/spm/.build/release/spm", 19 | "preLaunchTask": "swift: Build Release spm (spm)" 20 | }, 21 | { 22 | "type": "swift", 23 | "request": "launch", 24 | "args": [], 25 | "cwd": "${workspaceFolder:example_projects}/spm", 26 | "name": "Debug quick-task (spm)", 27 | "program": "${workspaceFolder:example_projects}/spm/.build/debug/quick-task", 28 | "preLaunchTask": "swift: Build Debug quick-task (spm)" 29 | }, 30 | { 31 | "type": "swift", 32 | "request": "launch", 33 | "args": [], 34 | "cwd": "${workspaceFolder:example_projects}/spm", 35 | "name": "Release quick-task (spm)", 36 | "program": "${workspaceFolder:example_projects}/spm/.build/release/quick-task", 37 | "preLaunchTask": "swift: Build Release quick-task (spm)" 38 | }, 39 | { 40 | "type": "swift", 41 | "request": "launch", 42 | "args": [], 43 | "cwd": "${workspaceFolder:example_projects}/spm", 44 | "name": "Debug long-server (spm)", 45 | "program": "${workspaceFolder:example_projects}/spm/.build/debug/long-server", 46 | "preLaunchTask": "swift: Build Debug long-server (spm)" 47 | }, 48 | { 49 | "type": "swift", 50 | "request": "launch", 51 | "args": [], 52 | "cwd": "${workspaceFolder:example_projects}/spm", 53 | "name": "Release long-server (spm)", 54 | "program": "${workspaceFolder:example_projects}/spm/.build/release/long-server", 55 | "preLaunchTask": "swift: Build Release long-server (spm)" 56 | } 57 | ] 58 | } ``` -------------------------------------------------------------------------------- /src/mcp/tools/swift-package/swift_package_clean.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import path from 'node:path'; 3 | import type { CommandExecutor } from '../../../utils/execution/index.ts'; 4 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; 5 | import { createErrorResponse } from '../../../utils/responses/index.ts'; 6 | import { log } from '../../../utils/logging/index.ts'; 7 | import { ToolResponse } from '../../../types/common.ts'; 8 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; 9 | 10 | // Define schema as ZodObject 11 | const swiftPackageCleanSchema = z.object({ 12 | packagePath: z.string().describe('Path to the Swift package root (Required)'), 13 | }); 14 | 15 | // Use z.infer for type safety 16 | type SwiftPackageCleanParams = z.infer<typeof swiftPackageCleanSchema>; 17 | 18 | export async function swift_package_cleanLogic( 19 | params: SwiftPackageCleanParams, 20 | executor: CommandExecutor, 21 | ): Promise<ToolResponse> { 22 | const resolvedPath = path.resolve(params.packagePath); 23 | const swiftArgs = ['package', '--package-path', resolvedPath, 'clean']; 24 | 25 | log('info', `Running swift ${swiftArgs.join(' ')}`); 26 | try { 27 | const result = await executor(['swift', ...swiftArgs], 'Swift Package Clean', true, undefined); 28 | if (!result.success) { 29 | const errorMessage = result.error ?? result.output ?? 'Unknown error'; 30 | return createErrorResponse('Swift package clean failed', errorMessage); 31 | } 32 | 33 | return { 34 | content: [ 35 | { type: 'text', text: '✅ Swift package cleaned successfully.' }, 36 | { 37 | type: 'text', 38 | text: '💡 Build artifacts and derived data removed. Ready for fresh build.', 39 | }, 40 | { type: 'text', text: result.output || '(clean completed silently)' }, 41 | ], 42 | isError: false, 43 | }; 44 | } catch (error) { 45 | const message = error instanceof Error ? error.message : String(error); 46 | log('error', `Swift package clean failed: ${message}`); 47 | return createErrorResponse('Failed to execute swift package clean', message); 48 | } 49 | } 50 | 51 | export default { 52 | name: 'swift_package_clean', 53 | description: 'Cleans Swift Package build artifacts and derived data', 54 | schema: swiftPackageCleanSchema.shape, // MCP SDK compatibility 55 | handler: createTypedTool( 56 | swiftPackageCleanSchema, 57 | swift_package_cleanLogic, 58 | getDefaultCommandExecutor, 59 | ), 60 | }; 61 | ``` -------------------------------------------------------------------------------- /example_projects/spm/Sources/TestLib/TaskManager.swift: -------------------------------------------------------------------------------- ```swift 1 | import Foundation 2 | 3 | public class TaskManager { 4 | private var isServerRunning = false 5 | 6 | public init() {} 7 | 8 | public func executeQuickTask(name: String, duration: Int, verbose: Bool) async { 9 | if verbose { 10 | print("📝 Task '\(name)' started at \(Date())") 11 | } 12 | 13 | // Simulate work with periodic output using Swift Concurrency 14 | for i in 1...duration { 15 | if verbose { 16 | print("⚙️ Working... step \(i)/\(duration)") 17 | } 18 | try? await Task.sleep(for: .seconds(1)) 19 | } 20 | 21 | if verbose { 22 | print("🎉 Task '\(name)' completed at \(Date())") 23 | } else { 24 | print("Task '\(name)' completed in \(duration)s") 25 | } 26 | } 27 | 28 | public func startLongRunningServer(port: Int, verbose: Bool, autoShutdown: Int) async { 29 | if verbose { 30 | print("🔧 Initializing server on port \(port)...") 31 | } 32 | 33 | var secondsRunning = 0 34 | let startTime = Date() 35 | isServerRunning = true 36 | 37 | // Simulate server startup 38 | try? await Task.sleep(for: .milliseconds(500)) 39 | print("✅ Server running on port \(port)") 40 | 41 | // Main server loop using Swift Concurrency 42 | while isServerRunning { 43 | try? await Task.sleep(for: .seconds(1)) 44 | secondsRunning += 1 45 | 46 | if verbose && secondsRunning % 5 == 0 { 47 | print("📊 Server heartbeat: \(secondsRunning)s uptime") 48 | } 49 | 50 | // Handle auto-shutdown 51 | if autoShutdown > 0 && secondsRunning >= autoShutdown { 52 | if verbose { 53 | print("⏰ Auto-shutdown triggered after \(autoShutdown)s") 54 | } 55 | break 56 | } 57 | } 58 | 59 | let uptime = Date().timeIntervalSince(startTime) 60 | print("🛑 Server stopped after \(String(format: "%.1f", uptime))s uptime") 61 | isServerRunning = false 62 | } 63 | 64 | public func stopServer() { 65 | isServerRunning = false 66 | } 67 | 68 | public func calculateSum(_ a: Int, _ b: Int) -> Int { 69 | return a + b 70 | } 71 | 72 | public func validateInput(_ input: String) -> Bool { 73 | return !input.isEmpty && input.count <= 100 74 | } 75 | } ``` -------------------------------------------------------------------------------- /src/mcp/tools/session-management/__tests__/index.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tests for session-management workflow metadata 3 | */ 4 | import { describe, it, expect } from 'vitest'; 5 | import { workflow } from '../index.ts'; 6 | 7 | describe('session-management workflow metadata', () => { 8 | describe('Workflow Structure', () => { 9 | it('should export workflow object with required properties', () => { 10 | expect(workflow).toHaveProperty('name'); 11 | expect(workflow).toHaveProperty('description'); 12 | expect(workflow).toHaveProperty('platforms'); 13 | expect(workflow).toHaveProperty('targets'); 14 | expect(workflow).toHaveProperty('capabilities'); 15 | }); 16 | 17 | it('should have correct workflow name', () => { 18 | expect(workflow.name).toBe('session-management'); 19 | }); 20 | 21 | it('should have correct description', () => { 22 | expect(workflow.description).toBe( 23 | 'Manage session defaults for projectPath/workspacePath, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS and arch. These defaults are required by many tools and must be set before attempting to call tools that would depend on these values.', 24 | ); 25 | }); 26 | 27 | it('should have correct platforms array', () => { 28 | expect(workflow.platforms).toEqual(['iOS', 'macOS', 'tvOS', 'watchOS', 'visionOS']); 29 | }); 30 | 31 | it('should have correct targets array', () => { 32 | expect(workflow.targets).toEqual(['simulator', 'device']); 33 | }); 34 | 35 | it('should have correct capabilities array', () => { 36 | expect(workflow.capabilities).toEqual(['configuration', 'state-management']); 37 | }); 38 | }); 39 | 40 | describe('Workflow Validation', () => { 41 | it('should have valid string properties', () => { 42 | expect(typeof workflow.name).toBe('string'); 43 | expect(typeof workflow.description).toBe('string'); 44 | expect(workflow.name.length).toBeGreaterThan(0); 45 | expect(workflow.description.length).toBeGreaterThan(0); 46 | }); 47 | 48 | it('should have valid array properties', () => { 49 | expect(Array.isArray(workflow.platforms)).toBe(true); 50 | expect(Array.isArray(workflow.targets)).toBe(true); 51 | expect(Array.isArray(workflow.capabilities)).toBe(true); 52 | 53 | expect(workflow.platforms.length).toBeGreaterThan(0); 54 | expect(workflow.targets.length).toBeGreaterThan(0); 55 | expect(workflow.capabilities.length).toBeGreaterThan(0); 56 | }); 57 | }); 58 | }); 59 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/logging/start_sim_log_cap.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Logging Plugin: Start Simulator Log Capture 3 | * 4 | * Starts capturing logs from a specified simulator. 5 | */ 6 | 7 | import { z } from 'zod'; 8 | import { startLogCapture } from '../../../utils/log-capture/index.ts'; 9 | import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.ts'; 10 | import { ToolResponse, createTextContent } from '../../../types/common.ts'; 11 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; 12 | 13 | // Define schema as ZodObject 14 | const startSimLogCapSchema = z.object({ 15 | simulatorUuid: z 16 | .string() 17 | .describe('UUID of the simulator to capture logs from (obtained from list_simulators).'), 18 | bundleId: z.string().describe('Bundle identifier of the app to capture logs for.'), 19 | captureConsole: z 20 | .boolean() 21 | .optional() 22 | .describe('Whether to capture console output (requires app relaunch).'), 23 | }); 24 | 25 | // Use z.infer for type safety 26 | type StartSimLogCapParams = z.infer<typeof startSimLogCapSchema>; 27 | 28 | export async function start_sim_log_capLogic( 29 | params: StartSimLogCapParams, 30 | _executor: CommandExecutor = getDefaultCommandExecutor(), 31 | logCaptureFunction: typeof startLogCapture = startLogCapture, 32 | ): Promise<ToolResponse> { 33 | const paramsWithDefaults = { 34 | ...params, 35 | captureConsole: params.captureConsole ?? false, 36 | }; 37 | const { sessionId, error } = await logCaptureFunction(paramsWithDefaults, _executor); 38 | if (error) { 39 | return { 40 | content: [createTextContent(`Error starting log capture: ${error}`)], 41 | isError: true, 42 | }; 43 | } 44 | return { 45 | content: [ 46 | createTextContent( 47 | `Log capture started successfully. Session ID: ${sessionId}.\n\n${paramsWithDefaults.captureConsole ? 'Note: Your app was relaunched to capture console output.' : 'Note: Only structured logs are being captured.'}\n\nNext Steps:\n1. Interact with your simulator and app.\n2. Use 'stop_sim_log_cap' with session ID '${sessionId}' to stop capture and retrieve logs.`, 48 | ), 49 | ], 50 | }; 51 | } 52 | 53 | export default { 54 | name: 'start_sim_log_cap', 55 | description: 56 | 'Starts capturing logs from a specified simulator. Returns a session ID. By default, captures only structured logs.', 57 | schema: startSimLogCapSchema.shape, // MCP SDK compatibility 58 | handler: createTypedTool(startSimLogCapSchema, start_sim_log_capLogic, getDefaultCommandExecutor), 59 | }; 60 | ``` -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Claude Code 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | pull-requests: write 24 | issues: write 25 | id-token: write 26 | actions: read # Required for Claude to read CI results on PRs 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 1 32 | 33 | - name: Run Claude Code 34 | id: claude 35 | uses: anthropics/claude-code-action@beta 36 | with: 37 | claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 38 | 39 | # This is an optional setting that allows Claude to read CI results on PRs 40 | additional_permissions: | 41 | actions: read 42 | 43 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 44 | # model: "claude-opus-4-20250514" 45 | 46 | # Optional: Customize the trigger phrase (default: @claude) 47 | # trigger_phrase: "/claude" 48 | 49 | # Optional: Trigger when specific user is assigned to an issue 50 | assignee_trigger: "claude-bot" 51 | 52 | # Optional: Allow Claude to run specific commands 53 | allowed_tools: "Bash(npm install),Bash(npm run build:*),Bash(npm run test:*),Bash(npm run lint:*),Bash(npm run format:*),Bash(npm run doctor)" 54 | 55 | # Optional: Add custom instructions for Claude to customize its behavior for your project 56 | # custom_instructions: | 57 | # Follow our coding standards 58 | # Ensure all new code has tests 59 | # Use TypeScript for new files 60 | 61 | # Optional: Custom environment variables for Claude 62 | # claude_env: | 63 | # NODE_ENV: test 64 | 65 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/session-management/session_set_defaults.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import { sessionStore, type SessionDefaults } from '../../../utils/session-store.ts'; 3 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; 4 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; 5 | import type { ToolResponse } from '../../../types/common.ts'; 6 | 7 | const baseSchema = z.object({ 8 | projectPath: z.string().optional(), 9 | workspacePath: z.string().optional(), 10 | scheme: z.string().optional(), 11 | configuration: z.string().optional(), 12 | simulatorName: z.string().optional(), 13 | simulatorId: z.string().optional(), 14 | deviceId: z.string().optional(), 15 | useLatestOS: z.boolean().optional(), 16 | arch: z.enum(['arm64', 'x86_64']).optional(), 17 | }); 18 | 19 | const schemaObj = baseSchema 20 | .refine((v) => !(v.projectPath && v.workspacePath), { 21 | message: 'projectPath and workspacePath are mutually exclusive', 22 | path: ['projectPath'], 23 | }) 24 | .refine((v) => !(v.simulatorId && v.simulatorName), { 25 | message: 'simulatorId and simulatorName are mutually exclusive', 26 | path: ['simulatorId'], 27 | }); 28 | 29 | type Params = z.infer<typeof schemaObj>; 30 | 31 | export async function sessionSetDefaultsLogic(params: Params): Promise<ToolResponse> { 32 | // Clear mutually exclusive counterparts before merging new defaults 33 | const toClear = new Set<keyof SessionDefaults>(); 34 | if (Object.prototype.hasOwnProperty.call(params, 'projectPath')) toClear.add('workspacePath'); 35 | if (Object.prototype.hasOwnProperty.call(params, 'workspacePath')) toClear.add('projectPath'); 36 | if (Object.prototype.hasOwnProperty.call(params, 'simulatorId')) toClear.add('simulatorName'); 37 | if (Object.prototype.hasOwnProperty.call(params, 'simulatorName')) toClear.add('simulatorId'); 38 | 39 | if (toClear.size > 0) { 40 | sessionStore.clear(Array.from(toClear)); 41 | } 42 | 43 | sessionStore.setDefaults(params as Partial<SessionDefaults>); 44 | const current = sessionStore.getAll(); 45 | return { 46 | content: [{ type: 'text', text: `Defaults updated:\n${JSON.stringify(current, null, 2)}` }], 47 | isError: false, 48 | }; 49 | } 50 | 51 | export default { 52 | name: 'session-set-defaults', 53 | description: 54 | 'Set the session defaults needed by many tools. Most tools require one or more session defaults to be set before they can be used. Agents should set the relevant defaults at the beginning of a session.', 55 | schema: baseSchema.shape, 56 | handler: createTypedTool(schemaObj, sessionSetDefaultsLogic, getDefaultCommandExecutor), 57 | }; 58 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/boot_sim.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import { ToolResponse } from '../../../types/common.ts'; 3 | import { log } from '../../../utils/logging/index.ts'; 4 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; 5 | import type { CommandExecutor } from '../../../utils/execution/index.ts'; 6 | import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; 7 | 8 | const bootSimSchemaObject = z.object({ 9 | simulatorId: z.string().describe('UUID of the simulator to boot'), 10 | }); 11 | 12 | // Use z.infer for type safety 13 | type BootSimParams = z.infer<typeof bootSimSchemaObject>; 14 | 15 | const publicSchemaObject = bootSimSchemaObject.omit({ 16 | simulatorId: true, 17 | } as const); 18 | 19 | export async function boot_simLogic( 20 | params: BootSimParams, 21 | executor: CommandExecutor, 22 | ): Promise<ToolResponse> { 23 | log('info', `Starting xcrun simctl boot request for simulator ${params.simulatorId}`); 24 | 25 | try { 26 | const command = ['xcrun', 'simctl', 'boot', params.simulatorId]; 27 | const result = await executor(command, 'Boot Simulator', true); 28 | 29 | if (!result.success) { 30 | return { 31 | content: [ 32 | { 33 | type: 'text', 34 | text: `Boot simulator operation failed: ${result.error}`, 35 | }, 36 | ], 37 | }; 38 | } 39 | 40 | return { 41 | content: [ 42 | { 43 | type: 'text', 44 | text: `✅ Simulator booted successfully. To make it visible, use: open_sim() 45 | 46 | Next steps: 47 | 1. Open the Simulator app (makes it visible): open_sim() 48 | 2. Install an app: install_app_sim({ simulatorId: "${params.simulatorId}", appPath: "PATH_TO_YOUR_APP" }) 49 | 3. Launch an app: launch_app_sim({ simulatorId: "${params.simulatorId}", bundleId: "YOUR_APP_BUNDLE_ID" })`, 50 | }, 51 | ], 52 | }; 53 | } catch (error) { 54 | const errorMessage = error instanceof Error ? error.message : String(error); 55 | log('error', `Error during boot simulator operation: ${errorMessage}`); 56 | return { 57 | content: [ 58 | { 59 | type: 'text', 60 | text: `Boot simulator operation failed: ${errorMessage}`, 61 | }, 62 | ], 63 | }; 64 | } 65 | } 66 | 67 | export default { 68 | name: 'boot_sim', 69 | description: 'Boots an iOS simulator.', 70 | schema: publicSchemaObject.shape, 71 | handler: createSessionAwareTool<BootSimParams>({ 72 | internalSchema: bootSimSchemaObject, 73 | logicFunction: boot_simLogic, 74 | getExecutor: getDefaultCommandExecutor, 75 | requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }], 76 | }), 77 | }; 78 | ``` -------------------------------------------------------------------------------- /src/mcp/tools/simulator/open_sim.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import { ToolResponse } from '../../../types/common.ts'; 3 | import { log } from '../../../utils/logging/index.ts'; 4 | import type { CommandExecutor } from '../../../utils/execution/index.ts'; 5 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; 6 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; 7 | 8 | // Define schema as ZodObject 9 | const openSimSchema = z.object({}); 10 | 11 | // Use z.infer for type safety 12 | type OpenSimParams = z.infer<typeof openSimSchema>; 13 | 14 | export async function open_simLogic( 15 | params: OpenSimParams, 16 | executor: CommandExecutor, 17 | ): Promise<ToolResponse> { 18 | log('info', 'Starting open simulator request'); 19 | 20 | try { 21 | const command = ['open', '-a', 'Simulator']; 22 | const result = await executor(command, 'Open Simulator', true); 23 | 24 | if (!result.success) { 25 | return { 26 | content: [ 27 | { 28 | type: 'text', 29 | text: `Open simulator operation failed: ${result.error}`, 30 | }, 31 | ], 32 | }; 33 | } 34 | 35 | return { 36 | content: [ 37 | { 38 | type: 'text', 39 | text: `Simulator app opened successfully`, 40 | }, 41 | { 42 | type: 'text', 43 | text: `Next Steps: 44 | 1. Boot a simulator if needed: boot_sim({ simulatorUuid: 'UUID_FROM_LIST_SIMULATORS' }) 45 | 2. Launch your app and interact with it 46 | 3. Log capture options: 47 | - Option 1: Capture structured logs only (app continues running): 48 | start_sim_log_cap({ simulatorUuid: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID' }) 49 | - Option 2: Capture both console and structured logs (app will restart): 50 | start_sim_log_cap({ simulatorUuid: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID', captureConsole: true }) 51 | - Option 3: Launch app with logs in one step: 52 | launch_app_logs_sim({ simulatorUuid: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID' })`, 53 | }, 54 | ], 55 | }; 56 | } catch (error) { 57 | const errorMessage = error instanceof Error ? error.message : String(error); 58 | log('error', `Error during open simulator operation: ${errorMessage}`); 59 | return { 60 | content: [ 61 | { 62 | type: 'text', 63 | text: `Open simulator operation failed: ${errorMessage}`, 64 | }, 65 | ], 66 | }; 67 | } 68 | } 69 | 70 | export default { 71 | name: 'open_sim', 72 | description: 'Opens the iOS Simulator app.', 73 | schema: openSimSchema.shape, // MCP SDK compatibility 74 | handler: createTypedTool(openSimSchema, open_simLogic, getDefaultCommandExecutor), 75 | }; 76 | ```