This is page 1 of 16. 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
│ ├── README.md
│ ├── release.yml
│ ├── sentry.yml
│ └── stale.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
├── docs
│ ├── CONFIGURATION.md
│ ├── DAP_BACKEND_IMPLEMENTATION_PLAN.md
│ ├── DEBUGGING_ARCHITECTURE.md
│ ├── DEMOS.md
│ ├── dev
│ │ ├── ARCHITECTURE.md
│ │ ├── CODE_QUALITY.md
│ │ ├── CONTRIBUTING.md
│ │ ├── ESLINT_TYPE_SAFETY.md
│ │ ├── MANUAL_TESTING.md
│ │ ├── NODEJS_2025.md
│ │ ├── PLUGIN_DEVELOPMENT.md
│ │ ├── README.md
│ │ ├── RELEASE_PROCESS.md
│ │ ├── RELOADEROO_FOR_XCODEBUILDMCP.md
│ │ ├── RELOADEROO_XCODEBUILDMCP_PRIMER.md
│ │ ├── RELOADEROO.md
│ │ ├── session_management_plan.md
│ │ ├── session-aware-migration-todo.md
│ │ ├── SMITHERY.md
│ │ ├── TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md
│ │ ├── TESTING.md
│ │ └── ZOD_MIGRATION_GUIDE.md
│ ├── DEVICE_CODE_SIGNING.md
│ ├── GETTING_STARTED.md
│ ├── investigations
│ │ ├── issue-154-screenshot-downscaling.md
│ │ ├── issue-163.md
│ │ ├── issue-debugger-attach-stopped.md
│ │ └── issue-describe-ui-empty-after-debugger-resume.md
│ ├── OVERVIEW.md
│ ├── PRIVACY.md
│ ├── README.md
│ ├── SESSION_DEFAULTS.md
│ ├── TOOLS.md
│ └── TROUBLESHOOTING.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
│ │ ├── .gitignore
│ │ ├── 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
│ │ └── MCPTestTests
│ │ └── MCPTestTests.swift
│ └── 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
│ ├── generate-loaders.ts
│ ├── generate-version.ts
│ ├── release.sh
│ ├── tools-cli.ts
│ ├── update-tools-docs.ts
│ └── verify-smithery-bundle.sh
├── server.json
├── smithery.config.js
├── smithery.yaml
├── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── resources.test.ts
│ │ ├── generated-plugins.ts
│ │ ├── generated-resources.ts
│ │ ├── plugin-registry.ts
│ │ ├── plugin-types.ts
│ │ └── resources.ts
│ ├── doctor-cli.ts
│ ├── index.ts
│ ├── mcp
│ │ ├── resources
│ │ │ ├── __tests__
│ │ │ │ ├── devices.test.ts
│ │ │ │ ├── doctor.test.ts
│ │ │ │ ├── session-status.test.ts
│ │ │ │ └── simulators.test.ts
│ │ │ ├── devices.ts
│ │ │ ├── doctor.ts
│ │ │ ├── session-status.ts
│ │ │ └── simulators.ts
│ │ └── tools
│ │ ├── debugging
│ │ │ ├── debug_attach_sim.ts
│ │ │ ├── debug_breakpoint_add.ts
│ │ │ ├── debug_breakpoint_remove.ts
│ │ │ ├── debug_continue.ts
│ │ │ ├── debug_detach.ts
│ │ │ ├── debug_lldb_command.ts
│ │ │ ├── debug_stack.ts
│ │ │ ├── debug_variables.ts
│ │ │ └── index.ts
│ │ ├── 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
│ │ ├── 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
│ │ ├── bootstrap.ts
│ │ └── server.ts
│ ├── smithery.ts
│ ├── test-utils
│ │ └── mock-executors.ts
│ ├── types
│ │ └── common.ts
│ ├── utils
│ │ ├── __tests__
│ │ │ ├── build-utils-suppress-warnings.test.ts
│ │ │ ├── build-utils.test.ts
│ │ │ ├── debugger-simctl.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
│ │ │ └── workflow-selection.test.ts
│ │ ├── axe
│ │ │ └── index.ts
│ │ ├── axe-helpers.ts
│ │ ├── build
│ │ │ └── index.ts
│ │ ├── build-utils.ts
│ │ ├── capabilities.ts
│ │ ├── command.ts
│ │ ├── CommandExecutor.ts
│ │ ├── debugger
│ │ │ ├── __tests__
│ │ │ │ └── debugger-manager-dap.test.ts
│ │ │ ├── backends
│ │ │ │ ├── __tests__
│ │ │ │ │ └── dap-backend.test.ts
│ │ │ │ ├── dap-backend.ts
│ │ │ │ ├── DebuggerBackend.ts
│ │ │ │ └── lldb-cli-backend.ts
│ │ │ ├── dap
│ │ │ │ ├── __tests__
│ │ │ │ │ └── transport-framing.test.ts
│ │ │ │ ├── adapter-discovery.ts
│ │ │ │ ├── transport.ts
│ │ │ │ └── types.ts
│ │ │ ├── debugger-manager.ts
│ │ │ ├── index.ts
│ │ │ ├── simctl.ts
│ │ │ ├── tool-context.ts
│ │ │ ├── types.ts
│ │ │ └── ui-automation-guard.ts
│ │ ├── environment.ts
│ │ ├── errors.ts
│ │ ├── execution
│ │ │ ├── index.ts
│ │ │ └── interactive-process.ts
│ │ ├── FileSystemExecutor.ts
│ │ ├── log_capture.ts
│ │ ├── log-capture
│ │ │ ├── device-log-sessions.ts
│ │ │ └── index.ts
│ │ ├── logger.ts
│ │ ├── logging
│ │ │ └── index.ts
│ │ ├── plugin-registry
│ │ │ └── index.ts
│ │ ├── responses
│ │ │ └── index.ts
│ │ ├── runtime-registry.ts
│ │ ├── schema-helpers.ts
│ │ ├── sentry.ts
│ │ ├── session-status.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
│ │ ├── workflow-selection.ts
│ │ ├── xcode.ts
│ │ ├── xcodemake
│ │ │ └── index.ts
│ │ └── xcodemake.ts
│ └── version.ts
├── tsconfig.json
├── tsconfig.test.json
├── tsconfig.tests.json
├── tsup.config.ts
├── vitest.config.ts
└── XcodeBuildMCP.code-workspace
```
# Files
--------------------------------------------------------------------------------
/.axe-version:
--------------------------------------------------------------------------------
```
1 | 1.2.1
2 |
```
--------------------------------------------------------------------------------
/.cursorrules:
--------------------------------------------------------------------------------
```
1 | AGENTS.md
```
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
1 | node_modules
2 | build
3 | dist
4 | coverage
5 | *.json
6 | *.md
7 |
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/.gitignore:
--------------------------------------------------------------------------------
```
1 |
2 | # xcode-build-server files
3 | buildServer.json
4 | .compile
5 |
6 | # Local build artifacts
7 | .build/
8 |
```
--------------------------------------------------------------------------------
/.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 |
14 | # IDE and editor files
15 | .idea/
16 | .vscode/*
17 | !.vscode/mcp.json
18 | !.vscode/launch.json
19 | !.vscode/settings.json
20 | !.vscode/tasks.json
21 | !.vscode/extensions.json
22 | *.swp
23 | *.swo
24 | .DS_Store
25 | .env
26 | .env.local
27 | .env.*.local
28 |
29 | # Logs
30 | logs/
31 | *.log
32 |
33 | # Test coverage
34 | coverage/
35 |
36 | # macOS specific
37 | .DS_Store
38 | .AppleDouble
39 | .LSOverride
40 | Icon
41 | ._*
42 | .DocumentRevisions-V100
43 | .fseventsd
44 | .Spotlight-V100
45 | .TemporaryItems
46 | .Trashes
47 | .VolumeIcon.icns
48 | .com.apple.timemachine.donotpresent
49 |
50 | # Xcode
51 | *.xcodeproj/*
52 | !*.xcodeproj/project.pbxproj
53 | !*.xcodeproj/xcshareddata/
54 | !*.xcworkspace/contents.xcworkspacedata
55 | **/xcshareddata/WorkspaceSettings.xcsettings
56 | *.xcuserstate
57 | project.xcworkspace/
58 | xcuserdata/
59 |
60 | # Debug files
61 | .nyc_output/
62 | *.map
63 |
64 | # Optional npm cache directory
65 | .npm
66 |
67 | # Optional eslint cache
68 | .eslintcache
69 |
70 | # Optional REPL history
71 | .node_repl_history
72 |
73 | # Output of 'npm pack'
74 | *.tgz
75 |
76 | # Yarn Integrity file
77 | .yarn-integrity
78 |
79 | # dotenv environment variable files
80 | .env
81 | .env.test
82 | .env.production
83 |
84 | # parcel-bundler cache
85 | .cache
86 | .parcel-cache
87 |
88 | # Windsurf
89 | .windsurfrules
90 |
91 | # Sentry Config File
92 | .sentryclirc
93 |
94 | # Claude Config File
95 | **/.claude/settings.local.json
96 |
97 | # incremental builds
98 | Makefile
99 | buildServer.json
100 |
101 | # Bundled AXe artifacts (generated during build)
102 | bundled/
103 | .smithery/
104 |
105 | /.mcpregistry_github_token
106 | /.mcpregistry_registry_token
107 | /key.pem
108 | .mcpli
109 | .factory
110 | DerivedData
111 |
```
--------------------------------------------------------------------------------
/.github/workflows/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Test workflow trigger
2 |
```
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # XcodeBuildMCP Documentation
2 |
3 | ## Start here
4 | - [Getting started](GETTING_STARTED.md)
5 | - [Configuration and options](CONFIGURATION.md)
6 | - [Tools reference (all workflows/tools)](TOOLS.md)
7 | - [Troubleshooting and doctor](TROUBLESHOOTING.md)
8 | - [Privacy and telemetry](PRIVACY.md)
9 | - [Demos](DEMOS.md)
10 |
11 | ## User guides
12 | - [Product overview and rationale](OVERVIEW.md)
13 | - [Session defaults and opt-out](SESSION_DEFAULTS.md)
14 | - [Device code signing notes](DEVICE_CODE_SIGNING.md)
15 |
16 | ## Developer docs
17 | - [Developer documentation index](dev/README.md)
18 | - [Contributing guide](dev/CONTRIBUTING.md)
19 | - [Architecture](dev/ARCHITECTURE.md)
20 | - [Testing](dev/TESTING.md)
21 | - [Code quality rules](dev/CODE_QUALITY.md)
22 | - [Plugin development](dev/PLUGIN_DEVELOPMENT.md)
23 | - [Release process](dev/RELEASE_PROCESS.md)
24 |
25 | ## Generated docs
26 | - [TOOLS.md](TOOLS.md) is generated by `scripts/update-tools-docs.ts`.
27 |
```
--------------------------------------------------------------------------------
/docs/dev/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Developer Documentation
2 |
3 | ## Getting started for contributors
4 | - [Contributing guide](CONTRIBUTING.md)
5 | - [Code quality standards](CODE_QUALITY.md)
6 | - [Testing guidelines](TESTING.md)
7 | - [Architecture overview](ARCHITECTURE.md)
8 |
9 | ## Build and tooling
10 | - [ESLint and type safety](ESLINT_TYPE_SAFETY.md)
11 | - [Node.js and runtime notes](NODEJS_2025.md)
12 | - [Smithery integration notes](SMITHERY.md)
13 |
14 | ## Release and maintenance
15 | - [Release process](RELEASE_PROCESS.md)
16 | - [Manual testing](MANUAL_TESTING.md)
17 |
18 | ## Deep dives and plans
19 | - [Plugin development](PLUGIN_DEVELOPMENT.md)
20 | - [Reloaderoo docs](RELOADEROO.md)
21 | - [Reloaderoo primer](RELOADEROO_XCODEBUILDMCP_PRIMER.md)
22 | - [Reloaderoo for XcodeBuildMCP](RELOADEROO_FOR_XCODEBUILDMCP.md)
23 | - [Zod migration guide](ZOD_MIGRATION_GUIDE.md)
24 | - [Test runner env plan](TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md)
25 | - [Session management plan](session_management_plan.md)
26 | - [Session-aware migration todo](session-aware-migration-todo.md)
27 |
```
--------------------------------------------------------------------------------
/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 | ## Easy install
9 |
10 | Easiest way to install XcodeBuildMCP is to use [Smithery](https://smithery.ai) to install it from the registry. Copy and paste one of the following commands into your terminal.
11 |
12 | ```bash
13 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client client-name
14 | ```
15 |
16 | > [!IMPORTANT]
17 | > Due to a Smithery limitation the AXe library isn't bundled with Smithery installs, instead to ensure UI-automation tools work please install AXe and ensure it's globally available by issuing `brew install cameroncooke/axe/axe`, see [AXe](https://github.com/cameroncooke/axe) for more details.
18 |
19 | <details>
20 | <summary>Cursor</summary>
21 | <br />
22 |
23 | ```bash
24 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client cursor
25 | ```
26 | <br />
27 | </details>
28 |
29 | <details>
30 | <summary>Codex CLI</summary>
31 | <br />
32 |
33 | ```bash
34 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client codex
35 | ```
36 | <br />
37 | </details>
38 |
39 | <details>
40 | <summary>Claude Code</summary>
41 | <br />
42 |
43 | ```bash
44 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client claude-code
45 | ```
46 | <br />
47 | </details>
48 |
49 | <details>
50 | <summary>Claude Desktop</summary>
51 | <br />
52 |
53 | ```bash
54 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client claude
55 | ```
56 | <br />
57 | </details>
58 |
59 | <details>
60 | <summary>VS Code</summary>
61 | <br />
62 |
63 | ```bash
64 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client vscode
65 | ```
66 | <br />
67 | </details>
68 |
69 | <details>
70 | <summary>Windsurf</summary>
71 | <br />
72 |
73 | ```bash
74 | npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client windsurf
75 | ```
76 | <br />
77 | </details>
78 |
79 | <br />
80 |
81 | For other clients see: [Smithery XcodeBuildMCP](https://smithery.ai/server/cameroncooke/xcodebuildmcp), for other installation options including manual installation see [Getting Started](docs/GETTING_STARTED.md)
82 |
83 | ## Requirements
84 |
85 | - macOS 14.5 or later
86 | - Xcode 16.x or later
87 | - Node.js 18.x or later
88 |
89 | ## Notes
90 |
91 | - XcodeBuildMCP requests xcodebuild to skip macro validation to avoid errors when building projects that use Swift Macros.
92 | - Device tools require code signing to be configured in Xcode. See [docs/DEVICE_CODE_SIGNING.md](docs/DEVICE_CODE_SIGNING.md).
93 |
94 | ## Privacy
95 |
96 | XcodeBuildMCP uses Sentry for error telemetry. For more information or to opt out of error telemetry see [docs/PRIVACY.md](docs/PRIVACY.md).
97 |
98 | ## Documentation
99 |
100 | - Getting started: [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)
101 | - Configuration and options: [docs/CONFIGURATION.md](docs/CONFIGURATION.md)
102 | - Tools reference: [docs/TOOLS.md](docs/TOOLS.md)
103 | - Troubleshooting: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
104 | - Privacy: [docs/PRIVACY.md](docs/PRIVACY.md)
105 | - Contributing: [docs/dev/CONTRIBUTING.md](docs/dev/CONTRIBUTING.md)
106 |
107 | ## Licence
108 |
109 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
110 |
```
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
```markdown
1 | AGENTS.md
```
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
```markdown
1 | # Development Rules
2 |
3 | ## Code Quality
4 | - No `any` types unless absolutely necessary
5 | - Check node_modules for external API type definitions instead of guessing
6 | - **NEVER use inline imports** - no `await import("./foo.js")`, no `import("pkg").Type` in type positions, no dynamic imports for types. Always use standard top-level imports.
7 | - NEVER remove or downgrade code to fix type errors from outdated dependencies; upgrade the dependency instead
8 | - Always ask before removing functionality or code that appears to be intentional
9 | - Follow TypeScript best practices
10 |
11 | ## Commands
12 | - NEVER commit unless user asks
13 |
14 | ## GitHub
15 | When reading issues:
16 | - Always read all comments on the issue
17 | -
18 | ## Tools
19 | - GitHub CLI for issues/PRs
20 | -
21 | ## Style
22 | - Keep answers short and concise
23 | - No emojis in commits, issues, PR comments, or code
24 | - No fluff or cheerful filler text
25 | - Technical prose only, be kind but direct (e.g., "Thanks @user" not "Thanks so much @user!")
26 |
27 | ## Docs
28 | - If modifying or adding/removing tools run `npm run docs:update` to update the TOOLS.md file, never edit this file directly.
29 | -
30 | ### Changelog
31 | Location: `CHANGELOG.md`
32 |
33 | #### Format
34 | Use these sections under `## [Unreleased]`:
35 | - `### Added` - New features
36 | - `### Changed` - Changes to existing functionality
37 | - `### Fixed` - Bug fixes
38 | - `### Removed` - Removed features
39 | -
40 | #### Rules
41 | - Before adding entries, read the full `[Unreleased]` section to see which subsections already exist
42 | - New entries ALWAYS go under `## [Unreleased]` section
43 | - Append to existing subsections (e.g., `### Fixed`), do not create duplicates
44 | - NEVER modify already-released version sections (e.g., `## [0.12.2]`)
45 | - Each version section is immutable once released
46 | -
47 | #### Attribution
48 | - **Internal changes (from issues)**: `Fixed foo bar ([#123](https://github.com/cameroncook/XcodeBuildMCP/issues/123))`
49 | - **External contributions**: `Added feature X ([#456](https://github.com/cameroncook/XcodeBuildMCP/pull/456) by [@username](https://github.com/username))`
50 |
51 | ## **CRITICAL** Tool Usage Rules **CRITICAL**
52 | - NEVER use sed/cat to read a file or a range of a file. Always use the native read tool.
53 | - You MUST read every file you modify in full before editing.
54 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/docs/dev/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contributing
2 |
3 | Contributions are welcome! Here's how you can help improve XcodeBuildMCP.
4 |
5 | - [Local development setup](#local-development-setup)
6 | - [Prerequisites](#prerequisites)
7 | - [Optional: Enabling UI Automation](#optional-enabling-ui-automation)
8 | - [Installation](#installation)
9 | - [Configure your MCP client](#configure-your-mcp-client)
10 | - [Developing using VS Code](#developing-using-vs-code)
11 | - [Debugging](#debugging)
12 | - [MCP Inspector (Basic Debugging)](#mcp-inspector-basic-debugging)
13 | - [Reloaderoo (Advanced Debugging) - **RECOMMENDED**](#reloaderoo-advanced-debugging---recommended)
14 | - [1. Proxy Mode (Hot-Reloading)](#1-proxy-mode-hot-reloading)
15 | - [2. Inspection Mode (Raw MCP Debugging)](#2-inspection-mode-raw-mcp-debugging)
16 | - [Workflow Selection Testing](#workflow-selection-testing)
17 | - [Using XcodeBuildMCP doctor tool](#using-xcodebuildmcp-doctor-tool)
18 | - [Development Workflow with Reloaderoo](#development-workflow-with-reloaderoo)
19 | - [Architecture and Code Standards](#architecture-and-code-standards)
20 | - [Code Quality Requirements](#code-quality-requirements)
21 | - [Testing Standards](#testing-standards)
22 | - [Pre-Commit Checklist](#pre-commit-checklist)
23 | - [Making changes](#making-changes)
24 | - [Plugin Development](#plugin-development)
25 | - [Quick Plugin Development Checklist](#quick-plugin-development-checklist)
26 | - [Working with Project Templates](#working-with-project-templates)
27 | - [Template Repositories](#template-repositories)
28 | - [Local Template Development](#local-template-development)
29 | - [Template Versioning](#template-versioning)
30 | - [Testing Template Changes](#testing-template-changes)
31 | - [Testing](#testing)
32 | - [Submitting](#submitting)
33 | - [Code of Conduct](#code-of-conduct)
34 |
35 | ## Local development setup
36 |
37 | ### Prerequisites
38 |
39 | In addition to the prerequisites mentioned in the [Getting started](README.md/#getting-started) section of the README, you will also need:
40 |
41 | - Node.js (v18 or later)
42 | - npm
43 |
44 | #### Optional: Enabling UI Automation
45 |
46 | When running locally, you'll need to install AXe for UI automation:
47 |
48 | ```bash
49 | # Install axe (required for UI automation)
50 | brew tap cameroncooke/axe
51 | brew install axe
52 | ```
53 |
54 | ### Installation
55 |
56 | 1. Clone the repository
57 | 2. Install dependencies:
58 | ```
59 | npm install
60 | ```
61 | 3. Build the project:
62 | ```
63 | npm run build
64 | ```
65 | 4. Start the server:
66 | ```
67 | node build/index.js
68 | ```
69 |
70 | ### Configure your MCP client
71 |
72 | 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:
73 |
74 | ```json
75 | {
76 | "mcpServers": {
77 | "XcodeBuildMCP": {
78 | "command": "node",
79 | "args": [
80 | "/path_to/XcodeBuildMCP/build/index.js"
81 | ]
82 | }
83 | }
84 | }
85 | ```
86 |
87 | ### Developing using VS Code
88 |
89 | 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.
90 |
91 | To make your development workflow in VS Code more efficient:
92 |
93 | 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.
94 | 2. **Launch the Debugger**: Press `F5` to attach the Node.js debugger.
95 |
96 | Once these steps are completed, you can utilize the tools from the MCP server you are developing within this repository in agent mode.
97 | For more details on how to work with MCP servers in VS Code see: https://code.visualstudio.com/docs/copilot/chat/mcp-servers
98 |
99 | ### Debugging
100 |
101 | #### MCP Inspector (Basic Debugging)
102 |
103 | You can use MCP Inspector for basic debugging via:
104 |
105 | ```bash
106 | npm run inspect
107 | ```
108 |
109 | or if you prefer the explicit command:
110 |
111 | ```bash
112 | npx @modelcontextprotocol/inspector node build/index.js
113 | ```
114 |
115 | #### Reloaderoo (Advanced Debugging) - **RECOMMENDED**
116 |
117 | For development and debugging, we strongly recommend using **Reloaderoo**, which provides hot-reloading capabilities and advanced debugging features for MCP servers.
118 |
119 | Reloaderoo operates in two modes:
120 |
121 | ##### 1. Proxy Mode (Hot-Reloading)
122 | Provides transparent hot-reloading without disconnecting your MCP client:
123 |
124 | ```bash
125 | # Install reloaderoo globally
126 | npm install -g reloaderoo
127 |
128 | # Start XcodeBuildMCP through reloaderoo proxy
129 | reloaderoo -- node build/index.js
130 | ```
131 |
132 | **Benefits**:
133 | - 🔄 Hot-reload server without restarting client
134 | - 🛠️ Automatic `restart_server` tool added to toolset
135 | - 🌊 Transparent MCP protocol forwarding
136 | - 📡 Full protocol support (tools, resources, prompts)
137 |
138 | **MCP Client Configuration for Proxy Mode**:
139 | ```json
140 | "XcodeBuildMCP": {
141 | "command": "reloaderoo",
142 | "args": ["--", "node", "/path/to/XcodeBuildMCP/build/index.js"],
143 | "env": {
144 | "XCODEBUILDMCP_DEBUG": "true"
145 | }
146 | }
147 | ```
148 |
149 | ##### 2. Inspection Mode (Raw MCP Debugging)
150 | Exposes debug tools for making raw MCP protocol calls and inspecting server responses:
151 |
152 | ```bash
153 | # Start reloaderoo in inspection mode
154 | reloaderoo inspect mcp -- node build/index.js
155 | ```
156 |
157 | **Available Debug Tools**:
158 | - `list_tools` - List all server tools
159 | - `call_tool` - Execute any server tool with parameters
160 | - `list_resources` - List all server resources
161 | - `read_resource` - Read any server resource
162 | - `list_prompts` - List all server prompts
163 | - `get_prompt` - Get any server prompt
164 | - `get_server_info` - Get comprehensive server information
165 | - `ping` - Test server connectivity
166 |
167 | **MCP Client Configuration for Inspection Mode**:
168 | ```json
169 | "XcodeBuildMCP": {
170 | "command": "node",
171 | "args": [
172 | "/path/to/reloaderoo/dist/bin/reloaderoo.js",
173 | "inspect", "mcp",
174 | "--working-dir", "/path/to/XcodeBuildMCP",
175 | "--",
176 | "node", "/path/to/XcodeBuildMCP/build/index.js"
177 | ],
178 | "env": {
179 | "XCODEBUILDMCP_DEBUG": "true"
180 | }
181 | }
182 | ```
183 |
184 | #### Workflow Selection Testing
185 |
186 | Test full vs. selective workflow registration during development:
187 |
188 | ```bash
189 | # Test full tool registration (default)
190 | reloaderoo inspect mcp -- node build/index.js
191 |
192 | # Test selective workflow registration
193 | XCODEBUILDMCP_ENABLED_WORKFLOWS=simulator,device reloaderoo inspect mcp -- node build/index.js
194 | ```
195 | **Key Differences to Test**:
196 | - **Full Registration**: All tools are available immediately via `list_tools`
197 | - **Selective Registration**: Only tools from the selected workflows (plus `session-management`) are available
198 |
199 | #### Using XcodeBuildMCP doctor tool
200 |
201 | 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.
202 |
203 | > [!NOTE]
204 | > 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:
205 | > ```bash
206 | > npm run doctor
207 | > ```
208 |
209 | #### Development Workflow with Reloaderoo
210 |
211 | 1. **Start Development Session**:
212 | ```bash
213 | # Terminal 1: Start in hot-reload mode
214 | reloaderoo -- node build/index.js
215 |
216 | # Terminal 2: Start build watcher
217 | npm run build:watch
218 | ```
219 |
220 | 2. **Make Changes**: Edit source code in `src/`
221 |
222 | 3. **Test Changes**: Ask your AI client to restart the server:
223 | ```
224 | "Please restart the MCP server to load my changes"
225 | ```
226 | The AI will automatically call the `restart_server` tool provided by reloaderoo.
227 |
228 | 4. **Verify Changes**: New functionality immediately available without reconnecting client
229 |
230 | ## Architecture and Code Standards
231 |
232 | Before making changes, please familiarize yourself with:
233 | - [ARCHITECTURE.md](ARCHITECTURE.md) - Comprehensive architectural overview
234 | - [CLAUDE.md](../../CLAUDE.md) - AI assistant guidelines and testing principles
235 | - [TOOLS.md](../TOOLS.md) - Complete tool documentation
236 | - [CONFIGURATION.md](../CONFIGURATION.md) - Tool configuration options
237 |
238 | ### Code Quality Requirements
239 |
240 | 1. **Follow existing code patterns and structure**
241 | 2. **Use TypeScript strictly** - no `any` types, proper typing throughout
242 | 3. **Add proper error handling and logging** - all failures must set `isError: true`
243 | 4. **Update documentation for new features**
244 | 5. **Test with example projects before submitting**
245 |
246 | ### Testing Standards
247 |
248 | All contributions must adhere to the testing standards outlined in the [**XcodeBuildMCP Plugin Testing Guidelines (TESTING.md)**](TESTING.md). This is the canonical source of truth for all testing practices.
249 |
250 | **Key Principles (Summary):**
251 | - **No Vitest Mocking**: All forms of `vi.mock`, `vi.fn`, `vi.spyOn`, etc., are strictly forbidden.
252 | - **Dependency Injection**: All external dependencies (command execution, file system access) must be injected into tool logic functions using the `CommandExecutor` and `FileSystemExecutor` patterns.
253 | - **Test Production Code**: Tests must import and execute the actual tool logic, not mock implementations.
254 | - **Comprehensive Coverage**: Tests must cover input validation, command generation, and output processing.
255 |
256 | Please read [TESTING.md](TESTING.md) in its entirety before writing tests.
257 |
258 | ### Pre-Commit Checklist
259 |
260 | **MANDATORY**: Run these commands before any commit and ensure they all pass:
261 |
262 | ```bash
263 | # 1. Run linting (must pass with 0 errors)
264 | npm run lint
265 |
266 | # 2. Run formatting (must format all files)
267 | npm run format
268 |
269 | # 3. Run build (must compile successfully)
270 | npm run build
271 |
272 | # 4. Run tests (all tests must pass)
273 | npm test
274 | ```
275 |
276 | **NO EXCEPTIONS**: Code that fails any of these commands cannot be committed.
277 |
278 | ## Making changes
279 |
280 | 1. Fork the repository and create a new branch
281 | 2. Follow the TypeScript best practices and existing code style
282 | 3. Add proper parameter validation and error handling
283 |
284 | ## Plugin Development
285 |
286 | For comprehensive instructions on creating new tools and workflow groups, see our dedicated [Plugin Development Guide](PLUGIN_DEVELOPMENT.md).
287 |
288 | The plugin development guide covers:
289 | - Auto-discovery system architecture
290 | - Tool creation with dependency injection patterns
291 | - Workflow group organization
292 | - Testing guidelines and patterns
293 | - Workflow registration and selection
294 |
295 | ### Quick Plugin Development Checklist
296 |
297 | 1. Choose appropriate workflow directory in `src/mcp/tools/`
298 | 2. Follow naming conventions: `{action}_{target}_{specifier}_{projectType}`
299 | 3. Use dependency injection pattern with separate logic functions
300 | 4. Create comprehensive tests using `createMockExecutor()`
301 | 5. Add workflow metadata if creating new workflow group
302 |
303 | See [PLUGIN_DEVELOPMENT.md](PLUGIN_DEVELOPMENT.md) for complete details.
304 |
305 | ### Working with Project Templates
306 |
307 | XcodeBuildMCP uses external template repositories for the iOS and macOS project scaffolding features. These templates are maintained separately to allow independent versioning and updates.
308 |
309 | #### Template Repositories
310 |
311 | - **iOS Template**: [XcodeBuildMCP-iOS-Template](https://github.com/cameroncooke/XcodeBuildMCP-iOS-Template)
312 | - **macOS Template**: [XcodeBuildMCP-macOS-Template](https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template)
313 |
314 | #### Local Template Development
315 |
316 | When developing or testing changes to the templates:
317 |
318 | 1. Clone the template repository you want to work on:
319 | ```bash
320 | git clone https://github.com/cameroncooke/XcodeBuildMCP-iOS-Template.git
321 | git clone https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template.git
322 | ```
323 |
324 | 2. Set the appropriate environment variable to use your local template:
325 | ```bash
326 | # For iOS template development
327 | export XCODEBUILDMCP_IOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-iOS-Template
328 |
329 | # For macOS template development
330 | export XCODEBUILDMCP_MACOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-macOS-Template
331 | ```
332 |
333 | 3. When using MCP clients, add these environment variables to your MCP configuration:
334 | ```json
335 | "XcodeBuildMCP": {
336 | "command": "node",
337 | "args": ["/path_to/XcodeBuildMCP/build/index.js"],
338 | "env": {
339 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-iOS-Template",
340 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-macOS-Template"
341 | }
342 | }
343 | ```
344 |
345 | 4. The scaffold tools will use your local templates instead of downloading from GitHub releases.
346 |
347 | #### Template Versioning
348 |
349 | - Templates are versioned independently from XcodeBuildMCP
350 | - The default template version is specified in `package.json` under `templateVersion`
351 | - You can override the template version with `XCODEBUILD_MCP_TEMPLATE_VERSION` environment variable
352 | - To update the default template version:
353 | 1. Update `templateVersion` in `package.json`
354 | 2. Run `npm run build` to regenerate version.ts
355 | 3. Create a new XcodeBuildMCP release
356 |
357 | #### Testing Template Changes
358 |
359 | 1. Make changes to your local template
360 | 2. Test scaffolding with your changes using the local override
361 | 3. Verify the scaffolded project builds and runs correctly
362 | 4. Once satisfied, create a PR in the template repository
363 | 5. After merging, create a new release in the template repository using the release script
364 |
365 | ## Testing
366 |
367 | 1. Build the project with `npm run build`
368 | 2. Test your changes with MCP Inspector
369 | 3. Verify tools work correctly with different MCP clients
370 |
371 | ## Submitting
372 |
373 | 1. Run `npm run lint` to check for linting issues (use `npm run lint:fix` to auto-fix)
374 | 2. Run `npm run format:check` to verify formatting (use `npm run format` to fix)
375 | 3. Update documentation if you've added or modified features
376 | 4. Add your changes to the CHANGELOG.md file
377 | 5. Push your changes and create a pull request with a clear description
378 | 6. Link any related issues
379 |
380 | For major changes or new features, please open an issue first to discuss your proposed changes.
381 |
382 | ## Code of Conduct
383 |
384 | Please follow our [Code of Conduct](../../CODE_OF_CONDUCT.md) and community guidelines.
385 |
```
--------------------------------------------------------------------------------
/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/plugin-registry/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { loadWorkflowGroups, loadPlugins } from '../../core/plugin-registry.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 |
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config
2 | runtime: "typescript"
3 | target: "local"
4 |
```
--------------------------------------------------------------------------------
/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/version.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const version = '1.15.1';
2 | export const iOSTemplateVersion = 'v1.0.8';
3 | export const macOSTemplateVersion = 'v1.0.5';
4 |
```
--------------------------------------------------------------------------------
/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/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/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 |
```
--------------------------------------------------------------------------------
/src/utils/axe/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export {
2 | createAxeNotAvailableResponse,
3 | getAxePath,
4 | getBundledAxeEnvironment,
5 | areAxeToolsAvailable,
6 | isAxeAtLeastVersion,
7 | resolveAxeBinary,
8 | } from '../axe-helpers.ts';
9 |
```
--------------------------------------------------------------------------------
/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 | };
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 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const workflow = {
2 | name: 'Simulator Debugging',
3 | description:
4 | 'Interactive iOS Simulator debugging tools: attach LLDB, manage breakpoints, inspect stack/variables, and run LLDB commands.',
5 | };
6 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 | }
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/src/utils/log-capture/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { activeLogSessions, startLogCapture, stopLogCapture } from '../log_capture.ts';
2 |
3 | export function listActiveSimulatorLogSessionIds(): string[] {
4 | return Array.from(activeLogSessions.keys()).sort();
5 | }
6 |
7 | export { startLogCapture, stopLogCapture };
8 |
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTestTests/MCPTestTests.swift:
--------------------------------------------------------------------------------
```swift
1 | //
2 | // MCPTestTests.swift
3 | // MCPTestTests
4 | //
5 | // Created by Cameron on 15/12/2025.
6 | //
7 |
8 | import Testing
9 |
10 | struct MCPTestTests {
11 |
12 | @Test func example() async throws {
13 | // Write your test here and use APIs like `#expect(...)` to check expected conditions.
14 | }
15 |
16 | }
17 |
```
--------------------------------------------------------------------------------
/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 | };
6 |
```
--------------------------------------------------------------------------------
/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 | }
```
--------------------------------------------------------------------------------
/tsconfig.tests.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": true,
5 | "types": ["vitest/globals", "node"]
6 | },
7 | "include": ["src/**/*.test.ts", "tests-vitest/**/*.ts"],
8 | "exclude": [
9 | "node_modules",
10 | "plugins/**/*",
11 | "src/core/generated-plugins.ts",
12 | "src/core/generated-resources.ts"
13 | ]
14 | }
15 |
```
--------------------------------------------------------------------------------
/scripts/generate-loaders.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { generateResourceLoaders, generateWorkflowLoaders } from '../build-plugins/plugin-discovery.ts';
2 |
3 | async function main(): Promise<void> {
4 | await generateWorkflowLoaders();
5 | await generateResourceLoaders();
6 | }
7 |
8 | main().catch((error) => {
9 | console.error('Failed to generate plugin/resource loaders:', error);
10 | process.exit(1);
11 | });
12 |
```
--------------------------------------------------------------------------------
/src/utils/log-capture/device-log-sessions.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { ChildProcess } from 'child_process';
2 | import type * as fs from 'fs';
3 |
4 | export interface DeviceLogSession {
5 | process: ChildProcess;
6 | logFilePath: string;
7 | deviceUuid: string;
8 | bundleId: string;
9 | logStream?: fs.WriteStream;
10 | hasEnded: boolean;
11 | }
12 |
13 | export const activeDeviceLogSessions = new Map<string, DeviceLogSession>();
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 | };
6 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/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/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 | };
14 |
```
--------------------------------------------------------------------------------
/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 | annotations: {
9 | title: 'Show Session Defaults',
10 | readOnlyHint: true,
11 | },
12 | handler: async (): Promise<ToolResponse> => {
13 | const current = sessionStore.getAll();
14 | return { content: [{ type: 'text', text: JSON.stringify(current, null, 2) }], isError: false };
15 | },
16 | };
17 |
```
--------------------------------------------------------------------------------
/src/utils/debugger/tool-context.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { CommandExecutor } from '../execution/index.ts';
2 | import { getDefaultCommandExecutor } from '../execution/index.ts';
3 | import type { DebuggerManager } from './debugger-manager.ts';
4 | import { getDefaultDebuggerManager } from './index.ts';
5 |
6 | export type DebuggerToolContext = {
7 | executor: CommandExecutor;
8 | debugger: DebuggerManager;
9 | };
10 |
11 | export function getDefaultDebuggerToolContext(): DebuggerToolContext {
12 | return {
13 | executor: getDefaultCommandExecutor(),
14 | debugger: getDefaultDebuggerManager(),
15 | };
16 | }
17 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/docs/PRIVACY.md:
--------------------------------------------------------------------------------
```markdown
1 | # Privacy
2 |
3 | XcodeBuildMCP uses Sentry for error monitoring and diagnostics. This helps track crashes and unexpected errors to improve reliability.
4 |
5 | ## What is sent to Sentry
6 | - Error-level logs and diagnostic information only.
7 | - Error logs may include error messages, stack traces, and in some cases file paths or project names.
8 |
9 | ## Opting out
10 | To disable error telemetry, set:
11 |
12 | ```json
13 | "env": {
14 | "XCODEBUILDMCP_SENTRY_DISABLED": "true"
15 | }
16 | ```
17 |
18 | ## Related docs
19 | - Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
20 | - Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
21 |
```
--------------------------------------------------------------------------------
/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 | export { getDefaultInteractiveSpawner } from './interactive-process.ts';
7 |
8 | // Types
9 | export type { CommandExecutor, CommandResponse, CommandExecOptions } from '../CommandExecutor.ts';
10 | export type { FileSystemExecutor } from '../FileSystemExecutor.ts';
11 | export type {
12 | InteractiveProcess,
13 | InteractiveSpawner,
14 | SpawnInteractiveOptions,
15 | } from './interactive-process.ts';
16 |
```
--------------------------------------------------------------------------------
/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/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 | };
14 |
```
--------------------------------------------------------------------------------
/docs/DEMOS.md:
--------------------------------------------------------------------------------
```markdown
1 | # Demos
2 |
3 | ## Autonomously fixing build errors in Cursor
4 | 
5 |
6 | ## Utilising the new UI automation and screen capture features
7 | 
8 |
9 | ## Building and running iOS app in Claude Desktop
10 | https://github.com/user-attachments/assets/e3c08d75-8be6-4857-b4d0-9350b26ef086
11 |
12 | ## Related docs
13 | - Getting started: [GETTING_STARTED.md](GETTING_STARTED.md)
14 | - Tools reference: [TOOLS.md](TOOLS.md)
15 | - Session defaults: [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md)
16 |
```
--------------------------------------------------------------------------------
/src/utils/debugger/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export type DebuggerBackendKind = 'lldb-cli' | 'dap';
2 |
3 | export interface DebugSessionInfo {
4 | id: string;
5 | backend: DebuggerBackendKind;
6 | simulatorId: string;
7 | pid: number;
8 | createdAt: number;
9 | lastUsedAt: number;
10 | }
11 |
12 | export type BreakpointSpec =
13 | | { kind: 'file-line'; file: string; line: number }
14 | | { kind: 'function'; name: string };
15 |
16 | export interface BreakpointInfo {
17 | id: number;
18 | spec: BreakpointSpec;
19 | rawOutput: string;
20 | }
21 |
22 | export type DebugExecutionStatus = 'running' | 'stopped' | 'unknown' | 'terminated';
23 |
24 | export type DebugExecutionState = {
25 | status: DebugExecutionStatus;
26 | reason?: string;
27 | description?: string;
28 | threadId?: number;
29 | };
30 |
```
--------------------------------------------------------------------------------
/docs/DEVICE_CODE_SIGNING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Device Code Signing
2 |
3 | For device deployment features to work, code signing must be configured in Xcode before using XcodeBuildMCP device tools.
4 |
5 | ## One-time setup in Xcode
6 | 1. Open your project in Xcode.
7 | 2. Select your project target.
8 | 3. Open the "Signing & Capabilities" tab.
9 | 4. Enable "Automatically manage signing" and select your development team.
10 | 5. Ensure a valid provisioning profile is selected.
11 |
12 | ## What XcodeBuildMCP can and cannot do
13 | - Can build, install, launch, and test once signing is configured.
14 | - Cannot configure code signing automatically.
15 |
16 | ## Related docs
17 | - Tools reference: [TOOLS.md](TOOLS.md)
18 | - Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
19 |
```
--------------------------------------------------------------------------------
/src/core/generated-resources.ts:
--------------------------------------------------------------------------------
```typescript
1 | // AUTO-GENERATED - DO NOT EDIT
2 | // This file is generated by the plugin discovery esbuild plugin
3 |
4 | export const RESOURCE_LOADERS = {
5 | devices: async () => {
6 | const module = await import('../mcp/resources/devices.ts');
7 | return module.default;
8 | },
9 | doctor: async () => {
10 | const module = await import('../mcp/resources/doctor.ts');
11 | return module.default;
12 | },
13 | 'session-status': async () => {
14 | const module = await import('../mcp/resources/session-status.ts');
15 | return module.default;
16 | },
17 | simulators: async () => {
18 | const module = await import('../mcp/resources/simulators.ts');
19 | return module.default;
20 | },
21 | };
22 |
23 | export type ResourceName = keyof typeof RESOURCE_LOADERS;
24 |
```
--------------------------------------------------------------------------------
/src/utils/debugger/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { DebuggerManager } from './debugger-manager.ts';
2 |
3 | let defaultDebuggerManager: DebuggerManager | null = null;
4 |
5 | export function getDefaultDebuggerManager(): DebuggerManager {
6 | defaultDebuggerManager ??= new DebuggerManager();
7 | return defaultDebuggerManager;
8 | }
9 |
10 | export { DebuggerManager } from './debugger-manager.ts';
11 | export { getDefaultDebuggerToolContext } from './tool-context.ts';
12 | export { resolveSimulatorAppPid } from './simctl.ts';
13 | export { guardUiAutomationAgainstStoppedDebugger } from './ui-automation-guard.ts';
14 | export type {
15 | BreakpointInfo,
16 | BreakpointSpec,
17 | DebugExecutionState,
18 | DebugExecutionStatus,
19 | DebugSessionInfo,
20 | DebuggerBackendKind,
21 | } from './types.ts';
22 | export type { DebuggerToolContext } from './tool-context.ts';
23 |
```
--------------------------------------------------------------------------------
/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/utils/debugger/backends/DebuggerBackend.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { BreakpointInfo, BreakpointSpec, DebugExecutionState } from '../types.ts';
2 |
3 | export interface DebuggerBackend {
4 | readonly kind: 'lldb-cli' | 'dap';
5 |
6 | attach(opts: { pid: number; simulatorId: string; waitFor?: boolean }): Promise<void>;
7 | detach(): Promise<void>;
8 |
9 | runCommand(command: string, opts?: { timeoutMs?: number }): Promise<string>;
10 | resume(opts?: { threadId?: number }): Promise<void>;
11 |
12 | addBreakpoint(spec: BreakpointSpec, opts?: { condition?: string }): Promise<BreakpointInfo>;
13 | removeBreakpoint(id: number): Promise<string>;
14 |
15 | getStack(opts?: { threadIndex?: number; maxFrames?: number }): Promise<string>;
16 | getVariables(opts?: { frameIndex?: number }): Promise<string>;
17 | getExecutionState(opts?: { timeoutMs?: number }): Promise<DebugExecutionState>;
18 |
19 | dispose(): Promise<void>;
20 | }
21 |
```
--------------------------------------------------------------------------------
/src/utils/runtime-registry.ts:
--------------------------------------------------------------------------------
```typescript
1 | export type RuntimeToolInfo =
2 | | {
3 | mode: 'runtime';
4 | enabledWorkflows: string[];
5 | enabledTools: string[];
6 | totalRegistered: number;
7 | }
8 | | {
9 | mode: 'static';
10 | enabledWorkflows: string[];
11 | enabledTools: string[];
12 | totalRegistered: number;
13 | note: string;
14 | };
15 |
16 | let runtimeToolInfo: RuntimeToolInfo | null = null;
17 |
18 | export function recordRuntimeRegistration(info: {
19 | enabledWorkflows: string[];
20 | enabledTools: string[];
21 | }): void {
22 | const enabledWorkflows = [...new Set(info.enabledWorkflows)];
23 | const enabledTools = [...new Set(info.enabledTools)];
24 |
25 | runtimeToolInfo = {
26 | mode: 'runtime',
27 | enabledWorkflows,
28 | enabledTools,
29 | totalRegistered: enabledTools.length,
30 | };
31 | }
32 |
33 | export function getRuntimeRegistration(): RuntimeToolInfo | null {
34 | return runtimeToolInfo;
35 | }
36 |
```
--------------------------------------------------------------------------------
/scripts/generate-version.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { readFile, writeFile } from 'node:fs/promises';
2 | import path from 'node:path';
3 |
4 | interface PackageJson {
5 | version: string;
6 | iOSTemplateVersion: string;
7 | macOSTemplateVersion: string;
8 | }
9 |
10 | async function main(): Promise<void> {
11 | const repoRoot = process.cwd();
12 | const packagePath = path.join(repoRoot, 'package.json');
13 | const versionPath = path.join(repoRoot, 'src', 'version.ts');
14 |
15 | const raw = await readFile(packagePath, 'utf8');
16 | const pkg = JSON.parse(raw) as PackageJson;
17 |
18 | const content =
19 | `export const version = '${pkg.version}';\n` +
20 | `export const iOSTemplateVersion = '${pkg.iOSTemplateVersion}';\n` +
21 | `export const macOSTemplateVersion = '${pkg.macOSTemplateVersion}';\n`;
22 |
23 | await writeFile(versionPath, content, 'utf8');
24 | }
25 |
26 | main().catch((error) => {
27 | console.error('Failed to generate src/version.ts:', error);
28 | process.exit(1);
29 | });
30 |
```
--------------------------------------------------------------------------------
/src/core/plugin-types.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as z from 'zod';
2 | import { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
3 | import { ToolResponse } from '../types/common.ts';
4 |
5 | export type ToolSchemaShape = Record<string, z.ZodType>;
6 |
7 | export interface PluginMeta {
8 | readonly name: string; // Verb used by MCP
9 | readonly schema: ToolSchemaShape; // Zod validation schema (object schema)
10 | readonly description?: string; // One-liner shown in help
11 | readonly annotations?: ToolAnnotations; // MCP tool annotations for LLM behavior hints
12 | handler(params: Record<string, unknown>): Promise<ToolResponse>;
13 | }
14 |
15 | export interface WorkflowMeta {
16 | readonly name: string;
17 | readonly description: 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 |
```
--------------------------------------------------------------------------------
/.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: Bundle AXe artifacts
30 | run: npm run bundle:axe
31 |
32 | - name: Build (Smithery)
33 | run: npm run build
34 |
35 | - name: Verify Smithery bundle
36 | run: npm run verify:smithery-bundle
37 |
38 | - name: Lint
39 | run: npm run lint
40 |
41 | - name: Check formatting
42 | run: npm run format:check
43 |
44 | - name: Type check
45 | run: npm run typecheck
46 |
47 | - name: Run tests
48 | run: npm test
49 |
50 | - run: npx pkg-pr-new publish
51 |
```
--------------------------------------------------------------------------------
/src/utils/FileSystemExecutor.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * File system executor interface for dependency injection
3 | */
4 |
5 | import type { WriteStream } from 'fs';
6 |
7 | export interface FileSystemExecutor {
8 | mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
9 | readFile(path: string, encoding?: BufferEncoding): Promise<string>;
10 | writeFile(path: string, content: string, encoding?: BufferEncoding): Promise<void>;
11 | createWriteStream(path: string, options?: { flags?: string }): WriteStream;
12 | cp(source: string, destination: string, options?: { recursive?: boolean }): Promise<void>;
13 | readdir(path: string, options?: { withFileTypes?: boolean }): Promise<unknown[]>;
14 | rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
15 | existsSync(path: string): boolean;
16 | stat(path: string): Promise<{ isDirectory(): boolean; mtimeMs: number }>;
17 | mkdtemp(prefix: string): Promise<string>;
18 | tmpdir(): string;
19 | }
20 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('Simulator Management');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | '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.',
21 | );
22 | });
23 | });
24 | });
25 |
```
--------------------------------------------------------------------------------
/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 | });
38 |
```
--------------------------------------------------------------------------------
/docs/SESSION_DEFAULTS.md:
--------------------------------------------------------------------------------
```markdown
1 | # Session Defaults
2 |
3 | By default, XcodeBuildMCP uses a session-aware mode. The client sets shared defaults once (simulator, device, project/workspace, scheme, etc.) and all tools reuse them. This reduces schema size and repeated payloads.
4 |
5 | ## How it works
6 | - Call `session_set_defaults` once at the start of a workflow.
7 | - Tools reuse those defaults automatically.
8 | - Use `session_show_defaults` to inspect current values.
9 | - Use `session_clear_defaults` to clear values when switching contexts.
10 |
11 | See the session-management tools in [TOOLS.md](TOOLS.md).
12 |
13 | ## Opting out
14 | If you prefer explicit parameters on every tool call, set:
15 |
16 | ```json
17 | "env": {
18 | "XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS": "true"
19 | }
20 | ```
21 |
22 | This restores the legacy schemas with per-call parameters while still honoring any defaults you choose to set.
23 |
24 | ## Related docs
25 | - Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
26 | - Tools reference: [TOOLS.md](TOOLS.md)
27 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/scripts/verify-smithery-bundle.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6 | BUNDLE_DIR="$PROJECT_ROOT/.smithery/bundled"
7 | AXE_BIN="$BUNDLE_DIR/axe"
8 | FRAMEWORK_DIR="$BUNDLE_DIR/Frameworks"
9 |
10 | if [ ! -f "$AXE_BIN" ]; then
11 | echo "❌ Missing AXe binary at $AXE_BIN"
12 | if [ -d "$PROJECT_ROOT/.smithery" ]; then
13 | echo "🔍 .smithery contents:"
14 | ls -la "$PROJECT_ROOT/.smithery"
15 | fi
16 | exit 1
17 | fi
18 |
19 | if [ ! -d "$FRAMEWORK_DIR" ]; then
20 | echo "❌ Missing Frameworks directory at $FRAMEWORK_DIR"
21 | if [ -d "$BUNDLE_DIR" ]; then
22 | echo "🔍 bundled contents:"
23 | ls -la "$BUNDLE_DIR"
24 | fi
25 | exit 1
26 | fi
27 |
28 | FRAMEWORK_COUNT="$(find "$FRAMEWORK_DIR" -maxdepth 2 -type d -name "*.framework" | wc -l | tr -d ' ')"
29 | if [ "$FRAMEWORK_COUNT" -eq 0 ]; then
30 | echo "❌ No frameworks found in $FRAMEWORK_DIR"
31 | find "$FRAMEWORK_DIR" -maxdepth 2 -type d | head -n 50
32 | exit 1
33 | fi
34 |
35 | echo "✅ Smithery bundle includes AXe binary and $FRAMEWORK_COUNT frameworks"
36 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/.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 | "INCREMENTAL_BUILDS_ENABLED": "false",
13 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-iOS-Template",
14 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-macOS-Template"
15 | }
16 | },
17 | "XcodeBuildMCP-Dev": {
18 | "type": "stdio",
19 | "command": "node",
20 | "args": [
21 | "--inspect=9999",
22 | "--trace-warnings",
23 | "${workspaceFolder}/build/index.js"
24 | ],
25 | "env": {
26 | "XCODEBUILDMCP_DEBUG": "true",
27 | "INCREMENTAL_BUILDS_ENABLED": "false",
28 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-iOS-Template",
29 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-macOS-Template"
30 | }
31 | }
32 | }
33 | }
```
--------------------------------------------------------------------------------
/src/utils/session-status.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { getDefaultDebuggerManager } from './debugger/index.ts';
2 | import { listActiveSimulatorLogSessionIds } from './log-capture/index.ts';
3 | import { activeDeviceLogSessions } from './log-capture/device-log-sessions.ts';
4 |
5 | export type SessionRuntimeStatusSnapshot = {
6 | logging: {
7 | simulator: { activeSessionIds: string[] };
8 | device: { activeSessionIds: string[] };
9 | };
10 | debug: {
11 | currentSessionId: string | null;
12 | sessionIds: string[];
13 | };
14 | };
15 |
16 | export function getSessionRuntimeStatusSnapshot(): SessionRuntimeStatusSnapshot {
17 | const debuggerManager = getDefaultDebuggerManager();
18 | const sessionIds = debuggerManager
19 | .listSessions()
20 | .map((session) => session.id)
21 | .sort();
22 |
23 | return {
24 | logging: {
25 | simulator: {
26 | activeSessionIds: listActiveSimulatorLogSessionIds(),
27 | },
28 | device: {
29 | activeSessionIds: Array.from(activeDeviceLogSessions.keys()).sort(),
30 | },
31 | },
32 | debug: {
33 | currentSessionId: debuggerManager.getCurrentSessionId(),
34 | sessionIds,
35 | },
36 | };
37 | }
38 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/smithery.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import { execFileSync } from 'child_process';
2 | import { cpSync, existsSync, mkdirSync } from 'fs';
3 | import { dirname, join, resolve } from 'path';
4 |
5 | const projectRoot = process.cwd();
6 | const bundledDir = join(projectRoot, 'bundled');
7 | const bundledAxePath = join(bundledDir, 'axe');
8 |
9 | function resolveOutputDir() {
10 | const args = process.argv;
11 | const outIndex = args.findIndex((arg) => arg === '--out' || arg === '-o');
12 | if (outIndex !== -1 && args[outIndex + 1]) {
13 | return dirname(resolve(args[outIndex + 1]));
14 | }
15 | return join(projectRoot, '.smithery');
16 | }
17 |
18 | const outputDir = resolveOutputDir();
19 | const bundledTargetDir = join(outputDir, 'bundled');
20 |
21 | if (!existsSync(bundledAxePath)) {
22 | execFileSync('bash', [join(projectRoot, 'scripts', 'bundle-axe.sh')], {
23 | stdio: 'inherit',
24 | });
25 | }
26 |
27 | if (existsSync(bundledAxePath)) {
28 | mkdirSync(outputDir, { recursive: true });
29 | cpSync(bundledDir, bundledTargetDir, { recursive: true });
30 | } else {
31 | throw new Error(`AXe bundle missing at ${bundledAxePath}`);
32 | }
33 |
34 | export default {
35 | esbuild: {
36 | format: 'cjs',
37 | target: 'node18',
38 | },
39 | };
40 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/doctor/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for doctor workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('doctor 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('System Doctor');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/utils/debugger/simctl.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { CommandExecutor } from '../execution/index.ts';
2 |
3 | export async function resolveSimulatorAppPid(opts: {
4 | executor: CommandExecutor;
5 | simulatorId: string;
6 | bundleId: string;
7 | }): Promise<number> {
8 | const result = await opts.executor(
9 | ['xcrun', 'simctl', 'spawn', opts.simulatorId, 'launchctl', 'list'],
10 | 'Resolve simulator app PID',
11 | true,
12 | );
13 |
14 | if (!result.success) {
15 | throw new Error(result.error ?? 'Failed to read simulator process list');
16 | }
17 |
18 | const lines = result.output.split('\n');
19 | for (const line of lines) {
20 | if (!line.includes(opts.bundleId)) continue;
21 |
22 | const columns = line.trim().split(/\s+/);
23 | const pidToken = columns[0];
24 |
25 | if (!pidToken || pidToken === '-') {
26 | throw new Error(`App ${opts.bundleId} is not running on simulator ${opts.simulatorId}`);
27 | }
28 |
29 | const pid = Number(pidToken);
30 | if (Number.isNaN(pid) || pid <= 0) {
31 | throw new Error(`Unable to parse PID for ${opts.bundleId} from: ${line}`);
32 | }
33 |
34 | return pid;
35 | }
36 |
37 | throw new Error(`No running process found for ${opts.bundleId} on simulator ${opts.simulatorId}`);
38 | }
39 |
```
--------------------------------------------------------------------------------
/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/macos/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for macos-project workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('macos-project 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('macOS Development');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for swift-package workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('swift-package 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('Swift Package Manager');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Swift Package Manager operations for building, testing, running, and managing Swift packages and dependencies. Complete SPM workflow support.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/utilities/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for utilities workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('utilities 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('Project Utilities');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/utils/debugger/dap/adapter-discovery.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { CommandExecutor } from '../../execution/index.ts';
2 | import { log } from '../../logging/index.ts';
3 | import { DependencyError } from '../../errors.ts';
4 |
5 | const LOG_PREFIX = '[DAP Adapter]';
6 |
7 | export async function resolveLldbDapCommand(opts: {
8 | executor: CommandExecutor;
9 | }): Promise<string[]> {
10 | try {
11 | const result = await opts.executor(['xcrun', '--find', 'lldb-dap'], LOG_PREFIX);
12 | if (!result.success) {
13 | throw new DependencyError('xcrun returned a non-zero exit code for lldb-dap discovery.');
14 | }
15 |
16 | const resolved = result.output.trim();
17 | if (!resolved) {
18 | throw new DependencyError('xcrun did not return a path for lldb-dap.');
19 | }
20 |
21 | log('debug', `${LOG_PREFIX} resolved lldb-dap: ${resolved}`);
22 | return [resolved];
23 | } catch (error) {
24 | if (error instanceof DependencyError) {
25 | throw error;
26 | }
27 | const message = error instanceof Error ? error.message : String(error);
28 | throw new DependencyError(
29 | 'DAP backend selected but lldb-dap not found. Ensure Xcode is installed and xcrun can locate lldb-dap, or set XCODEBUILDMCP_DEBUGGER_BACKEND=lldb-cli.',
30 | message,
31 | );
32 | }
33 | }
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for project-discovery workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('project-discovery 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('Project Discovery');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/logging/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for logging workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('logging 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('Log Capture & Management');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Log capture and management tools for iOS simulators and physical devices. Start, stop, and analyze application and system logs during development and testing.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/ui-testing/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for ui-testing workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('ui-testing 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('UI Testing & Automation');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/docs/OVERVIEW.md:
--------------------------------------------------------------------------------
```markdown
1 | # Overview
2 |
3 | XcodeBuildMCP is a Model Context Protocol (MCP) server that exposes Xcode operations as tools and resources for AI assistants and other MCP clients. It uses a plugin-based architecture with workflow groups so clients can access Xcode projects, simulators, devices, and Swift packages through a standard interface.
4 |
5 | ## Why it exists
6 | - Standardizes Xcode interactions for AI agents instead of ad-hoc command lines.
7 | - Reduces configuration errors by providing purpose-built tools.
8 | - Enables agents to build, inspect errors, and iterate autonomously.
9 |
10 | ## What it can do
11 | - Xcode project discovery, build, test, and clean.
12 | - Simulator and device app lifecycle management.
13 | - Swift Package Manager build, test, and run.
14 | - UI automation, screenshots, and video capture.
15 | - Log capture and system diagnostics.
16 | - Debugger attach, breakpoints, stack, variables, and LLDB command execution.
17 |
18 | See the full tool catalog in [TOOLS.md](TOOLS.md).
19 |
20 | ## Next steps
21 | - Get started: [GETTING_STARTED.md](GETTING_STARTED.md)
22 | - Configure options: [CONFIGURATION.md](CONFIGURATION.md)
23 | - Tools reference: [TOOLS.md](TOOLS.md)
24 | - Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
25 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for simulator-project workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('simulator-project 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('iOS Simulator Development');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/project-scaffolding/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for project-scaffolding workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('project-scaffolding 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('Project Scaffolding');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | 'Tools for creating new iOS and macOS projects from templates. Bootstrap new applications with best practices, standard configurations, and modern project structures.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for device-project workflow metadata
3 | */
4 | import { describe, it, expect } from 'vitest';
5 | import { workflow } from '../index.ts';
6 |
7 | describe('device-project 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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('iOS Device Development');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | '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.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/session-management/session_clear_defaults.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as 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 | annotations: {
37 | title: 'Clear Session Defaults',
38 | destructiveHint: true,
39 | },
40 | handler: createTypedTool(schemaObj, sessionClearDefaultsLogic, getDefaultCommandExecutor),
41 | };
42 |
```
--------------------------------------------------------------------------------
/src/mcp/resources/session-status.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Session Status Resource Plugin
3 | *
4 | * Provides read-only runtime session state for log capture and debugging.
5 | */
6 |
7 | import { log } from '../../utils/logging/index.ts';
8 | import { getSessionRuntimeStatusSnapshot } from '../../utils/session-status.ts';
9 |
10 | export async function sessionStatusResourceLogic(): Promise<{ contents: Array<{ text: string }> }> {
11 | try {
12 | log('info', 'Processing session status resource request');
13 | const status = getSessionRuntimeStatusSnapshot();
14 |
15 | return {
16 | contents: [
17 | {
18 | text: JSON.stringify(status, null, 2),
19 | },
20 | ],
21 | };
22 | } catch (error) {
23 | const errorMessage = error instanceof Error ? error.message : String(error);
24 | log('error', `Error in session status resource handler: ${errorMessage}`);
25 |
26 | return {
27 | contents: [
28 | {
29 | text: `Error retrieving session status: ${errorMessage}`,
30 | },
31 | ],
32 | };
33 | }
34 | }
35 |
36 | export default {
37 | uri: 'xcodebuildmcp://session-status',
38 | name: 'session-status',
39 | description: 'Runtime session state for log capture and debugging',
40 | mimeType: 'application/json',
41 | async handler(): Promise<{ contents: Array<{ text: string }> }> {
42 | return sessionStatusResourceLogic();
43 | },
44 | };
45 |
```
--------------------------------------------------------------------------------
/.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/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 | });
13 |
14 | it('should have correct workflow name', () => {
15 | expect(workflow.name).toBe('session-management');
16 | });
17 |
18 | it('should have correct description', () => {
19 | expect(workflow.description).toBe(
20 | '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.',
21 | );
22 | });
23 | });
24 |
25 | describe('Workflow Validation', () => {
26 | it('should have valid string properties', () => {
27 | expect(typeof workflow.name).toBe('string');
28 | expect(typeof workflow.description).toBe('string');
29 | expect(workflow.name.length).toBeGreaterThan(0);
30 | expect(workflow.description.length).toBeGreaterThan(0);
31 | });
32 | });
33 | });
34 |
```
--------------------------------------------------------------------------------
/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 | suppressWarnings?: boolean;
14 | };
15 |
16 | class SessionStore {
17 | private defaults: SessionDefaults = {};
18 |
19 | setDefaults(partial: Partial<SessionDefaults>): void {
20 | this.defaults = { ...this.defaults, ...partial };
21 | log('info', `[Session] Defaults updated: ${Object.keys(partial).join(', ')}`);
22 | }
23 |
24 | clear(keys?: (keyof SessionDefaults)[]): void {
25 | if (keys == null) {
26 | this.defaults = {};
27 | log('info', '[Session] All defaults cleared');
28 | return;
29 | }
30 | if (keys.length === 0) {
31 | // No-op when an empty array is provided (e.g., empty UI selection)
32 | log('info', '[Session] No keys provided to clear; no changes made');
33 | return;
34 | }
35 | for (const k of keys) delete this.defaults[k];
36 | log('info', `[Session] Defaults cleared: ${keys.join(', ')}`);
37 | }
38 |
39 | get<K extends keyof SessionDefaults>(key: K): SessionDefaults[K] {
40 | return this.defaults[key];
41 | }
42 |
43 | getAll(): SessionDefaults {
44 | return { ...this.defaults };
45 | }
46 | }
47 |
48 | export const sessionStore = new SessionStore();
49 |
```
--------------------------------------------------------------------------------
/src/utils/__tests__/debugger-simctl.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it } from 'vitest';
2 | import { resolveSimulatorAppPid } from '../debugger/simctl.ts';
3 | import { createMockExecutor } from '../../test-utils/mock-executors.ts';
4 |
5 | describe('resolveSimulatorAppPid', () => {
6 | it('returns PID when bundle id is found', async () => {
7 | const mockExecutor = createMockExecutor({
8 | success: true,
9 | output: '1234 0 com.example.MyApp\n',
10 | });
11 |
12 | const pid = await resolveSimulatorAppPid({
13 | executor: mockExecutor,
14 | simulatorId: 'SIM-123',
15 | bundleId: 'com.example.MyApp',
16 | });
17 |
18 | expect(pid).toBe(1234);
19 | });
20 |
21 | it('throws when bundle id is missing', async () => {
22 | const mockExecutor = createMockExecutor({
23 | success: true,
24 | output: '999 0 other.app\n',
25 | });
26 |
27 | await expect(
28 | resolveSimulatorAppPid({
29 | executor: mockExecutor,
30 | simulatorId: 'SIM-123',
31 | bundleId: 'com.example.MyApp',
32 | }),
33 | ).rejects.toThrow('No running process found');
34 | });
35 |
36 | it('throws when PID is missing', async () => {
37 | const mockExecutor = createMockExecutor({
38 | success: true,
39 | output: '- 0 com.example.MyApp\n',
40 | });
41 |
42 | await expect(
43 | resolveSimulatorAppPid({
44 | executor: mockExecutor,
45 | simulatorId: 'SIM-123',
46 | bundleId: 'com.example.MyApp',
47 | }),
48 | ).rejects.toThrow('not running');
49 | });
50 | });
51 |
```
--------------------------------------------------------------------------------
/.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 | "allowedModels": [
43 | "copilot/gpt-5.2"
44 | ]
45 | }
46 | },
47 | }
```
--------------------------------------------------------------------------------
/.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 | "INCREMENTAL_BUILDS_ENABLED": "false",
38 | "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-iOS-Template",
39 | "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-macOS-Template"
40 | },
41 | "sourceMaps": true,
42 | "outFiles": [
43 | "${workspaceFolder}/build/**/*.js"
44 | ],
45 | "skipFiles": [
46 | "<node_internals>/**"
47 | ],
48 | "sourceMapPathOverrides": {
49 | "/*": "${workspaceFolder}/src/*"
50 | }
51 | }
52 | ]
53 | }
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_detach.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as z from 'zod';
2 | import { ToolResponse } from '../../../types/common.ts';
3 | import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
4 | import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
5 | import {
6 | getDefaultDebuggerToolContext,
7 | type DebuggerToolContext,
8 | } from '../../../utils/debugger/index.ts';
9 |
10 | const debugDetachSchema = z.object({
11 | debugSessionId: z
12 | .string()
13 | .optional()
14 | .describe('Debug session ID to detach (defaults to current session)'),
15 | });
16 |
17 | export type DebugDetachParams = z.infer<typeof debugDetachSchema>;
18 |
19 | export async function debug_detachLogic(
20 | params: DebugDetachParams,
21 | ctx: DebuggerToolContext,
22 | ): Promise<ToolResponse> {
23 | try {
24 | const targetId = params.debugSessionId ?? ctx.debugger.getCurrentSessionId();
25 | await ctx.debugger.detachSession(targetId ?? undefined);
26 |
27 | return createTextResponse(`✅ Detached debugger session${targetId ? ` ${targetId}` : ''}.`);
28 | } catch (error) {
29 | const message = error instanceof Error ? error.message : String(error);
30 | return createErrorResponse('Failed to detach debugger', message);
31 | }
32 | }
33 |
34 | export default {
35 | name: 'debug_detach',
36 | description: 'Detach the current debugger session or a specific debugSessionId.',
37 | schema: debugDetachSchema.shape,
38 | handler: createTypedToolWithContext<DebugDetachParams, DebuggerToolContext>(
39 | debugDetachSchema,
40 | debug_detachLogic,
41 | getDefaultDebuggerToolContext,
42 | ),
43 | };
44 |
```
--------------------------------------------------------------------------------
/src/server/bootstrap.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3 | import process from 'node:process';
4 | import { registerResources } from '../core/resources.ts';
5 | import { log, setLogLevel, type LogLevel } from '../utils/logger.ts';
6 | import { registerWorkflows } from '../utils/tool-registry.ts';
7 |
8 | export interface BootstrapOptions {
9 | enabledWorkflows?: string[];
10 | }
11 |
12 | function parseEnabledWorkflows(value: string): string[] {
13 | return value
14 | .split(',')
15 | .map((name) => name.trim())
16 | .filter(Boolean);
17 | }
18 |
19 | export async function bootstrapServer(
20 | server: McpServer,
21 | options: BootstrapOptions = {},
22 | ): Promise<void> {
23 | server.server.setRequestHandler(SetLevelRequestSchema, async (request) => {
24 | const { level } = request.params;
25 | setLogLevel(level as LogLevel);
26 | log('info', `Client requested log level: ${level}`);
27 | return {};
28 | });
29 |
30 | const enabledWorkflows = options.enabledWorkflows?.length
31 | ? options.enabledWorkflows
32 | : process.env.XCODEBUILDMCP_ENABLED_WORKFLOWS
33 | ? parseEnabledWorkflows(process.env.XCODEBUILDMCP_ENABLED_WORKFLOWS)
34 | : [];
35 |
36 | if (enabledWorkflows.length > 0) {
37 | log('info', `🚀 Initializing server with selected workflows: ${enabledWorkflows.join(', ')}`);
38 | await registerWorkflows(server, enabledWorkflows);
39 | } else {
40 | log('info', '🚀 Initializing server with all tools...');
41 | await registerWorkflows(server);
42 | }
43 |
44 | await registerResources(server);
45 | }
46 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_continue.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as z from 'zod';
2 | import { ToolResponse } from '../../../types/common.ts';
3 | import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
4 | import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
5 | import {
6 | getDefaultDebuggerToolContext,
7 | type DebuggerToolContext,
8 | } from '../../../utils/debugger/index.ts';
9 |
10 | const debugContinueSchema = z.object({
11 | debugSessionId: z
12 | .string()
13 | .optional()
14 | .describe('Debug session ID to resume (defaults to current session)'),
15 | });
16 |
17 | export type DebugContinueParams = z.infer<typeof debugContinueSchema>;
18 |
19 | export async function debug_continueLogic(
20 | params: DebugContinueParams,
21 | ctx: DebuggerToolContext,
22 | ): Promise<ToolResponse> {
23 | try {
24 | const targetId = params.debugSessionId ?? ctx.debugger.getCurrentSessionId();
25 | await ctx.debugger.resumeSession(targetId ?? undefined);
26 |
27 | return createTextResponse(`✅ Resumed debugger session${targetId ? ` ${targetId}` : ''}.`);
28 | } catch (error) {
29 | const message = error instanceof Error ? error.message : String(error);
30 | return createErrorResponse('Failed to resume debugger', message);
31 | }
32 | }
33 |
34 | export default {
35 | name: 'debug_continue',
36 | description: 'Resume execution in the active debug session or a specific debugSessionId.',
37 | schema: debugContinueSchema.shape,
38 | handler: createTypedToolWithContext<DebugContinueParams, DebuggerToolContext>(
39 | debugContinueSchema,
40 | debug_continueLogic,
41 | getDefaultDebuggerToolContext,
42 | ),
43 | };
44 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_variables.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as z from 'zod';
2 | import { ToolResponse } from '../../../types/common.ts';
3 | import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
4 | import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
5 | import {
6 | getDefaultDebuggerToolContext,
7 | type DebuggerToolContext,
8 | } from '../../../utils/debugger/index.ts';
9 |
10 | const debugVariablesSchema = z.object({
11 | debugSessionId: z
12 | .string()
13 | .optional()
14 | .describe('Debug session ID to target (defaults to current session)'),
15 | frameIndex: z.number().int().nonnegative().optional().describe('Frame index to inspect'),
16 | });
17 |
18 | export type DebugVariablesParams = z.infer<typeof debugVariablesSchema>;
19 |
20 | export async function debug_variablesLogic(
21 | params: DebugVariablesParams,
22 | ctx: DebuggerToolContext,
23 | ): Promise<ToolResponse> {
24 | try {
25 | const output = await ctx.debugger.getVariables(params.debugSessionId, {
26 | frameIndex: params.frameIndex,
27 | });
28 | return createTextResponse(output.trim());
29 | } catch (error) {
30 | const message = error instanceof Error ? error.message : String(error);
31 | return createErrorResponse('Failed to get variables', message);
32 | }
33 | }
34 |
35 | export default {
36 | name: 'debug_variables',
37 | description: 'Return variables for a selected frame in the active debug session.',
38 | schema: debugVariablesSchema.shape,
39 | handler: createTypedToolWithContext<DebugVariablesParams, DebuggerToolContext>(
40 | debugVariablesSchema,
41 | debug_variablesLogic,
42 | getDefaultDebuggerToolContext,
43 | ),
44 | };
45 |
```
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: "Stale Issues and PRs"
2 |
3 | on:
4 | schedule:
5 | - cron: "30 3 * * *"
6 | workflow_dispatch: {}
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | stale:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Mark and close stale items
17 | uses: actions/[email protected]
18 | with:
19 | repo-token: ${{ secrets.GITHUB_TOKEN }}
20 | days-before-issue-stale: 30
21 | days-before-issue-close: 7
22 | days-before-pr-stale: 21
23 | days-before-pr-close: 7
24 | stale-issue-label: "stale"
25 | stale-pr-label: "stale-pr"
26 | exempt-issue-assignees: true
27 | exempt-pr-assignees: true
28 | exempt-issue-labels: "no-stale,security,pinned"
29 | exempt-pr-labels: "no-stale,security,pinned"
30 | stale-issue-message: >
31 | This issue has been inactive for 30 days. It will be closed in 7 days
32 | if no further activity occurs. Add a comment to keep it open, or apply
33 | the `no-stale` label.
34 | stale-pr-message: >
35 | This PR has been inactive for 21 days. It will be closed in 7 days if
36 | no further activity occurs. Add a comment to keep it open, or apply
37 | the `no-stale` label.
38 | close-issue-message: >
39 | Closing due to inactivity. If this is still relevant, please reopen
40 | or file a new issue with updated context.
41 | close-pr-message: >
42 | Closing due to inactivity. If this is still relevant, please reopen
43 | or open a fresh PR with updated context.
44 |
```
--------------------------------------------------------------------------------
/src/utils/workflow-selection.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { WorkflowGroup } from '../core/plugin-types.ts';
2 |
3 | const REQUIRED_WORKFLOW = 'session-management';
4 | const DEBUG_WORKFLOW = 'doctor';
5 |
6 | function normalizeWorkflowNames(workflowNames: string[]): string[] {
7 | return workflowNames.map((name) => name.trim().toLowerCase()).filter(Boolean);
8 | }
9 |
10 | function isWorkflowGroup(value: WorkflowGroup | undefined): value is WorkflowGroup {
11 | return Boolean(value);
12 | }
13 |
14 | function isDebugEnabled(): boolean {
15 | const value = process.env.XCODEBUILDMCP_DEBUG ?? '';
16 | return value.toLowerCase() === 'true' || value === '1';
17 | }
18 |
19 | export function resolveSelectedWorkflows(
20 | workflowGroups: Map<string, WorkflowGroup>,
21 | workflowNames: string[] = [],
22 | ): {
23 | selectedWorkflows: WorkflowGroup[];
24 | selectedNames: string[] | null;
25 | } {
26 | const normalizedNames = normalizeWorkflowNames(workflowNames);
27 | const autoSelected = isDebugEnabled() ? [REQUIRED_WORKFLOW, DEBUG_WORKFLOW] : [REQUIRED_WORKFLOW];
28 | const selectedNames =
29 | normalizedNames.length > 0 ? [...new Set([...autoSelected, ...normalizedNames])] : null;
30 |
31 | const selectedWorkflows = selectedNames
32 | ? selectedNames.map((workflowName) => workflowGroups.get(workflowName)).filter(isWorkflowGroup)
33 | : [...workflowGroups.values()];
34 |
35 | return { selectedWorkflows, selectedNames };
36 | }
37 |
38 | export function collectToolNames(workflows: WorkflowGroup[]): string[] {
39 | const toolNames = new Set<string>();
40 |
41 | for (const workflow of workflows) {
42 | for (const tool of workflow.tools) {
43 | if (tool?.name) {
44 | toolNames.add(tool.name);
45 | }
46 | }
47 | }
48 |
49 | return [...toolNames];
50 | }
51 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_stack.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as z from 'zod';
2 | import { ToolResponse } from '../../../types/common.ts';
3 | import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
4 | import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
5 | import {
6 | getDefaultDebuggerToolContext,
7 | type DebuggerToolContext,
8 | } from '../../../utils/debugger/index.ts';
9 |
10 | const debugStackSchema = z.object({
11 | debugSessionId: z
12 | .string()
13 | .optional()
14 | .describe('Debug session ID to target (defaults to current session)'),
15 | threadIndex: z.number().int().nonnegative().optional().describe('Thread index for backtrace'),
16 | maxFrames: z.number().int().positive().optional().describe('Maximum frames to return'),
17 | });
18 |
19 | export type DebugStackParams = z.infer<typeof debugStackSchema>;
20 |
21 | export async function debug_stackLogic(
22 | params: DebugStackParams,
23 | ctx: DebuggerToolContext,
24 | ): Promise<ToolResponse> {
25 | try {
26 | const output = await ctx.debugger.getStack(params.debugSessionId, {
27 | threadIndex: params.threadIndex,
28 | maxFrames: params.maxFrames,
29 | });
30 | return createTextResponse(output.trim());
31 | } catch (error) {
32 | const message = error instanceof Error ? error.message : String(error);
33 | return createErrorResponse('Failed to get stack', message);
34 | }
35 | }
36 |
37 | export default {
38 | name: 'debug_stack',
39 | description: 'Return a thread backtrace from the active debug session.',
40 | schema: debugStackSchema.shape,
41 | handler: createTypedToolWithContext<DebugStackParams, DebuggerToolContext>(
42 | debugStackSchema,
43 | debug_stackLogic,
44 | getDefaultDebuggerToolContext,
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 | }
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_breakpoint_remove.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as z from 'zod';
2 | import { ToolResponse } from '../../../types/common.ts';
3 | import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
4 | import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
5 | import {
6 | getDefaultDebuggerToolContext,
7 | type DebuggerToolContext,
8 | } from '../../../utils/debugger/index.ts';
9 |
10 | const debugBreakpointRemoveSchema = z.object({
11 | debugSessionId: z
12 | .string()
13 | .optional()
14 | .describe('Debug session ID to target (defaults to current session)'),
15 | breakpointId: z.number().int().positive().describe('Breakpoint id to remove'),
16 | });
17 |
18 | export type DebugBreakpointRemoveParams = z.infer<typeof debugBreakpointRemoveSchema>;
19 |
20 | export async function debug_breakpoint_removeLogic(
21 | params: DebugBreakpointRemoveParams,
22 | ctx: DebuggerToolContext,
23 | ): Promise<ToolResponse> {
24 | try {
25 | const output = await ctx.debugger.removeBreakpoint(params.debugSessionId, params.breakpointId);
26 | return createTextResponse(`✅ Breakpoint ${params.breakpointId} removed.\n\n${output.trim()}`);
27 | } catch (error) {
28 | const message = error instanceof Error ? error.message : String(error);
29 | return createErrorResponse('Failed to remove breakpoint', message);
30 | }
31 | }
32 |
33 | export default {
34 | name: 'debug_breakpoint_remove',
35 | description: 'Remove a breakpoint by id for the active debug session.',
36 | schema: debugBreakpointRemoveSchema.shape,
37 | handler: createTypedToolWithContext<DebugBreakpointRemoveParams, DebuggerToolContext>(
38 | debugBreakpointRemoveSchema,
39 | debug_breakpoint_removeLogic,
40 | getDefaultDebuggerToolContext,
41 | ),
42 | };
43 |
```
--------------------------------------------------------------------------------
/docs/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Troubleshooting
2 |
3 | ## Quick triage
4 | - Run the doctor tool and include output when filing issues.
5 | - Confirm Xcode and Command Line Tools are installed.
6 | - Verify required workflows are enabled in configuration.
7 | - Check simulator/device availability and permissions.
8 |
9 | ## Doctor tool
10 | The doctor tool checks system configuration and reports on all dependencies required by XcodeBuildMCP.
11 |
12 | ```bash
13 | npx --package xcodebuildmcp@latest xcodebuildmcp-doctor
14 | ```
15 |
16 | It reports on:
17 | - System and Node.js environment
18 | - Xcode installation and configuration
19 | - Required dependencies (xcodebuild, AXe, etc.)
20 | - Environment variables affecting XcodeBuildMCP
21 | - Feature availability status
22 |
23 | > [!NOTE]
24 | > You can also ask you agent to run the doctor tool which will provide a more representative output.
25 |
26 | ## Common issues
27 |
28 | ### UI automation reports missing AXe
29 | UI automation (describe/tap/swipe/type) and simulator video capture require the AXe binary. If you see a missing AXe error:
30 | - Ensure `bundled/` artifacts exist when installing from Smithery or npm.
31 | - Or set `XCODEBUILDMCP_AXE_PATH` to a known AXe binary path (preferred), or `AXE_PATH`.
32 | - Re-run the doctor tool to confirm AXe is detected.
33 |
34 | ### Tool timeouts
35 | Some clients have short tool timeouts. If you see timeouts, increase the client timeout (for example, `tool_timeout_sec = 600` in Codex).
36 |
37 | ### Missing tools
38 | If tools do not appear, verify `XCODEBUILDMCP_ENABLED_WORKFLOWS` includes the required workflow groups.
39 |
40 | ## Related docs
41 | - Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
42 | - Tools reference: [TOOLS.md](TOOLS.md)
43 | - Privacy and telemetry: [PRIVACY.md](PRIVACY.md)
44 |
```
--------------------------------------------------------------------------------
/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/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/utils/tool-registry.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { loadWorkflowGroups } from '../core/plugin-registry.ts';
3 | import { ToolResponse } from '../types/common.ts';
4 | import { log } from './logger.ts';
5 | import { recordRuntimeRegistration } from './runtime-registry.ts';
6 | import { resolveSelectedWorkflows } from './workflow-selection.ts';
7 |
8 | /**
9 | * Register workflows (selected list or all when omitted)
10 | */
11 | export async function registerWorkflows(
12 | server: McpServer,
13 | workflowNames: string[] = [],
14 | ): Promise<void> {
15 | const workflowGroups = await loadWorkflowGroups();
16 | const selection = resolveSelectedWorkflows(workflowGroups, workflowNames);
17 | let registeredCount = 0;
18 | const registeredTools = new Set<string>();
19 | const registeredWorkflows = new Set<string>();
20 |
21 | for (const workflow of selection.selectedWorkflows) {
22 | registeredWorkflows.add(workflow.directoryName);
23 | for (const tool of workflow.tools) {
24 | if (registeredTools.has(tool.name)) {
25 | continue;
26 | }
27 | server.registerTool(
28 | tool.name,
29 | {
30 | description: tool.description ?? '',
31 | inputSchema: tool.schema,
32 | annotations: tool.annotations,
33 | },
34 | (args: unknown): Promise<ToolResponse> => tool.handler(args as Record<string, unknown>),
35 | );
36 | registeredTools.add(tool.name);
37 | registeredCount += 1;
38 | }
39 | }
40 |
41 | recordRuntimeRegistration({
42 | enabledWorkflows: [...registeredWorkflows],
43 | enabledTools: [...registeredTools],
44 | });
45 |
46 | if (selection.selectedNames) {
47 | log(
48 | 'info',
49 | `✅ Registered ${registeredCount} tools from workflows: ${selection.selectedNames.join(', ')}`,
50 | );
51 | } else {
52 | log('info', `✅ Registered ${registeredCount} tools in static mode.`);
53 | }
54 | }
55 |
```
--------------------------------------------------------------------------------
/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 | expect(result.content).toHaveLength(1);
37 | expect(typeof result.content[0].text).toBe('string');
38 | const parsed = JSON.parse(result.content[0].text as string);
39 | expect(parsed).toEqual({});
40 | });
41 |
42 | it('should return current defaults when set', async () => {
43 | sessionStore.setDefaults({ scheme: 'MyScheme', simulatorId: 'SIM-123' });
44 | const result = await plugin.handler();
45 | expect(result.isError).toBe(false);
46 | expect(result.content).toHaveLength(1);
47 | expect(typeof result.content[0].text).toBe('string');
48 | const parsed = JSON.parse(result.content[0].text as string);
49 | expect(parsed.scheme).toBe('MyScheme');
50 | expect(parsed.simulatorId).toBe('SIM-123');
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 '@modelcontextprotocol/sdk/server/mcp.js';
16 | import { StdioServerTransport } from '@modelcontextprotocol/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 |
```