This is page 1 of 12. Use http://codebase.md/cameroncooke/xcodebuildmcp?lines=false&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.2.1
```
--------------------------------------------------------------------------------
/.cursorrules:
--------------------------------------------------------------------------------
```
AGENTS.md
```
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
node_modules
build
dist
coverage
*.json
*.md
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/.gitignore:
--------------------------------------------------------------------------------
```
# xcode-build-server files
buildServer.json
.compile
# Local build artifacts
.build/
```
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
```javascript
export default {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
endOfLine: 'auto',
};
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/.gitignore:
--------------------------------------------------------------------------------
```
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
```
--------------------------------------------------------------------------------
/example_projects/spm/.gitignore:
--------------------------------------------------------------------------------
```
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# TypeScript build output
dist/
/build/
*.tsbuildinfo
# Auto-generated files
# IDE and editor files
.idea/
.vscode/*
!.vscode/mcp.json
!.vscode/launch.json
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/extensions.json
*.swp
*.swo
.DS_Store
.env
.env.local
.env.*.local
# Logs
logs/
*.log
# Test coverage
coverage/
# macOS specific
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Xcode
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings
*.xcuserstate
project.xcworkspace/
xcuserdata/
# Debug files
.nyc_output/
*.map
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.test
.env.production
# parcel-bundler cache
.cache
.parcel-cache
# Windsurf
.windsurfrules
# Sentry Config File
.sentryclirc
# Claude Config File
**/.claude/settings.local.json
# incremental builds
Makefile
buildServer.json
# Bundled AXe artifacts (generated during build)
bundled/
.smithery/
/.mcpregistry_github_token
/.mcpregistry_registry_token
/key.pem
.mcpli
.factory
DerivedData
```
--------------------------------------------------------------------------------
/.github/workflows/README.md:
--------------------------------------------------------------------------------
```markdown
# Test workflow trigger
```
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
```markdown
# XcodeBuildMCP Documentation
## Start here
- [Getting started](GETTING_STARTED.md)
- [Configuration and options](CONFIGURATION.md)
- [Tools reference (all workflows/tools)](TOOLS.md)
- [Troubleshooting and doctor](TROUBLESHOOTING.md)
- [Privacy and telemetry](PRIVACY.md)
- [Demos](DEMOS.md)
## User guides
- [Product overview and rationale](OVERVIEW.md)
- [Session defaults and opt-out](SESSION_DEFAULTS.md)
- [Device code signing notes](DEVICE_CODE_SIGNING.md)
## Developer docs
- [Developer documentation index](dev/README.md)
- [Contributing guide](dev/CONTRIBUTING.md)
- [Architecture](dev/ARCHITECTURE.md)
- [Testing](dev/TESTING.md)
- [Code quality rules](dev/CODE_QUALITY.md)
- [Plugin development](dev/PLUGIN_DEVELOPMENT.md)
- [Release process](dev/RELEASE_PROCESS.md)
## Generated docs
- [TOOLS.md](TOOLS.md) is generated by `scripts/update-tools-docs.ts`.
```
--------------------------------------------------------------------------------
/docs/dev/README.md:
--------------------------------------------------------------------------------
```markdown
# Developer Documentation
## Getting started for contributors
- [Contributing guide](CONTRIBUTING.md)
- [Code quality standards](CODE_QUALITY.md)
- [Testing guidelines](TESTING.md)
- [Architecture overview](ARCHITECTURE.md)
## Build and tooling
- [ESLint and type safety](ESLINT_TYPE_SAFETY.md)
- [Node.js and runtime notes](NODEJS_2025.md)
- [Smithery integration notes](SMITHERY.md)
## Release and maintenance
- [Release process](RELEASE_PROCESS.md)
- [Manual testing](MANUAL_TESTING.md)
## Deep dives and plans
- [Plugin development](PLUGIN_DEVELOPMENT.md)
- [Reloaderoo docs](RELOADEROO.md)
- [Reloaderoo primer](RELOADEROO_XCODEBUILDMCP_PRIMER.md)
- [Reloaderoo for XcodeBuildMCP](RELOADEROO_FOR_XCODEBUILDMCP.md)
- [Zod migration guide](ZOD_MIGRATION_GUIDE.md)
- [Test runner env plan](TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md)
- [Session management plan](session_management_plan.md)
- [Session-aware migration todo](session-aware-migration-todo.md)
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
<img src="banner.png" alt="XcodeBuild MCP" width="600"/>
A Model Context Protocol (MCP) server that provides Xcode-related tools for integration with AI assistants and other MCP clients.
[](https://github.com/cameroncooke/XcodeBuildMCP/actions/workflows/ci.yml)
[](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)
## Easy install
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.
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client client-name
```
> [!IMPORTANT]
> 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.
<details>
<summary>Cursor</summary>
<br />
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client cursor
```
<br />
</details>
<details>
<summary>Codex CLI</summary>
<br />
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client codex
```
<br />
</details>
<details>
<summary>Claude Code</summary>
<br />
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client claude-code
```
<br />
</details>
<details>
<summary>Claude Desktop</summary>
<br />
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client claude
```
<br />
</details>
<details>
<summary>VS Code</summary>
<br />
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client vscode
```
<br />
</details>
<details>
<summary>Windsurf</summary>
<br />
```bash
npx -y @smithery/cli@latest install cameroncooke/xcodebuildmcp --client windsurf
```
<br />
</details>
<br />
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)
## Requirements
- macOS 14.5 or later
- Xcode 16.x or later
- Node.js 18.x or later
## Notes
- XcodeBuildMCP requests xcodebuild to skip macro validation to avoid errors when building projects that use Swift Macros.
- Device tools require code signing to be configured in Xcode. See [docs/DEVICE_CODE_SIGNING.md](docs/DEVICE_CODE_SIGNING.md).
## Privacy
XcodeBuildMCP uses Sentry for error telemetry. For more information or to opt out of error telemetry see [docs/PRIVACY.md](docs/PRIVACY.md).
## Documentation
- Getting started: [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)
- Configuration and options: [docs/CONFIGURATION.md](docs/CONFIGURATION.md)
- Tools reference: [docs/TOOLS.md](docs/TOOLS.md)
- Troubleshooting: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
- Privacy: [docs/PRIVACY.md](docs/PRIVACY.md)
- Contributing: [docs/dev/CONTRIBUTING.md](docs/dev/CONTRIBUTING.md)
## Licence
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
```markdown
AGENTS.md
```
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
```markdown
# Development Rules
## Code Quality
- No `any` types unless absolutely necessary
- Check node_modules for external API type definitions instead of guessing
- **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.
- NEVER remove or downgrade code to fix type errors from outdated dependencies; upgrade the dependency instead
- Always ask before removing functionality or code that appears to be intentional
- Follow TypeScript best practices
## Commands
- NEVER commit unless user asks
## GitHub
When reading issues:
- Always read all comments on the issue
-
## Tools
- GitHub CLI for issues/PRs
-
## Style
- Keep answers short and concise
- No emojis in commits, issues, PR comments, or code
- No fluff or cheerful filler text
- Technical prose only, be kind but direct (e.g., "Thanks @user" not "Thanks so much @user!")
## Docs
- If modifying or adding/removing tools run `npm run docs:update` to update the TOOLS.md file, never edit this file directly.
-
### Changelog
Location: `CHANGELOG.md`
#### Format
Use these sections under `## [Unreleased]`:
- `### Added` - New features
- `### Changed` - Changes to existing functionality
- `### Fixed` - Bug fixes
- `### Removed` - Removed features
-
#### Rules
- Before adding entries, read the full `[Unreleased]` section to see which subsections already exist
- New entries ALWAYS go under `## [Unreleased]` section
- Append to existing subsections (e.g., `### Fixed`), do not create duplicates
- NEVER modify already-released version sections (e.g., `## [0.12.2]`)
- Each version section is immutable once released
-
#### Attribution
- **Internal changes (from issues)**: `Fixed foo bar ([#123](https://github.com/cameroncook/XcodeBuildMCP/issues/123))`
- **External contributions**: `Added feature X ([#456](https://github.com/cameroncook/XcodeBuildMCP/pull/456) by [@username](https://github.com/username))`
## **CRITICAL** Tool Usage Rules **CRITICAL**
- NEVER use sed/cat to read a file or a range of a file. Always use the native read tool.
- You MUST read every file you modify in full before editing.
```
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
```markdown
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[email protected].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
```
--------------------------------------------------------------------------------
/docs/dev/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
# Contributing
Contributions are welcome! Here's how you can help improve XcodeBuildMCP.
- [Local development setup](#local-development-setup)
- [Prerequisites](#prerequisites)
- [Optional: Enabling UI Automation](#optional-enabling-ui-automation)
- [Installation](#installation)
- [Configure your MCP client](#configure-your-mcp-client)
- [Developing using VS Code](#developing-using-vs-code)
- [Debugging](#debugging)
- [MCP Inspector (Basic Debugging)](#mcp-inspector-basic-debugging)
- [Reloaderoo (Advanced Debugging) - **RECOMMENDED**](#reloaderoo-advanced-debugging---recommended)
- [1. Proxy Mode (Hot-Reloading)](#1-proxy-mode-hot-reloading)
- [2. Inspection Mode (Raw MCP Debugging)](#2-inspection-mode-raw-mcp-debugging)
- [Workflow Selection Testing](#workflow-selection-testing)
- [Using XcodeBuildMCP doctor tool](#using-xcodebuildmcp-doctor-tool)
- [Development Workflow with Reloaderoo](#development-workflow-with-reloaderoo)
- [Architecture and Code Standards](#architecture-and-code-standards)
- [Code Quality Requirements](#code-quality-requirements)
- [Testing Standards](#testing-standards)
- [Pre-Commit Checklist](#pre-commit-checklist)
- [Making changes](#making-changes)
- [Plugin Development](#plugin-development)
- [Quick Plugin Development Checklist](#quick-plugin-development-checklist)
- [Working with Project Templates](#working-with-project-templates)
- [Template Repositories](#template-repositories)
- [Local Template Development](#local-template-development)
- [Template Versioning](#template-versioning)
- [Testing Template Changes](#testing-template-changes)
- [Testing](#testing)
- [Submitting](#submitting)
- [Code of Conduct](#code-of-conduct)
## Local development setup
### Prerequisites
In addition to the prerequisites mentioned in the [Getting started](README.md/#getting-started) section of the README, you will also need:
- Node.js (v18 or later)
- npm
#### Optional: Enabling UI Automation
When running locally, you'll need to install AXe for UI automation:
```bash
# Install axe (required for UI automation)
brew tap cameroncooke/axe
brew install axe
```
### Installation
1. Clone the repository
2. Install dependencies:
```
npm install
```
3. Build the project:
```
npm run build
```
4. Start the server:
```
node build/index.js
```
### Configure your MCP client
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:
```json
{
"mcpServers": {
"XcodeBuildMCP": {
"command": "node",
"args": [
"/path_to/XcodeBuildMCP/build/index.js"
]
}
}
}
```
### Developing using VS Code
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.
To make your development workflow in VS Code more efficient:
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.
2. **Launch the Debugger**: Press `F5` to attach the Node.js debugger.
Once these steps are completed, you can utilize the tools from the MCP server you are developing within this repository in agent mode.
For more details on how to work with MCP servers in VS Code see: https://code.visualstudio.com/docs/copilot/chat/mcp-servers
### Debugging
#### MCP Inspector (Basic Debugging)
You can use MCP Inspector for basic debugging via:
```bash
npm run inspect
```
or if you prefer the explicit command:
```bash
npx @modelcontextprotocol/inspector node build/index.js
```
#### Reloaderoo (Advanced Debugging) - **RECOMMENDED**
For development and debugging, we strongly recommend using **Reloaderoo**, which provides hot-reloading capabilities and advanced debugging features for MCP servers.
Reloaderoo operates in two modes:
##### 1. Proxy Mode (Hot-Reloading)
Provides transparent hot-reloading without disconnecting your MCP client:
```bash
# Install reloaderoo globally
npm install -g reloaderoo
# Start XcodeBuildMCP through reloaderoo proxy
reloaderoo -- node build/index.js
```
**Benefits**:
- 🔄 Hot-reload server without restarting client
- 🛠️ Automatic `restart_server` tool added to toolset
- 🌊 Transparent MCP protocol forwarding
- 📡 Full protocol support (tools, resources, prompts)
**MCP Client Configuration for Proxy Mode**:
```json
"XcodeBuildMCP": {
"command": "reloaderoo",
"args": ["--", "node", "/path/to/XcodeBuildMCP/build/index.js"],
"env": {
"XCODEBUILDMCP_DEBUG": "true"
}
}
```
##### 2. Inspection Mode (Raw MCP Debugging)
Exposes debug tools for making raw MCP protocol calls and inspecting server responses:
```bash
# Start reloaderoo in inspection mode
reloaderoo inspect mcp -- node build/index.js
```
**Available Debug Tools**:
- `list_tools` - List all server tools
- `call_tool` - Execute any server tool with parameters
- `list_resources` - List all server resources
- `read_resource` - Read any server resource
- `list_prompts` - List all server prompts
- `get_prompt` - Get any server prompt
- `get_server_info` - Get comprehensive server information
- `ping` - Test server connectivity
**MCP Client Configuration for Inspection Mode**:
```json
"XcodeBuildMCP": {
"command": "node",
"args": [
"/path/to/reloaderoo/dist/bin/reloaderoo.js",
"inspect", "mcp",
"--working-dir", "/path/to/XcodeBuildMCP",
"--",
"node", "/path/to/XcodeBuildMCP/build/index.js"
],
"env": {
"XCODEBUILDMCP_DEBUG": "true"
}
}
```
#### Workflow Selection Testing
Test full vs. selective workflow registration during development:
```bash
# Test full tool registration (default)
reloaderoo inspect mcp -- node build/index.js
# Test selective workflow registration
XCODEBUILDMCP_ENABLED_WORKFLOWS=simulator,device reloaderoo inspect mcp -- node build/index.js
```
**Key Differences to Test**:
- **Full Registration**: All tools are available immediately via `list_tools`
- **Selective Registration**: Only tools from the selected workflows (plus `session-management`) are available
#### Using XcodeBuildMCP doctor tool
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.
> [!NOTE]
> 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:
> ```bash
> npm run doctor
> ```
#### Development Workflow with Reloaderoo
1. **Start Development Session**:
```bash
# Terminal 1: Start in hot-reload mode
reloaderoo -- node build/index.js
# Terminal 2: Start build watcher
npm run build:watch
```
2. **Make Changes**: Edit source code in `src/`
3. **Test Changes**: Ask your AI client to restart the server:
```
"Please restart the MCP server to load my changes"
```
The AI will automatically call the `restart_server` tool provided by reloaderoo.
4. **Verify Changes**: New functionality immediately available without reconnecting client
## Architecture and Code Standards
Before making changes, please familiarize yourself with:
- [ARCHITECTURE.md](ARCHITECTURE.md) - Comprehensive architectural overview
- [CLAUDE.md](../../CLAUDE.md) - AI assistant guidelines and testing principles
- [TOOLS.md](../TOOLS.md) - Complete tool documentation
- [CONFIGURATION.md](../CONFIGURATION.md) - Tool configuration options
### Code Quality Requirements
1. **Follow existing code patterns and structure**
2. **Use TypeScript strictly** - no `any` types, proper typing throughout
3. **Add proper error handling and logging** - all failures must set `isError: true`
4. **Update documentation for new features**
5. **Test with example projects before submitting**
### Testing Standards
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.
**Key Principles (Summary):**
- **No Vitest Mocking**: All forms of `vi.mock`, `vi.fn`, `vi.spyOn`, etc., are strictly forbidden.
- **Dependency Injection**: All external dependencies (command execution, file system access) must be injected into tool logic functions using the `CommandExecutor` and `FileSystemExecutor` patterns.
- **Test Production Code**: Tests must import and execute the actual tool logic, not mock implementations.
- **Comprehensive Coverage**: Tests must cover input validation, command generation, and output processing.
Please read [TESTING.md](TESTING.md) in its entirety before writing tests.
### Pre-Commit Checklist
**MANDATORY**: Run these commands before any commit and ensure they all pass:
```bash
# 1. Run linting (must pass with 0 errors)
npm run lint
# 2. Run formatting (must format all files)
npm run format
# 3. Run build (must compile successfully)
npm run build
# 4. Run tests (all tests must pass)
npm test
```
**NO EXCEPTIONS**: Code that fails any of these commands cannot be committed.
## Making changes
1. Fork the repository and create a new branch
2. Follow the TypeScript best practices and existing code style
3. Add proper parameter validation and error handling
## Plugin Development
For comprehensive instructions on creating new tools and workflow groups, see our dedicated [Plugin Development Guide](PLUGIN_DEVELOPMENT.md).
The plugin development guide covers:
- Auto-discovery system architecture
- Tool creation with dependency injection patterns
- Workflow group organization
- Testing guidelines and patterns
- Workflow registration and selection
### Quick Plugin Development Checklist
1. Choose appropriate workflow directory in `src/mcp/tools/`
2. Follow naming conventions: `{action}_{target}_{specifier}_{projectType}`
3. Use dependency injection pattern with separate logic functions
4. Create comprehensive tests using `createMockExecutor()`
5. Add workflow metadata if creating new workflow group
See [PLUGIN_DEVELOPMENT.md](PLUGIN_DEVELOPMENT.md) for complete details.
### Working with Project Templates
XcodeBuildMCP uses external template repositories for the iOS and macOS project scaffolding features. These templates are maintained separately to allow independent versioning and updates.
#### Template Repositories
- **iOS Template**: [XcodeBuildMCP-iOS-Template](https://github.com/cameroncooke/XcodeBuildMCP-iOS-Template)
- **macOS Template**: [XcodeBuildMCP-macOS-Template](https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template)
#### Local Template Development
When developing or testing changes to the templates:
1. Clone the template repository you want to work on:
```bash
git clone https://github.com/cameroncooke/XcodeBuildMCP-iOS-Template.git
git clone https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template.git
```
2. Set the appropriate environment variable to use your local template:
```bash
# For iOS template development
export XCODEBUILDMCP_IOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-iOS-Template
# For macOS template development
export XCODEBUILDMCP_MACOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-macOS-Template
```
3. When using MCP clients, add these environment variables to your MCP configuration:
```json
"XcodeBuildMCP": {
"command": "node",
"args": ["/path_to/XcodeBuildMCP/build/index.js"],
"env": {
"XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-iOS-Template",
"XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-macOS-Template"
}
}
```
4. The scaffold tools will use your local templates instead of downloading from GitHub releases.
#### Template Versioning
- Templates are versioned independently from XcodeBuildMCP
- The default template version is specified in `package.json` under `templateVersion`
- You can override the template version with `XCODEBUILD_MCP_TEMPLATE_VERSION` environment variable
- To update the default template version:
1. Update `templateVersion` in `package.json`
2. Run `npm run build` to regenerate version.ts
3. Create a new XcodeBuildMCP release
#### Testing Template Changes
1. Make changes to your local template
2. Test scaffolding with your changes using the local override
3. Verify the scaffolded project builds and runs correctly
4. Once satisfied, create a PR in the template repository
5. After merging, create a new release in the template repository using the release script
## Testing
1. Build the project with `npm run build`
2. Test your changes with MCP Inspector
3. Verify tools work correctly with different MCP clients
## Submitting
1. Run `npm run lint` to check for linting issues (use `npm run lint:fix` to auto-fix)
2. Run `npm run format:check` to verify formatting (use `npm run format` to fix)
3. Update documentation if you've added or modified features
4. Add your changes to the CHANGELOG.md file
5. Push your changes and create a pull request with a clear description
6. Link any related issues
For major changes or new features, please open an issue first to discuss your proposed changes.
## Code of Conduct
Please follow our [Code of Conduct](../../CODE_OF_CONDUCT.md) and community guidelines.
```
--------------------------------------------------------------------------------
/src/utils/capabilities.ts:
--------------------------------------------------------------------------------
```typescript
```
--------------------------------------------------------------------------------
/example_projects/iOS/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
{}
```
--------------------------------------------------------------------------------
/example_projects/spm/Sources/spm/main.swift:
--------------------------------------------------------------------------------
```swift
print("Hello, world!")
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
```yaml
blank_issues_enabled: false
```
--------------------------------------------------------------------------------
/.cursor/environment.json:
--------------------------------------------------------------------------------
```json
{
"agentCanUpdateSnapshot": true
}
```
--------------------------------------------------------------------------------
/src/utils/version/index.ts:
--------------------------------------------------------------------------------
```typescript
export { version } from '../../version.ts';
```
--------------------------------------------------------------------------------
/src/utils/test/index.ts:
--------------------------------------------------------------------------------
```typescript
export { handleTestLogic } from '../test-common.ts';
```
--------------------------------------------------------------------------------
/src/utils/template/index.ts:
--------------------------------------------------------------------------------
```typescript
export { TemplateManager } from '../template-manager.ts';
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
```json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTest/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
```json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTest/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
```json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTest/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
```json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTest/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
```json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/src/utils/plugin-registry/index.ts:
--------------------------------------------------------------------------------
```typescript
export { loadWorkflowGroups, loadPlugins } from '../../core/plugin-registry.ts';
```
--------------------------------------------------------------------------------
/src/utils/xcodemake/index.ts:
--------------------------------------------------------------------------------
```typescript
export { isXcodemakeEnabled, isXcodemakeAvailable, doesMakefileExist } from '../xcodemake.ts';
```
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
```yaml
# These are supported funding model platforms
github: cameroncooke
buy_me_a_coffee: cameroncooke
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/boot_sim.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from simulator to avoid duplication
export { default } from '../simulator/boot_sim.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/open_sim.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from simulator to avoid duplication
export { default } from '../simulator/open_sim.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/list_sims.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from simulator to avoid duplication
export { default } from '../simulator/list_sims.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/screenshot.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from ui-testing to avoid duplication
export { default } from '../ui-testing/screenshot.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/describe_ui.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from ui-testing to avoid duplication
export { default } from '../ui-testing/describe_ui.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/stop_device_log_cap.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from logging to complete workflow
export { default } from '../logging/stop_device_log_cap.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/clean.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified clean tool for macos-project workflow
export { default } from '../utilities/clean.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/clean.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified clean tool for device-project workflow
export { default } from '../utilities/clean.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/start_device_log_cap.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from logging to complete workflow
export { default } from '../logging/start_device_log_cap.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/clean.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified clean tool for simulator-project workflow
export { default } from '../utilities/clean.ts';
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/build/project-config
runtime: "typescript"
target: "local"
```
--------------------------------------------------------------------------------
/src/utils/video-capture/index.ts:
--------------------------------------------------------------------------------
```typescript
export {
startSimulatorVideoCapture,
stopSimulatorVideoCapture,
type AxeHelpers,
} from '../video_capture.ts';
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
```json
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTest/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
```json
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTest/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
```json
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/discover_projs.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from project-discovery to complete workflow
export { default } from '../project-discovery/discover_projs.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/discover_projs.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from project-discovery to complete workflow
export { default } from '../project-discovery/discover_projs.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/discover_projs.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from project-discovery to complete workflow
export { default } from '../project-discovery/discover_projs.ts';
```
--------------------------------------------------------------------------------
/src/version.ts:
--------------------------------------------------------------------------------
```typescript
export const version = '1.15.1';
export const iOSTemplateVersion = 'v1.0.8';
export const macOSTemplateVersion = 'v1.0.5';
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/show_build_settings.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified tool for macos-project workflow
export { default } from '../project-discovery/show_build_settings.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/show_build_settings.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified tool for device-project workflow
export { default } from '../project-discovery/show_build_settings.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/get_app_bundle_id.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from project-discovery to complete workflow
export { default } from '../project-discovery/get_app_bundle_id.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/get_mac_bundle_id.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from project-discovery to complete workflow
export { default } from '../project-discovery/get_mac_bundle_id.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/get_app_bundle_id.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export from project-discovery to complete workflow
export { default } from '../project-discovery/get_app_bundle_id.ts';
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
{
"recommendations": [
"dbaeumer.vscode-eslint"
],
"unwantedRecommendations": [
"esbenp.prettier-vscode"
]
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/show_build_settings.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified tool for simulator-project workflow
export { default } from '../project-discovery/show_build_settings.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/list_schemes.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified list_schemes tool for macos-project workflow
export { default } from '../project-discovery/list_schemes.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/list_schemes.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified list_schemes tool for device-project workflow
export { default } from '../project-discovery/list_schemes.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/list_schemes.ts:
--------------------------------------------------------------------------------
```typescript
// Re-export unified list_schemes tool for simulator-project workflow
export { default } from '../project-discovery/list_schemes.ts';
```
--------------------------------------------------------------------------------
/src/utils/logging/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Focused logging facade.
* Prefer importing from 'utils/logging/index.js' instead of the legacy utils barrel.
*/
export { log } from '../logger.ts';
```
--------------------------------------------------------------------------------
/src/utils/validation/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Focused validation facade.
* Prefer importing from 'utils/validation/index.js' instead of the legacy utils barrel.
*/
export * from '../validation.ts';
```
--------------------------------------------------------------------------------
/src/utils/axe/index.ts:
--------------------------------------------------------------------------------
```typescript
export {
createAxeNotAvailableResponse,
getAxePath,
getBundledAxeEnvironment,
areAxeToolsAvailable,
isAxeAtLeastVersion,
resolveAxeBinary,
} from '../axe-helpers.ts';
```
--------------------------------------------------------------------------------
/src/mcp/tools/doctor/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'System Doctor',
description:
'Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability.',
};
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorApp/CalculatorApp.swift:
--------------------------------------------------------------------------------
```swift
import SwiftUI
import CalculatorAppFeature
@main
struct CalculatorApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
#Preview {
ContentView()
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'Simulator Debugging',
description:
'Interactive iOS Simulator debugging tools: attach LLDB, manage breakpoints, inspect stack/variables, and run LLDB commands.',
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'macOS Development',
description:
'Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications.',
};
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTest/MCPTestApp.swift:
--------------------------------------------------------------------------------
```swift
//
// MCPTestApp.swift
// MCPTest
//
// Created by Cameron on 16/02/2025.
//
import SwiftUI
@main
struct MCPTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTest/MCPTestApp.swift:
--------------------------------------------------------------------------------
```swift
//
// MCPTestApp.swift
// MCPTest
//
// Created by Cameron on 16/02/2025.
//
import SwiftUI
@main
struct MCPTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'Project Discovery',
description:
'Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information.',
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'Swift Package Manager',
description:
'Swift Package Manager operations for building, testing, running, and managing Swift packages and dependencies. Complete SPM workflow support.',
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/utilities/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'Project Utilities',
description:
'Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files.',
};
```
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["vitest/globals", "node"],
"allowJs": true,
"noEmit": true
},
"include": ["src/**/*.test.ts", "tests-vitest/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/logging/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'Log Capture & Management',
description:
'Log capture and management tools for iOS simulators and physical devices. Start, stop, and analyze application and system logs during development and testing.',
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/ui-testing/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'UI Testing & Automation',
description:
'UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows.',
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'iOS Simulator Development',
description:
'Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators.',
};
```
--------------------------------------------------------------------------------
/src/utils/log-capture/index.ts:
--------------------------------------------------------------------------------
```typescript
import { activeLogSessions, startLogCapture, stopLogCapture } from '../log_capture.ts';
export function listActiveSimulatorLogSessionIds(): string[] {
return Array.from(activeLogSessions.keys()).sort();
}
export { startLogCapture, stopLogCapture };
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTestTests/MCPTestTests.swift:
--------------------------------------------------------------------------------
```swift
//
// MCPTestTests.swift
// MCPTestTests
//
// Created by Cameron on 15/12/2025.
//
import Testing
struct MCPTestTests {
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'iOS Device Development',
description:
'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.',
};
```
--------------------------------------------------------------------------------
/build-plugins/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"target": "ES2022",
"outDir": "../build-plugins-dist",
"rootDir": ".",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/tsconfig.tests.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["vitest/globals", "node"]
},
"include": ["src/**/*.test.ts", "tests-vitest/**/*.ts"],
"exclude": [
"node_modules",
"plugins/**/*",
"src/core/generated-plugins.ts",
"src/core/generated-resources.ts"
]
}
```
--------------------------------------------------------------------------------
/scripts/generate-loaders.ts:
--------------------------------------------------------------------------------
```typescript
import { generateResourceLoaders, generateWorkflowLoaders } from '../build-plugins/plugin-discovery.ts';
async function main(): Promise<void> {
await generateWorkflowLoaders();
await generateResourceLoaders();
}
main().catch((error) => {
console.error('Failed to generate plugin/resource loaders:', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/utils/log-capture/device-log-sessions.ts:
--------------------------------------------------------------------------------
```typescript
import type { ChildProcess } from 'child_process';
import type * as fs from 'fs';
export interface DeviceLogSession {
process: ChildProcess;
logFilePath: string;
deviceUuid: string;
bundleId: string;
logStream?: fs.WriteStream;
hasEnded: boolean;
}
export const activeDeviceLogSessions = new Map<string, DeviceLogSession>();
```
--------------------------------------------------------------------------------
/src/mcp/tools/session-management/index.ts:
--------------------------------------------------------------------------------
```typescript
export const workflow = {
name: 'session-management',
description:
'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.',
};
```
--------------------------------------------------------------------------------
/src/utils/responses/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Focused responses facade.
* Prefer importing from 'utils/responses/index.js' instead of the legacy utils barrel.
*/
export { createTextResponse } from '../validation.ts';
export {
createErrorResponse,
DependencyError,
AxeError,
SystemError,
ValidationError,
} from '../errors.ts';
// Types
export type { ToolResponse } from '../../types/common.ts';
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTest/ContentView.swift:
--------------------------------------------------------------------------------
```swift
//
// ContentView.swift
// MCPTest
//
// Created by Cameron on 16/02/2025.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/project-scaffolding/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Project Scaffolding workflow
*
* Provides tools for creating new iOS and macOS projects from templates.
* These tools are used at project inception to bootstrap new applications
* with best practices and standard configurations.
*/
export const workflow = {
name: 'Project Scaffolding',
description:
'Tools for creating new iOS and macOS projects from templates. Bootstrap new applications with best practices, standard configurations, and modern project structures.',
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/session-management/session_show_defaults.ts:
--------------------------------------------------------------------------------
```typescript
import { sessionStore } from '../../../utils/session-store.ts';
import type { ToolResponse } from '../../../types/common.ts';
export default {
name: 'session-show-defaults',
description: 'Show current session defaults.',
schema: {},
annotations: {
title: 'Show Session Defaults',
readOnlyHint: true,
},
handler: async (): Promise<ToolResponse> => {
const current = sessionStore.getAll();
return { content: [{ type: 'text', text: JSON.stringify(current, null, 2) }], isError: false };
},
};
```
--------------------------------------------------------------------------------
/src/utils/debugger/tool-context.ts:
--------------------------------------------------------------------------------
```typescript
import type { CommandExecutor } from '../execution/index.ts';
import { getDefaultCommandExecutor } from '../execution/index.ts';
import type { DebuggerManager } from './debugger-manager.ts';
import { getDefaultDebuggerManager } from './index.ts';
export type DebuggerToolContext = {
executor: CommandExecutor;
debugger: DebuggerManager;
};
export function getDefaultDebuggerToolContext(): DebuggerToolContext {
return {
executor: getDefaultCommandExecutor(),
debugger: getDefaultDebuggerManager(),
};
}
```
--------------------------------------------------------------------------------
/src/utils/CommandExecutor.ts:
--------------------------------------------------------------------------------
```typescript
import { ChildProcess } from 'child_process';
export interface CommandExecOptions {
env?: Record<string, string>;
cwd?: string;
}
/**
* Command executor function type for dependency injection
*/
export type CommandExecutor = (
command: string[],
logPrefix?: string,
useShell?: boolean,
opts?: CommandExecOptions,
detached?: boolean,
) => Promise<CommandResponse>;
/**
* Command execution response interface
*/
export interface CommandResponse {
success: boolean;
output: string;
error?: string;
process: ChildProcess;
exitCode?: number;
}
```
--------------------------------------------------------------------------------
/docs/PRIVACY.md:
--------------------------------------------------------------------------------
```markdown
# Privacy
XcodeBuildMCP uses Sentry for error monitoring and diagnostics. This helps track crashes and unexpected errors to improve reliability.
## What is sent to Sentry
- Error-level logs and diagnostic information only.
- Error logs may include error messages, stack traces, and in some cases file paths or project names.
## Opting out
To disable error telemetry, set:
```json
"env": {
"XCODEBUILDMCP_SENTRY_DISABLED": "true"
}
```
## Related docs
- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
```
--------------------------------------------------------------------------------
/src/utils/execution/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Focused execution facade.
* Prefer importing from 'utils/execution/index.js' instead of the legacy utils barrel.
*/
export { getDefaultCommandExecutor, getDefaultFileSystemExecutor } from '../command.ts';
export { getDefaultInteractiveSpawner } from './interactive-process.ts';
// Types
export type { CommandExecutor, CommandResponse, CommandExecOptions } from '../CommandExecutor.ts';
export type { FileSystemExecutor } from '../FileSystemExecutor.ts';
export type {
InteractiveProcess,
InteractiveSpawner,
SpawnInteractiveOptions,
} from './interactive-process.ts';
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
```json
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTest/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
```json
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Simulator Management workflow
*
* Provides tools for working with simulators like booting and opening simulators, launching apps,
* listing sims, stopping apps, erasing simulator content and settings, and setting sim environment
* options like location, network, statusbar and appearance.
*/
export const workflow = {
name: 'Simulator Management',
description:
'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.',
};
```
--------------------------------------------------------------------------------
/docs/DEMOS.md:
--------------------------------------------------------------------------------
```markdown
# Demos
## Autonomously fixing build errors in Cursor

## Utilising the new UI automation and screen capture features

## Building and running iOS app in Claude Desktop
https://github.com/user-attachments/assets/e3c08d75-8be6-4857-b4d0-9350b26ef086
## Related docs
- Getting started: [GETTING_STARTED.md](GETTING_STARTED.md)
- Tools reference: [TOOLS.md](TOOLS.md)
- Session defaults: [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md)
```
--------------------------------------------------------------------------------
/src/utils/debugger/types.ts:
--------------------------------------------------------------------------------
```typescript
export type DebuggerBackendKind = 'lldb-cli' | 'dap';
export interface DebugSessionInfo {
id: string;
backend: DebuggerBackendKind;
simulatorId: string;
pid: number;
createdAt: number;
lastUsedAt: number;
}
export type BreakpointSpec =
| { kind: 'file-line'; file: string; line: number }
| { kind: 'function'; name: string };
export interface BreakpointInfo {
id: number;
spec: BreakpointSpec;
rawOutput: string;
}
export type DebugExecutionStatus = 'running' | 'stopped' | 'unknown' | 'terminated';
export type DebugExecutionState = {
status: DebugExecutionStatus;
reason?: string;
description?: string;
threadId?: number;
};
```
--------------------------------------------------------------------------------
/docs/DEVICE_CODE_SIGNING.md:
--------------------------------------------------------------------------------
```markdown
# Device Code Signing
For device deployment features to work, code signing must be configured in Xcode before using XcodeBuildMCP device tools.
## One-time setup in Xcode
1. Open your project in Xcode.
2. Select your project target.
3. Open the "Signing & Capabilities" tab.
4. Enable "Automatically manage signing" and select your development team.
5. Ensure a valid provisioning profile is selected.
## What XcodeBuildMCP can and cannot do
- Can build, install, launch, and test once signing is configured.
- Cannot configure code signing automatically.
## Related docs
- Tools reference: [TOOLS.md](TOOLS.md)
- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
```
--------------------------------------------------------------------------------
/src/core/generated-resources.ts:
--------------------------------------------------------------------------------
```typescript
// AUTO-GENERATED - DO NOT EDIT
// This file is generated by the plugin discovery esbuild plugin
export const RESOURCE_LOADERS = {
devices: async () => {
const module = await import('../mcp/resources/devices.ts');
return module.default;
},
doctor: async () => {
const module = await import('../mcp/resources/doctor.ts');
return module.default;
},
'session-status': async () => {
const module = await import('../mcp/resources/session-status.ts');
return module.default;
},
simulators: async () => {
const module = await import('../mcp/resources/simulators.ts');
return module.default;
},
};
export type ResourceName = keyof typeof RESOURCE_LOADERS;
```
--------------------------------------------------------------------------------
/src/utils/debugger/index.ts:
--------------------------------------------------------------------------------
```typescript
import { DebuggerManager } from './debugger-manager.ts';
let defaultDebuggerManager: DebuggerManager | null = null;
export function getDefaultDebuggerManager(): DebuggerManager {
defaultDebuggerManager ??= new DebuggerManager();
return defaultDebuggerManager;
}
export { DebuggerManager } from './debugger-manager.ts';
export { getDefaultDebuggerToolContext } from './tool-context.ts';
export { resolveSimulatorAppPid } from './simctl.ts';
export { guardUiAutomationAgainstStoppedDebugger } from './ui-automation-guard.ts';
export type {
BreakpointInfo,
BreakpointSpec,
DebugExecutionState,
DebugExecutionStatus,
DebugSessionInfo,
DebuggerBackendKind,
} from './types.ts';
export type { DebuggerToolContext } from './tool-context.ts';
```
--------------------------------------------------------------------------------
/src/utils/schema-helpers.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Schema Helper Utilities
*
* Shared utility functions for schema validation and preprocessing.
*/
/**
* Convert empty strings to undefined in an object (shallow transformation)
* Used for preprocessing Zod schemas with optional fields
*
* @param value - The value to process
* @returns The processed value with empty strings converted to undefined
*/
export function nullifyEmptyStrings(value: unknown): unknown {
if (value && typeof value === 'object' && !Array.isArray(value)) {
const copy: Record<string, unknown> = { ...(value as Record<string, unknown>) };
for (const key of Object.keys(copy)) {
const v = copy[key];
if (typeof v === 'string' && v.trim() === '') copy[key] = undefined;
}
return copy;
}
return value;
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"inlineSources": true,
// Set `sourceRoot` to "/" to strip the build path prefix
// from generated source code references.
// This improves issue grouping in Sentry.
"sourceRoot": "/",
"allowImportingTsExtensions": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"**/*.test.ts",
"tests-vitest/**/*",
"plugins/**/*",
"src/core/generated-plugins.ts",
"src/core/generated-resources.ts"
]
}
```
--------------------------------------------------------------------------------
/src/utils/debugger/backends/DebuggerBackend.ts:
--------------------------------------------------------------------------------
```typescript
import type { BreakpointInfo, BreakpointSpec, DebugExecutionState } from '../types.ts';
export interface DebuggerBackend {
readonly kind: 'lldb-cli' | 'dap';
attach(opts: { pid: number; simulatorId: string; waitFor?: boolean }): Promise<void>;
detach(): Promise<void>;
runCommand(command: string, opts?: { timeoutMs?: number }): Promise<string>;
resume(opts?: { threadId?: number }): Promise<void>;
addBreakpoint(spec: BreakpointSpec, opts?: { condition?: string }): Promise<BreakpointInfo>;
removeBreakpoint(id: number): Promise<string>;
getStack(opts?: { threadIndex?: number; maxFrames?: number }): Promise<string>;
getVariables(opts?: { frameIndex?: number }): Promise<string>;
getExecutionState(opts?: { timeoutMs?: number }): Promise<DebugExecutionState>;
dispose(): Promise<void>;
}
```
--------------------------------------------------------------------------------
/src/utils/runtime-registry.ts:
--------------------------------------------------------------------------------
```typescript
export type RuntimeToolInfo =
| {
mode: 'runtime';
enabledWorkflows: string[];
enabledTools: string[];
totalRegistered: number;
}
| {
mode: 'static';
enabledWorkflows: string[];
enabledTools: string[];
totalRegistered: number;
note: string;
};
let runtimeToolInfo: RuntimeToolInfo | null = null;
export function recordRuntimeRegistration(info: {
enabledWorkflows: string[];
enabledTools: string[];
}): void {
const enabledWorkflows = [...new Set(info.enabledWorkflows)];
const enabledTools = [...new Set(info.enabledTools)];
runtimeToolInfo = {
mode: 'runtime',
enabledWorkflows,
enabledTools,
totalRegistered: enabledTools.length,
};
}
export function getRuntimeRegistration(): RuntimeToolInfo | null {
return runtimeToolInfo;
}
```
--------------------------------------------------------------------------------
/scripts/generate-version.ts:
--------------------------------------------------------------------------------
```typescript
import { readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
interface PackageJson {
version: string;
iOSTemplateVersion: string;
macOSTemplateVersion: string;
}
async function main(): Promise<void> {
const repoRoot = process.cwd();
const packagePath = path.join(repoRoot, 'package.json');
const versionPath = path.join(repoRoot, 'src', 'version.ts');
const raw = await readFile(packagePath, 'utf8');
const pkg = JSON.parse(raw) as PackageJson;
const content =
`export const version = '${pkg.version}';\n` +
`export const iOSTemplateVersion = '${pkg.iOSTemplateVersion}';\n` +
`export const macOSTemplateVersion = '${pkg.macOSTemplateVersion}';\n`;
await writeFile(versionPath, content, 'utf8');
}
main().catch((error) => {
console.error('Failed to generate src/version.ts:', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/core/plugin-types.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
import { ToolResponse } from '../types/common.ts';
export type ToolSchemaShape = Record<string, z.ZodType>;
export interface PluginMeta {
readonly name: string; // Verb used by MCP
readonly schema: ToolSchemaShape; // Zod validation schema (object schema)
readonly description?: string; // One-liner shown in help
readonly annotations?: ToolAnnotations; // MCP tool annotations for LLM behavior hints
handler(params: Record<string, unknown>): Promise<ToolResponse>;
}
export interface WorkflowMeta {
readonly name: string;
readonly description: string;
}
export interface WorkflowGroup {
readonly workflow: WorkflowMeta;
readonly tools: PluginMeta[];
readonly directoryName: string;
}
export const defineTool = (meta: PluginMeta): PluginMeta => meta;
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [24.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Bundle AXe artifacts
run: npm run bundle:axe
- name: Build (Smithery)
run: npm run build
- name: Verify Smithery bundle
run: npm run verify:smithery-bundle
- name: Lint
run: npm run lint
- name: Check formatting
run: npm run format:check
- name: Type check
run: npm run typecheck
- name: Run tests
run: npm test
- run: npx pkg-pr-new publish
```
--------------------------------------------------------------------------------
/src/utils/FileSystemExecutor.ts:
--------------------------------------------------------------------------------
```typescript
/**
* File system executor interface for dependency injection
*/
import type { WriteStream } from 'fs';
export interface FileSystemExecutor {
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
readFile(path: string, encoding?: BufferEncoding): Promise<string>;
writeFile(path: string, content: string, encoding?: BufferEncoding): Promise<void>;
createWriteStream(path: string, options?: { flags?: string }): WriteStream;
cp(source: string, destination: string, options?: { recursive?: boolean }): Promise<void>;
readdir(path: string, options?: { withFileTypes?: boolean }): Promise<unknown[]>;
rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
existsSync(path: string): boolean;
stat(path: string): Promise<{ isDirectory(): boolean; mtimeMs: number }>;
mkdtemp(prefix: string): Promise<string>;
tmpdir(): string;
}
```
--------------------------------------------------------------------------------
/example_projects/macOS/MCPTest/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
```json
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for simulator-management workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('simulator-management workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('Simulator Management');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'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.',
);
});
});
});
```
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'tsup';
import { chmodSync, existsSync } from 'fs';
import { createPluginDiscoveryPlugin } from './build-plugins/plugin-discovery.js';
export default defineConfig({
entry: {
index: 'src/index.ts',
'doctor-cli': 'src/doctor-cli.ts',
},
format: ['esm'],
target: 'node18',
platform: 'node',
outDir: 'build',
clean: true,
sourcemap: true, // Enable source maps for debugging
dts: {
entry: {
index: 'src/index.ts',
},
},
splitting: false,
shims: false,
treeshake: true,
minify: false,
esbuildPlugins: [createPluginDiscoveryPlugin()],
onSuccess: async () => {
console.log('✅ Build complete!');
// Set executable permissions for built files
if (existsSync('build/index.js')) {
chmodSync('build/index.js', '755');
}
if (existsSync('build/doctor-cli.js')) {
chmodSync('build/doctor-cli.js', '755');
}
},
});
```
--------------------------------------------------------------------------------
/docs/SESSION_DEFAULTS.md:
--------------------------------------------------------------------------------
```markdown
# Session Defaults
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.
## How it works
- Call `session_set_defaults` once at the start of a workflow.
- Tools reuse those defaults automatically.
- Use `session_show_defaults` to inspect current values.
- Use `session_clear_defaults` to clear values when switching contexts.
See the session-management tools in [TOOLS.md](TOOLS.md).
## Opting out
If you prefer explicit parameters on every tool call, set:
```json
"env": {
"XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS": "true"
}
```
This restores the legacy schemas with per-call parameters while still honoring any defaults you choose to set.
## Related docs
- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
- Tools reference: [TOOLS.md](TOOLS.md)
```
--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/active-processes.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Shared process state management for Swift Package tools
* This module provides a centralized way to manage active processes
* between swift_package_run and swift_package_stop tools
*/
export interface ProcessInfo {
process: {
kill: (signal?: string) => void;
on: (event: string, callback: () => void) => void;
pid?: number;
};
startedAt: Date;
}
// Global map to track active processes
export const activeProcesses = new Map<number, ProcessInfo>();
// Helper functions for process management
export const getProcess = (pid: number): ProcessInfo | undefined => {
return activeProcesses.get(pid);
};
export const addProcess = (pid: number, processInfo: ProcessInfo): void => {
activeProcesses.set(pid, processInfo);
};
export const removeProcess = (pid: number): boolean => {
return activeProcesses.delete(pid);
};
export const clearAllProcesses = (): void => {
activeProcesses.clear();
};
```
--------------------------------------------------------------------------------
/.github/workflows/sentry.yml:
--------------------------------------------------------------------------------
```yaml
name: Sentry Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Run tests
run: npm test
- name: Extract version from build/version.js
id: get_version
run: echo "MCP_VERSION=$(grep -oE "'[0-9]+\.[0-9]+\.[0-9]+'" build/version.js | tr -d "'")" >> $GITHUB_OUTPUT
- name: Create Sentry release
uses: getsentry/action-release@v3
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
environment: production
sourcemaps: "./build"
version: ${{ steps.get_version.outputs.MCP_VERSION }}
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/Package.swift:
--------------------------------------------------------------------------------
```swift
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CalculatorAppFeature",
platforms: [.iOS(.v17)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "CalculatorAppFeature",
targets: ["CalculatorAppFeature"]
),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "CalculatorAppFeature"
),
.testTarget(
name: "CalculatorAppFeatureTests",
dependencies: [
"CalculatorAppFeature"
]
),
]
)
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/BackgroundEffect.swift:
--------------------------------------------------------------------------------
```swift
import SwiftUI
// MARK: - Background State Management
enum BackgroundState {
case normal, calculated, error
var colors: [Color] {
switch self {
case .normal:
return [Color.blue.opacity(0.8), Color.purple.opacity(0.8), Color.indigo.opacity(0.9)]
case .calculated:
return [Color.green.opacity(0.7), Color.mint.opacity(0.8), Color.teal.opacity(0.9)]
case .error:
return [Color.red.opacity(0.7), Color.pink.opacity(0.8), Color.orange.opacity(0.9)]
}
}
}
// MARK: - Animated Background Component
struct AnimatedBackground: View {
let backgroundGradient: BackgroundState
var body: some View {
AngularGradient(
colors: backgroundGradient.colors,
center: .topLeading,
angle: .degrees(45)
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 0.8), value: backgroundGradient)
}
}
```
--------------------------------------------------------------------------------
/example_projects/spm/Tests/TestLibTests/SimpleTests.swift:
--------------------------------------------------------------------------------
```swift
import Testing
@Test("Basic truth assertions")
func basicTruthTest() {
#expect(true == true)
#expect(false == false)
#expect(true != false)
}
@Test("Basic math operations")
func basicMathTest() {
#expect(2 + 2 == 4)
#expect(5 - 3 == 2)
#expect(3 * 4 == 12)
#expect(10 / 2 == 5)
}
@Test("String operations")
func stringTest() {
let greeting = "Hello"
let world = "World"
#expect(greeting + " " + world == "Hello World")
#expect(greeting.count == 5)
#expect(world.isEmpty == false)
}
@Test("Array operations")
func arrayTest() {
let numbers = [1, 2, 3, 4, 5]
#expect(numbers.count == 5)
#expect(numbers.first == 1)
#expect(numbers.last == 5)
#expect(numbers.contains(3) == true)
}
@Test("Optional handling")
func optionalTest() {
let someValue: Int? = 42
let nilValue: Int? = nil
#expect(someValue != nil)
#expect(nilValue == nil)
#expect(someValue! == 42)
}
```
--------------------------------------------------------------------------------
/scripts/verify-smithery-bundle.sh:
--------------------------------------------------------------------------------
```bash
#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BUNDLE_DIR="$PROJECT_ROOT/.smithery/bundled"
AXE_BIN="$BUNDLE_DIR/axe"
FRAMEWORK_DIR="$BUNDLE_DIR/Frameworks"
if [ ! -f "$AXE_BIN" ]; then
echo "❌ Missing AXe binary at $AXE_BIN"
if [ -d "$PROJECT_ROOT/.smithery" ]; then
echo "🔍 .smithery contents:"
ls -la "$PROJECT_ROOT/.smithery"
fi
exit 1
fi
if [ ! -d "$FRAMEWORK_DIR" ]; then
echo "❌ Missing Frameworks directory at $FRAMEWORK_DIR"
if [ -d "$BUNDLE_DIR" ]; then
echo "🔍 bundled contents:"
ls -la "$BUNDLE_DIR"
fi
exit 1
fi
FRAMEWORK_COUNT="$(find "$FRAMEWORK_DIR" -maxdepth 2 -type d -name "*.framework" | wc -l | tr -d ' ')"
if [ "$FRAMEWORK_COUNT" -eq 0 ]; then
echo "❌ No frameworks found in $FRAMEWORK_DIR"
find "$FRAMEWORK_DIR" -maxdepth 2 -type d | head -n 50
exit 1
fi
echo "✅ Smithery bundle includes AXe binary and $FRAMEWORK_COUNT frameworks"
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/CalculatorInputHandler.swift:
--------------------------------------------------------------------------------
```swift
import Foundation
// MARK: - Input Handling
/// Handles input parsing and routing to the calculator service
struct CalculatorInputHandler {
private let service: CalculatorService
init(service: CalculatorService) {
self.service = service
}
func handleInput(_ input: String) {
switch input {
case "C":
service.clear()
case "±":
service.toggleSign()
case "%":
service.percentage()
case "+", "-", "×", "÷":
if let operation = CalculatorService.Operation(rawValue: input) {
service.setOperation(operation)
}
case "=":
service.calculate()
case ".":
service.inputDecimal()
case "0"..."9":
service.inputNumber(input)
default:
break // Ignore unknown inputs
}
}
func deleteLastDigit() {
service.deleteLastDigit()
}
}
```
--------------------------------------------------------------------------------
/.vscode/mcp.json:
--------------------------------------------------------------------------------
```json
{
"servers": {
"XcodeBuildMCP": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"xcodebuildmcp@latest"
],
"env": {
"XCODEBUILDMCP_DEBUG": "true",
"INCREMENTAL_BUILDS_ENABLED": "false",
"XCODEBUILDMCP_IOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-iOS-Template",
"XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-macOS-Template"
}
},
"XcodeBuildMCP-Dev": {
"type": "stdio",
"command": "node",
"args": [
"--inspect=9999",
"--trace-warnings",
"${workspaceFolder}/build/index.js"
],
"env": {
"XCODEBUILDMCP_DEBUG": "true",
"INCREMENTAL_BUILDS_ENABLED": "false",
"XCODEBUILDMCP_IOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-iOS-Template",
"XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-macOS-Template"
}
}
}
}
```
--------------------------------------------------------------------------------
/src/utils/session-status.ts:
--------------------------------------------------------------------------------
```typescript
import { getDefaultDebuggerManager } from './debugger/index.ts';
import { listActiveSimulatorLogSessionIds } from './log-capture/index.ts';
import { activeDeviceLogSessions } from './log-capture/device-log-sessions.ts';
export type SessionRuntimeStatusSnapshot = {
logging: {
simulator: { activeSessionIds: string[] };
device: { activeSessionIds: string[] };
};
debug: {
currentSessionId: string | null;
sessionIds: string[];
};
};
export function getSessionRuntimeStatusSnapshot(): SessionRuntimeStatusSnapshot {
const debuggerManager = getDefaultDebuggerManager();
const sessionIds = debuggerManager
.listSessions()
.map((session) => session.id)
.sort();
return {
logging: {
simulator: {
activeSessionIds: listActiveSimulatorLogSessionIds(),
},
device: {
activeSessionIds: Array.from(activeDeviceLogSessions.keys()).sort(),
},
},
debug: {
currentSessionId: debuggerManager.getCurrentSessionId(),
sessionIds,
},
};
}
```
--------------------------------------------------------------------------------
/example_projects/spm/Sources/quick-task/main.swift:
--------------------------------------------------------------------------------
```swift
import Foundation
import TestLib
import ArgumentParser
@main
struct QuickTask: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "quick-task",
abstract: "A quick task that finishes within 5 seconds",
version: "1.0.0"
)
@Option(name: .shortAndLong, help: "Number of seconds to work (default: 3)")
var duration: Int = 3
@Flag(name: .shortAndLong, help: "Enable verbose output")
var verbose: Bool = false
@Option(name: .shortAndLong, help: "Task name to display")
var taskName: String = "DefaultTask"
func run() async throws {
let taskManager = TaskManager()
if verbose {
print("🚀 Starting quick task: \(taskName)")
print("⏱️ Duration: \(duration) seconds")
}
await taskManager.executeQuickTask(name: taskName, duration: duration, verbose: verbose)
if verbose {
print("✅ Quick task completed successfully!")
}
}
}
```
--------------------------------------------------------------------------------
/example_projects/spm/Package.swift:
--------------------------------------------------------------------------------
```swift
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "spm",
platforms: [
.macOS(.v15),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.1"),
],
targets: [
.executableTarget(
name: "spm"
),
.executableTarget(
name: "quick-task",
dependencies: [
"TestLib",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
.executableTarget(
name: "long-server",
dependencies: [
"TestLib",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
.target(
name: "TestLib"
),
.testTarget(
name: "TestLibTests",
dependencies: ["TestLib"]
),
]
)
```
--------------------------------------------------------------------------------
/smithery.config.js:
--------------------------------------------------------------------------------
```javascript
import { execFileSync } from 'child_process';
import { cpSync, existsSync, mkdirSync } from 'fs';
import { dirname, join, resolve } from 'path';
const projectRoot = process.cwd();
const bundledDir = join(projectRoot, 'bundled');
const bundledAxePath = join(bundledDir, 'axe');
function resolveOutputDir() {
const args = process.argv;
const outIndex = args.findIndex((arg) => arg === '--out' || arg === '-o');
if (outIndex !== -1 && args[outIndex + 1]) {
return dirname(resolve(args[outIndex + 1]));
}
return join(projectRoot, '.smithery');
}
const outputDir = resolveOutputDir();
const bundledTargetDir = join(outputDir, 'bundled');
if (!existsSync(bundledAxePath)) {
execFileSync('bash', [join(projectRoot, 'scripts', 'bundle-axe.sh')], {
stdio: 'inherit',
});
}
if (existsSync(bundledAxePath)) {
mkdirSync(outputDir, { recursive: true });
cpSync(bundledDir, bundledTargetDir, { recursive: true });
} else {
throw new Error(`AXe bundle missing at ${bundledAxePath}`);
}
export default {
esbuild: {
format: 'cjs',
target: 'node18',
},
};
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTest/ContentView.swift:
--------------------------------------------------------------------------------
```swift
//
// ContentView.swift
// MCPTest
//
// Created by Cameron on 16/02/2025.
//
import SwiftUI
import OSLog
struct ContentView: View {
@State private var text: String = ""
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
TextField("Enter text", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.horizontal)
Text(text)
Button("Log something") {
let message = ProcessInfo.processInfo.environment.map { "\($0.key): \($0.value)" }.joined(separator: "\n")
Logger.myApp.debug("Environment: \(message)")
debugPrint("Button was pressed.")
text = "You just pressed the button!"
}
}
.padding()
}
}
#Preview {
ContentView()
}
// OS Log Extension
extension Logger {
static let myApp = Logger(
subsystem: "com.cameroncooke.MCPTest",
category: "default"
)
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/doctor/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for doctor workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('doctor workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('System Doctor');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/utils/debugger/simctl.ts:
--------------------------------------------------------------------------------
```typescript
import type { CommandExecutor } from '../execution/index.ts';
export async function resolveSimulatorAppPid(opts: {
executor: CommandExecutor;
simulatorId: string;
bundleId: string;
}): Promise<number> {
const result = await opts.executor(
['xcrun', 'simctl', 'spawn', opts.simulatorId, 'launchctl', 'list'],
'Resolve simulator app PID',
true,
);
if (!result.success) {
throw new Error(result.error ?? 'Failed to read simulator process list');
}
const lines = result.output.split('\n');
for (const line of lines) {
if (!line.includes(opts.bundleId)) continue;
const columns = line.trim().split(/\s+/);
const pidToken = columns[0];
if (!pidToken || pidToken === '-') {
throw new Error(`App ${opts.bundleId} is not running on simulator ${opts.simulatorId}`);
}
const pid = Number(pidToken);
if (Number.isNaN(pid) || pid <= 0) {
throw new Error(`Unable to parse PID for ${opts.bundleId} from: ${line}`);
}
return pid;
}
throw new Error(`No running process found for ${opts.bundleId} on simulator ${opts.simulatorId}`);
}
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
globals: true,
include: [
'src/**/__tests__/**/*.test.ts' // Only __tests__ directories
],
exclude: [
'node_modules/**',
'build/**',
'coverage/**',
'bundled/**',
'example_projects/**',
'.git/**',
'**/*.d.ts',
'**/temp_*',
'**/full-output.txt',
'**/experiments/**',
'**/__pycache__/**',
'**/dist/**'
],
pool: 'threads',
poolOptions: {
threads: {
maxThreads: 4
}
},
env: {
NODE_OPTIONS: '--max-old-space-size=4096'
},
testTimeout: 30000,
hookTimeout: 10000,
teardownTimeout: 5000,
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/**',
'build/**',
'tests/**',
'example_projects/**',
'**/*.config.*',
'**/*.d.ts'
]
}
},
resolve: {
alias: {
// Handle .js imports in TypeScript files
'^(\\.{1,2}/.*)\\.js$': '$1'
}
}
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for macos-project workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('macos-project workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('macOS Development');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for swift-package workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('swift-package workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('Swift Package Manager');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Swift Package Manager operations for building, testing, running, and managing Swift packages and dependencies. Complete SPM workflow support.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/utilities/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for utilities workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('utilities workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('Project Utilities');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/utils/debugger/dap/adapter-discovery.ts:
--------------------------------------------------------------------------------
```typescript
import type { CommandExecutor } from '../../execution/index.ts';
import { log } from '../../logging/index.ts';
import { DependencyError } from '../../errors.ts';
const LOG_PREFIX = '[DAP Adapter]';
export async function resolveLldbDapCommand(opts: {
executor: CommandExecutor;
}): Promise<string[]> {
try {
const result = await opts.executor(['xcrun', '--find', 'lldb-dap'], LOG_PREFIX);
if (!result.success) {
throw new DependencyError('xcrun returned a non-zero exit code for lldb-dap discovery.');
}
const resolved = result.output.trim();
if (!resolved) {
throw new DependencyError('xcrun did not return a path for lldb-dap.');
}
log('debug', `${LOG_PREFIX} resolved lldb-dap: ${resolved}`);
return [resolved];
} catch (error) {
if (error instanceof DependencyError) {
throw error;
}
const message = error instanceof Error ? error.message : String(error);
throw new DependencyError(
'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.',
message,
);
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for project-discovery workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('project-discovery workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('Project Discovery');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/logging/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for logging workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('logging workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('Log Capture & Management');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Log capture and management tools for iOS simulators and physical devices. Start, stop, and analyze application and system logs during development and testing.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/ui-testing/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for ui-testing workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('ui-testing workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('UI Testing & Automation');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/docs/OVERVIEW.md:
--------------------------------------------------------------------------------
```markdown
# Overview
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.
## Why it exists
- Standardizes Xcode interactions for AI agents instead of ad-hoc command lines.
- Reduces configuration errors by providing purpose-built tools.
- Enables agents to build, inspect errors, and iterate autonomously.
## What it can do
- Xcode project discovery, build, test, and clean.
- Simulator and device app lifecycle management.
- Swift Package Manager build, test, and run.
- UI automation, screenshots, and video capture.
- Log capture and system diagnostics.
- Debugger attach, breakpoints, stack, variables, and LLDB command execution.
See the full tool catalog in [TOOLS.md](TOOLS.md).
## Next steps
- Get started: [GETTING_STARTED.md](GETTING_STARTED.md)
- Configure options: [CONFIGURATION.md](CONFIGURATION.md)
- Tools reference: [TOOLS.md](TOOLS.md)
- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for simulator-project workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('simulator-project workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('iOS Simulator Development');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. Build, test, deploy, and interact with iOS apps on simulators.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/project-scaffolding/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for project-scaffolding workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('project-scaffolding workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('Project Scaffolding');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'Tools for creating new iOS and macOS projects from templates. Bootstrap new applications with best practices, standard configurations, and modern project structures.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/device/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for device-project workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('device-project workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('iOS Device Development');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'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.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/session-management/session_clear_defaults.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { sessionStore } from '../../../utils/session-store.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import type { ToolResponse } from '../../../types/common.ts';
const keys = [
'projectPath',
'workspacePath',
'scheme',
'configuration',
'simulatorName',
'simulatorId',
'deviceId',
'useLatestOS',
'arch',
] as const;
const schemaObj = z.object({
keys: z.array(z.enum(keys)).optional(),
all: z.boolean().optional(),
});
type Params = z.infer<typeof schemaObj>;
export async function sessionClearDefaultsLogic(params: Params): Promise<ToolResponse> {
if (params.all || !params.keys) sessionStore.clear();
else sessionStore.clear(params.keys);
return { content: [{ type: 'text', text: 'Session defaults cleared' }], isError: false };
}
export default {
name: 'session-clear-defaults',
description: 'Clear selected or all session defaults.',
schema: schemaObj.shape,
annotations: {
title: 'Clear Session Defaults',
destructiveHint: true,
},
handler: createTypedTool(schemaObj, sessionClearDefaultsLogic, getDefaultCommandExecutor),
};
```
--------------------------------------------------------------------------------
/src/mcp/resources/session-status.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Session Status Resource Plugin
*
* Provides read-only runtime session state for log capture and debugging.
*/
import { log } from '../../utils/logging/index.ts';
import { getSessionRuntimeStatusSnapshot } from '../../utils/session-status.ts';
export async function sessionStatusResourceLogic(): Promise<{ contents: Array<{ text: string }> }> {
try {
log('info', 'Processing session status resource request');
const status = getSessionRuntimeStatusSnapshot();
return {
contents: [
{
text: JSON.stringify(status, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error in session status resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving session status: ${errorMessage}`,
},
],
};
}
}
export default {
uri: 'xcodebuildmcp://session-status',
name: 'session-status',
description: 'Runtime session state for log capture and debugging',
mimeType: 'application/json',
async handler(): Promise<{ contents: Array<{ text: string }> }> {
return sessionStatusResourceLogic();
},
};
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
```yaml
name: Feature Request
description: Suggest a new feature for XcodeBuildMCP
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a new feature for XcodeBuildMCP!
- type: textarea
id: feature-description
attributes:
label: Feature Description
description: Describe the new capability you'd like to add to XcodeBuildMCP
placeholder: I would like the AI assistant to be able to...
validations:
required: true
- type: textarea
id: use-cases
attributes:
label: Use Cases
description: Describe specific scenarios where this feature would be useful
placeholder: |
- Building and testing iOS apps with custom schemes
- Managing multiple simulator configurations
- Automating complex Xcode workflows
validations:
required: false
- type: textarea
id: example-interactions
attributes:
label: Example Interactions
description: Provide examples of how you envision using this feature
placeholder: |
You: [Example request to the AI]
AI: [Desired response/action]
validations:
required: false
```
--------------------------------------------------------------------------------
/src/mcp/tools/session-management/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for session-management workflow metadata
*/
import { describe, it, expect } from 'vitest';
import { workflow } from '../index.ts';
describe('session-management workflow metadata', () => {
describe('Workflow Structure', () => {
it('should export workflow object with required properties', () => {
expect(workflow).toHaveProperty('name');
expect(workflow).toHaveProperty('description');
});
it('should have correct workflow name', () => {
expect(workflow.name).toBe('session-management');
});
it('should have correct description', () => {
expect(workflow.description).toBe(
'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.',
);
});
});
describe('Workflow Validation', () => {
it('should have valid string properties', () => {
expect(typeof workflow.name).toBe('string');
expect(typeof workflow.description).toBe('string');
expect(workflow.name.length).toBeGreaterThan(0);
expect(workflow.description.length).toBeGreaterThan(0);
});
});
});
```
--------------------------------------------------------------------------------
/src/utils/session-store.ts:
--------------------------------------------------------------------------------
```typescript
import { log } from './logger.ts';
export type SessionDefaults = {
projectPath?: string;
workspacePath?: string;
scheme?: string;
configuration?: string;
simulatorName?: string;
simulatorId?: string;
deviceId?: string;
useLatestOS?: boolean;
arch?: 'arm64' | 'x86_64';
suppressWarnings?: boolean;
};
class SessionStore {
private defaults: SessionDefaults = {};
setDefaults(partial: Partial<SessionDefaults>): void {
this.defaults = { ...this.defaults, ...partial };
log('info', `[Session] Defaults updated: ${Object.keys(partial).join(', ')}`);
}
clear(keys?: (keyof SessionDefaults)[]): void {
if (keys == null) {
this.defaults = {};
log('info', '[Session] All defaults cleared');
return;
}
if (keys.length === 0) {
// No-op when an empty array is provided (e.g., empty UI selection)
log('info', '[Session] No keys provided to clear; no changes made');
return;
}
for (const k of keys) delete this.defaults[k];
log('info', `[Session] Defaults cleared: ${keys.join(', ')}`);
}
get<K extends keyof SessionDefaults>(key: K): SessionDefaults[K] {
return this.defaults[key];
}
getAll(): SessionDefaults {
return { ...this.defaults };
}
}
export const sessionStore = new SessionStore();
```
--------------------------------------------------------------------------------
/src/utils/__tests__/debugger-simctl.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from 'vitest';
import { resolveSimulatorAppPid } from '../debugger/simctl.ts';
import { createMockExecutor } from '../../test-utils/mock-executors.ts';
describe('resolveSimulatorAppPid', () => {
it('returns PID when bundle id is found', async () => {
const mockExecutor = createMockExecutor({
success: true,
output: '1234 0 com.example.MyApp\n',
});
const pid = await resolveSimulatorAppPid({
executor: mockExecutor,
simulatorId: 'SIM-123',
bundleId: 'com.example.MyApp',
});
expect(pid).toBe(1234);
});
it('throws when bundle id is missing', async () => {
const mockExecutor = createMockExecutor({
success: true,
output: '999 0 other.app\n',
});
await expect(
resolveSimulatorAppPid({
executor: mockExecutor,
simulatorId: 'SIM-123',
bundleId: 'com.example.MyApp',
}),
).rejects.toThrow('No running process found');
});
it('throws when PID is missing', async () => {
const mockExecutor = createMockExecutor({
success: true,
output: '- 0 com.example.MyApp\n',
});
await expect(
resolveSimulatorAppPid({
executor: mockExecutor,
simulatorId: 'SIM-123',
bundleId: 'com.example.MyApp',
}),
).rejects.toThrow('not running');
});
});
```
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
{
"eslint.useFlatConfig": true,
"eslint.validate": [
"javascript",
"typescript"
],
"eslint.runtime": "/opt/homebrew/bin/node",
"eslint.format.enable": true,
"eslint.nodePath": "${workspaceFolder}/node_modules",
"eslint.workingDirectories": [
{
"directory": "${workspaceFolder}",
"changeProcessCWD": true
}
],
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.tsserver.maxTsServerMemory": 4096,
"javascript.validate.enable": false,
"typescript.validate.enable": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "vscode.typescript-language-features",
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"terminal.integrated.shellIntegration.decorationsEnabled": "never",
"vitest.nodeExecutable": "/opt/homebrew/bin/node",
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"chat.mcp.serverSampling": {
"XcodeBuildMCP/.vscode/mcp.json: XcodeBuildMCP-Dev": {
"allowedDuringChat": true,
"allowedModels": [
"copilot/gpt-5.2"
]
}
},
}
```
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Wait for MCP Server to Start",
"port": 9999,
"address": "localhost",
"restart": true,
"skipFiles": [
"<node_internals>/**"
],
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/build/**/*.js"
],
"cwd": "${workspaceFolder}",
"sourceMapPathOverrides": {
"/*": "${workspaceFolder}/src/*"
},
"timeout": 60000,
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "Launch MCP Server Dev",
"program": "${workspaceFolder}/build/index.js",
"cwd": "${workspaceFolder}",
"runtimeArgs": [
"--inspect=9999"
],
"env": {
"XCODEBUILDMCP_DEBUG": "true",
"INCREMENTAL_BUILDS_ENABLED": "false",
"XCODEBUILDMCP_IOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-iOS-Template",
"XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "${workspaceFolder}/../XcodeBuildMCP-macOS-Template"
},
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/build/**/*.js"
],
"skipFiles": [
"<node_internals>/**"
],
"sourceMapPathOverrides": {
"/*": "${workspaceFolder}/src/*"
}
}
]
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_detach.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
} from '../../../utils/debugger/index.ts';
const debugDetachSchema = z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to detach (defaults to current session)'),
});
export type DebugDetachParams = z.infer<typeof debugDetachSchema>;
export async function debug_detachLogic(
params: DebugDetachParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const targetId = params.debugSessionId ?? ctx.debugger.getCurrentSessionId();
await ctx.debugger.detachSession(targetId ?? undefined);
return createTextResponse(`✅ Detached debugger session${targetId ? ` ${targetId}` : ''}.`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to detach debugger', message);
}
}
export default {
name: 'debug_detach',
description: 'Detach the current debugger session or a specific debugSessionId.',
schema: debugDetachSchema.shape,
handler: createTypedToolWithContext<DebugDetachParams, DebuggerToolContext>(
debugDetachSchema,
debug_detachLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/src/server/bootstrap.ts:
--------------------------------------------------------------------------------
```typescript
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import process from 'node:process';
import { registerResources } from '../core/resources.ts';
import { log, setLogLevel, type LogLevel } from '../utils/logger.ts';
import { registerWorkflows } from '../utils/tool-registry.ts';
export interface BootstrapOptions {
enabledWorkflows?: string[];
}
function parseEnabledWorkflows(value: string): string[] {
return value
.split(',')
.map((name) => name.trim())
.filter(Boolean);
}
export async function bootstrapServer(
server: McpServer,
options: BootstrapOptions = {},
): Promise<void> {
server.server.setRequestHandler(SetLevelRequestSchema, async (request) => {
const { level } = request.params;
setLogLevel(level as LogLevel);
log('info', `Client requested log level: ${level}`);
return {};
});
const enabledWorkflows = options.enabledWorkflows?.length
? options.enabledWorkflows
: process.env.XCODEBUILDMCP_ENABLED_WORKFLOWS
? parseEnabledWorkflows(process.env.XCODEBUILDMCP_ENABLED_WORKFLOWS)
: [];
if (enabledWorkflows.length > 0) {
log('info', `🚀 Initializing server with selected workflows: ${enabledWorkflows.join(', ')}`);
await registerWorkflows(server, enabledWorkflows);
} else {
log('info', '🚀 Initializing server with all tools...');
await registerWorkflows(server);
}
await registerResources(server);
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_continue.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
} from '../../../utils/debugger/index.ts';
const debugContinueSchema = z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to resume (defaults to current session)'),
});
export type DebugContinueParams = z.infer<typeof debugContinueSchema>;
export async function debug_continueLogic(
params: DebugContinueParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const targetId = params.debugSessionId ?? ctx.debugger.getCurrentSessionId();
await ctx.debugger.resumeSession(targetId ?? undefined);
return createTextResponse(`✅ Resumed debugger session${targetId ? ` ${targetId}` : ''}.`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to resume debugger', message);
}
}
export default {
name: 'debug_continue',
description: 'Resume execution in the active debug session or a specific debugSessionId.',
schema: debugContinueSchema.shape,
handler: createTypedToolWithContext<DebugContinueParams, DebuggerToolContext>(
debugContinueSchema,
debug_continueLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_variables.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
} from '../../../utils/debugger/index.ts';
const debugVariablesSchema = z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to target (defaults to current session)'),
frameIndex: z.number().int().nonnegative().optional().describe('Frame index to inspect'),
});
export type DebugVariablesParams = z.infer<typeof debugVariablesSchema>;
export async function debug_variablesLogic(
params: DebugVariablesParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const output = await ctx.debugger.getVariables(params.debugSessionId, {
frameIndex: params.frameIndex,
});
return createTextResponse(output.trim());
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to get variables', message);
}
}
export default {
name: 'debug_variables',
description: 'Return variables for a selected frame in the active debug session.',
schema: debugVariablesSchema.shape,
handler: createTypedToolWithContext<DebugVariablesParams, DebuggerToolContext>(
debugVariablesSchema,
debug_variablesLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
```yaml
name: "Stale Issues and PRs"
on:
schedule:
- cron: "30 3 * * *"
workflow_dispatch: {}
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- name: Mark and close stale items
uses: actions/[email protected]
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 30
days-before-issue-close: 7
days-before-pr-stale: 21
days-before-pr-close: 7
stale-issue-label: "stale"
stale-pr-label: "stale-pr"
exempt-issue-assignees: true
exempt-pr-assignees: true
exempt-issue-labels: "no-stale,security,pinned"
exempt-pr-labels: "no-stale,security,pinned"
stale-issue-message: >
This issue has been inactive for 30 days. It will be closed in 7 days
if no further activity occurs. Add a comment to keep it open, or apply
the `no-stale` label.
stale-pr-message: >
This PR has been inactive for 21 days. It will be closed in 7 days if
no further activity occurs. Add a comment to keep it open, or apply
the `no-stale` label.
close-issue-message: >
Closing due to inactivity. If this is still relevant, please reopen
or file a new issue with updated context.
close-pr-message: >
Closing due to inactivity. If this is still relevant, please reopen
or open a fresh PR with updated context.
```
--------------------------------------------------------------------------------
/src/utils/workflow-selection.ts:
--------------------------------------------------------------------------------
```typescript
import type { WorkflowGroup } from '../core/plugin-types.ts';
const REQUIRED_WORKFLOW = 'session-management';
const DEBUG_WORKFLOW = 'doctor';
function normalizeWorkflowNames(workflowNames: string[]): string[] {
return workflowNames.map((name) => name.trim().toLowerCase()).filter(Boolean);
}
function isWorkflowGroup(value: WorkflowGroup | undefined): value is WorkflowGroup {
return Boolean(value);
}
function isDebugEnabled(): boolean {
const value = process.env.XCODEBUILDMCP_DEBUG ?? '';
return value.toLowerCase() === 'true' || value === '1';
}
export function resolveSelectedWorkflows(
workflowGroups: Map<string, WorkflowGroup>,
workflowNames: string[] = [],
): {
selectedWorkflows: WorkflowGroup[];
selectedNames: string[] | null;
} {
const normalizedNames = normalizeWorkflowNames(workflowNames);
const autoSelected = isDebugEnabled() ? [REQUIRED_WORKFLOW, DEBUG_WORKFLOW] : [REQUIRED_WORKFLOW];
const selectedNames =
normalizedNames.length > 0 ? [...new Set([...autoSelected, ...normalizedNames])] : null;
const selectedWorkflows = selectedNames
? selectedNames.map((workflowName) => workflowGroups.get(workflowName)).filter(isWorkflowGroup)
: [...workflowGroups.values()];
return { selectedWorkflows, selectedNames };
}
export function collectToolNames(workflows: WorkflowGroup[]): string[] {
const toolNames = new Set<string>();
for (const workflow of workflows) {
for (const tool of workflow.tools) {
if (tool?.name) {
toolNames.add(tool.name);
}
}
}
return [...toolNames];
}
```
--------------------------------------------------------------------------------
/src/utils/__tests__/session-store.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { sessionStore } from '../session-store.ts';
describe('SessionStore', () => {
beforeEach(() => {
sessionStore.clear();
});
it('should set and get defaults', () => {
sessionStore.setDefaults({ scheme: 'App', useLatestOS: true });
expect(sessionStore.get('scheme')).toBe('App');
expect(sessionStore.get('useLatestOS')).toBe(true);
});
it('should merge defaults on set', () => {
sessionStore.setDefaults({ scheme: 'App' });
sessionStore.setDefaults({ simulatorName: 'iPhone 16' });
const all = sessionStore.getAll();
expect(all.scheme).toBe('App');
expect(all.simulatorName).toBe('iPhone 16');
});
it('should clear specific keys', () => {
sessionStore.setDefaults({ scheme: 'App', simulatorId: 'SIM-1', deviceId: 'DEV-1' });
sessionStore.clear(['simulatorId']);
const all = sessionStore.getAll();
expect(all.scheme).toBe('App');
expect(all.simulatorId).toBeUndefined();
expect(all.deviceId).toBe('DEV-1');
});
it('should clear all when no keys provided', () => {
sessionStore.setDefaults({ scheme: 'App', simulatorId: 'SIM-1' });
sessionStore.clear();
const all = sessionStore.getAll();
expect(Object.keys(all).length).toBe(0);
});
it('should be a no-op when empty keys array provided', () => {
sessionStore.setDefaults({ scheme: 'App', simulatorId: 'SIM-1' });
sessionStore.clear([]);
const all = sessionStore.getAll();
expect(all.scheme).toBe('App');
expect(all.simulatorId).toBe('SIM-1');
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_stack.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
} from '../../../utils/debugger/index.ts';
const debugStackSchema = z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to target (defaults to current session)'),
threadIndex: z.number().int().nonnegative().optional().describe('Thread index for backtrace'),
maxFrames: z.number().int().positive().optional().describe('Maximum frames to return'),
});
export type DebugStackParams = z.infer<typeof debugStackSchema>;
export async function debug_stackLogic(
params: DebugStackParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const output = await ctx.debugger.getStack(params.debugSessionId, {
threadIndex: params.threadIndex,
maxFrames: params.maxFrames,
});
return createTextResponse(output.trim());
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to get stack', message);
}
}
export default {
name: 'debug_stack',
description: 'Return a thread backtrace from the active debug session.',
schema: debugStackSchema.shape,
handler: createTypedToolWithContext<DebugStackParams, DebuggerToolContext>(
debugStackSchema,
debug_stackLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/example_projects/spm/Sources/long-server/main.swift:
--------------------------------------------------------------------------------
```swift
import Foundation
import TestLib
import ArgumentParser
@main
struct LongServer: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "long-server",
abstract: "A long-running server that runs indefinitely until stopped"
)
@Option(name: .shortAndLong, help: "Port to listen on (default: 8080)")
var port: Int = 8080
@Flag(name: .shortAndLong, help: "Enable verbose logging")
var verbose: Bool = false
@Option(name: .shortAndLong, help: "Auto-shutdown after N seconds (0 = run forever)")
var autoShutdown: Int = 0
func run() async throws {
let taskManager = TaskManager()
if verbose {
print("🚀 Starting long-running server...")
print("🌐 Port: \(port)")
if autoShutdown > 0 {
print("⏰ Auto-shutdown: \(autoShutdown) seconds")
} else {
print("♾️ Running indefinitely (use SIGTERM to stop)")
}
}
// Set up signal handling for graceful shutdown
let signalSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: .main)
signalSource.setEventHandler {
if verbose {
print("\n🛑 Received SIGTERM, shutting down gracefully...")
}
taskManager.stopServer()
}
signalSource.resume()
signal(SIGTERM, SIG_IGN)
await taskManager.startLongRunningServer(
port: port,
verbose: verbose,
autoShutdown: autoShutdown
)
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_breakpoint_remove.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
} from '../../../utils/debugger/index.ts';
const debugBreakpointRemoveSchema = z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to target (defaults to current session)'),
breakpointId: z.number().int().positive().describe('Breakpoint id to remove'),
});
export type DebugBreakpointRemoveParams = z.infer<typeof debugBreakpointRemoveSchema>;
export async function debug_breakpoint_removeLogic(
params: DebugBreakpointRemoveParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const output = await ctx.debugger.removeBreakpoint(params.debugSessionId, params.breakpointId);
return createTextResponse(`✅ Breakpoint ${params.breakpointId} removed.\n\n${output.trim()}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to remove breakpoint', message);
}
}
export default {
name: 'debug_breakpoint_remove',
description: 'Remove a breakpoint by id for the active debug session.',
schema: debugBreakpointRemoveSchema.shape,
handler: createTypedToolWithContext<DebugBreakpointRemoveParams, DebuggerToolContext>(
debugBreakpointRemoveSchema,
debug_breakpoint_removeLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/docs/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
```markdown
# Troubleshooting
## Quick triage
- Run the doctor tool and include output when filing issues.
- Confirm Xcode and Command Line Tools are installed.
- Verify required workflows are enabled in configuration.
- Check simulator/device availability and permissions.
## Doctor tool
The doctor tool checks system configuration and reports on all dependencies required by XcodeBuildMCP.
```bash
npx --package xcodebuildmcp@latest xcodebuildmcp-doctor
```
It reports on:
- System and Node.js environment
- Xcode installation and configuration
- Required dependencies (xcodebuild, AXe, etc.)
- Environment variables affecting XcodeBuildMCP
- Feature availability status
> [!NOTE]
> You can also ask you agent to run the doctor tool which will provide a more representative output.
## Common issues
### UI automation reports missing AXe
UI automation (describe/tap/swipe/type) and simulator video capture require the AXe binary. If you see a missing AXe error:
- Ensure `bundled/` artifacts exist when installing from Smithery or npm.
- Or set `XCODEBUILDMCP_AXE_PATH` to a known AXe binary path (preferred), or `AXE_PATH`.
- Re-run the doctor tool to confirm AXe is detected.
### Tool timeouts
Some clients have short tool timeouts. If you see timeouts, increase the client timeout (for example, `tool_timeout_sec = 600` in Codex).
### Missing tools
If tools do not appear, verify `XCODEBUILDMCP_ENABLED_WORKFLOWS` includes the required workflow groups.
## Related docs
- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
- Tools reference: [TOOLS.md](TOOLS.md)
- Privacy and telemetry: [PRIVACY.md](PRIVACY.md)
```
--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/CalculatorDisplay.swift:
--------------------------------------------------------------------------------
```swift
import SwiftUI
// MARK: - Calculator Display Component
struct CalculatorDisplay: View {
let expressionDisplay: String
let display: String
var onDeleteLastDigit: (() -> Void)? = nil
var body: some View {
VStack(alignment: .trailing, spacing: 8) {
// Expression display (smaller, secondary)
Text(expressionDisplay)
.font(.title2)
.foregroundColor(.white.opacity(0.7))
.frame(maxWidth: .infinity, alignment: .trailing)
.lineLimit(1)
.minimumScaleFactor(0.5)
// Main result display
Text(display)
.font(.system(size: 56, weight: .light, design: .rounded))
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .trailing)
.lineLimit(1)
.minimumScaleFactor(0.3)
.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .local)
.onEnded { value in
if value.translation.width < -20 || value.translation.width > 20 {
onDeleteLastDigit?()
}
}
)
}
.padding(.horizontal, 24)
.padding(.bottom, 30)
.frame(height: 140)
}
}
struct CalculatorDisplay_Previews: PreviewProvider {
static var previews: some View {
CalculatorDisplay(expressionDisplay: "12 + 7", display: "19", onDeleteLastDigit: nil)
.background(Color.black)
.previewLayout(.sizeThatFits)
}
}
```
--------------------------------------------------------------------------------
/src/doctor-cli.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
/**
* XcodeBuildMCP Doctor CLI
*
* This standalone script runs the doctor tool and outputs the results
* to the console. It's designed to be run directly via npx or mise.
*/
import { version } from './version.ts';
import { doctorLogic } from './mcp/tools/doctor/doctor.ts';
import { getDefaultCommandExecutor } from './utils/execution/index.ts';
async function runDoctor(): Promise<void> {
try {
// Using console.error to avoid linting issues as it's allowed by the project's linting rules
console.error(`Running XcodeBuildMCP Doctor (v${version})...`);
console.error('Collecting system information and checking dependencies...\n');
// Run the doctor tool logic directly with CLI flag enabled
const executor = getDefaultCommandExecutor();
const result = await doctorLogic({}, executor, true); // showAsciiLogo = true for CLI
// Output the doctor information
if (result.content && result.content.length > 0) {
const textContent = result.content.find((item) => item.type === 'text');
if (textContent && textContent.type === 'text') {
// eslint-disable-next-line no-console
console.log(textContent.text);
} else {
console.error('Error: Unexpected doctor result format');
}
} else {
console.error('Error: No doctor information returned');
}
console.error('\nDoctor run complete. Please include this output when reporting issues.');
} catch (error) {
console.error('Error running doctor:', error);
process.exit(1);
}
}
// Run the doctor
runDoctor().catch((error) => {
console.error('Unhandled exception:', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/example_projects/iOS/MCPTestUITests/MCPTestUITests.swift:
--------------------------------------------------------------------------------
```swift
import XCTest
/// Reproduction tests for TEST_RUNNER_ environment variable passthrough.
/// GitHub Issue: https://github.com/cameroncooke/XcodeBuildMCP/issues/101
///
/// Expected behavior:
/// - When invoking xcodebuild test with TEST_RUNNER_USE_DEV_MODE=YES,
/// the test runner environment should contain USE_DEV_MODE=YES
/// (the TEST_RUNNER_ prefix is stripped by xcodebuild).
///
/// Current behavior (before implementation in Node layer):
/// - Running via XcodeBuildMCP test tools does not yet pass TEST_RUNNER_
/// variables through, so this test will fail and serve as a repro.
final class MCPTestUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
/// Verifies that USE_DEV_MODE=YES is present in the test runner environment.
/// This proves TEST_RUNNER_USE_DEV_MODE=YES was passed to xcodebuild.
func testEnvironmentVariablePassthrough() throws {
let env = ProcessInfo.processInfo.environment
let value = env["USE_DEV_MODE"] ?? "<nil>"
XCTAssertEqual(
value,
"YES",
"Expected USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE. Actual: \(value)"
)
}
/// Example of how a project might use the env var to alter behavior in dev mode.
/// This does not change test runner configuration; it simply demonstrates conditional logic.
func testDevModeBehaviorPlaceholder() throws {
let isDevMode = ProcessInfo.processInfo.environment["USE_DEV_MODE"] == "YES"
if isDevMode {
XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs")
}
XCTAssertTrue(true)
}
}
```
--------------------------------------------------------------------------------
/src/utils/tool-registry.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { loadWorkflowGroups } from '../core/plugin-registry.ts';
import { ToolResponse } from '../types/common.ts';
import { log } from './logger.ts';
import { recordRuntimeRegistration } from './runtime-registry.ts';
import { resolveSelectedWorkflows } from './workflow-selection.ts';
/**
* Register workflows (selected list or all when omitted)
*/
export async function registerWorkflows(
server: McpServer,
workflowNames: string[] = [],
): Promise<void> {
const workflowGroups = await loadWorkflowGroups();
const selection = resolveSelectedWorkflows(workflowGroups, workflowNames);
let registeredCount = 0;
const registeredTools = new Set<string>();
const registeredWorkflows = new Set<string>();
for (const workflow of selection.selectedWorkflows) {
registeredWorkflows.add(workflow.directoryName);
for (const tool of workflow.tools) {
if (registeredTools.has(tool.name)) {
continue;
}
server.registerTool(
tool.name,
{
description: tool.description ?? '',
inputSchema: tool.schema,
annotations: tool.annotations,
},
(args: unknown): Promise<ToolResponse> => tool.handler(args as Record<string, unknown>),
);
registeredTools.add(tool.name);
registeredCount += 1;
}
}
recordRuntimeRegistration({
enabledWorkflows: [...registeredWorkflows],
enabledTools: [...registeredTools],
});
if (selection.selectedNames) {
log(
'info',
`✅ Registered ${registeredCount} tools from workflows: ${selection.selectedNames.join(', ')}`,
);
} else {
log('info', `✅ Registered ${registeredCount} tools in static mode.`);
}
}
```
--------------------------------------------------------------------------------
/src/mcp/tools/session-management/__tests__/session_show_defaults.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { sessionStore } from '../../../../utils/session-store.ts';
import plugin from '../session_show_defaults.ts';
describe('session-show-defaults tool', () => {
beforeEach(() => {
sessionStore.clear();
});
afterEach(() => {
sessionStore.clear();
});
describe('Export Field Validation (Literal)', () => {
it('should have correct name', () => {
expect(plugin.name).toBe('session-show-defaults');
});
it('should have correct description', () => {
expect(plugin.description).toBe('Show current session defaults.');
});
it('should have handler function', () => {
expect(typeof plugin.handler).toBe('function');
});
it('should have empty schema', () => {
expect(plugin.schema).toEqual({});
});
});
describe('Handler Behavior', () => {
it('should return empty defaults when none set', async () => {
const result = await plugin.handler();
expect(result.isError).toBe(false);
expect(result.content).toHaveLength(1);
expect(typeof result.content[0].text).toBe('string');
const parsed = JSON.parse(result.content[0].text as string);
expect(parsed).toEqual({});
});
it('should return current defaults when set', async () => {
sessionStore.setDefaults({ scheme: 'MyScheme', simulatorId: 'SIM-123' });
const result = await plugin.handler();
expect(result.isError).toBe(false);
expect(result.content).toHaveLength(1);
expect(typeof result.content[0].text).toBe('string');
const parsed = JSON.parse(result.content[0].text as string);
expect(parsed.scheme).toBe('MyScheme');
expect(parsed.simulatorId).toBe('SIM-123');
});
});
});
```
--------------------------------------------------------------------------------
/src/server/server.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Server Configuration - MCP Server setup and lifecycle management
*
* This module handles the creation, configuration, and lifecycle management of the
* Model Context Protocol (MCP) server. It provides the foundation for all tool
* registrations and server capabilities.
*
* Responsibilities:
* - Creating and configuring the MCP server instance
* - Setting up server capabilities and options
* - Managing server lifecycle (start/stop)
* - Handling transport configuration (stdio)
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { log } from '../utils/logger.ts';
import { version } from '../version.ts';
import * as Sentry from '@sentry/node';
/**
* Create and configure the MCP server
* @returns Configured MCP server instance
*/
export function createServer(): McpServer {
// Create server instance
const baseServer = new McpServer(
{
name: 'xcodebuildmcp',
version,
},
{
capabilities: {
tools: {
listChanged: true,
},
resources: {
subscribe: true,
listChanged: true,
},
logging: {},
},
},
);
// Wrap server with Sentry for MCP instrumentation
const server = Sentry.wrapMcpServerWithSentry(baseServer);
// Log server initialization
log('info', `Server initialized with Sentry MCP instrumentation (version ${version})`);
return server;
}
/**
* Start the MCP server with stdio transport
* @param server The MCP server instance to start
*/
export async function startServer(server: McpServer): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
log('info', 'XcodeBuildMCP Server running on stdio');
}
```
--------------------------------------------------------------------------------
/src/mcp/resources/devices.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Devices Resource Plugin
*
* Provides access to connected Apple devices through MCP resource system.
* This resource reuses the existing list_devices tool logic to maintain consistency.
*/
import { log } from '../../utils/logging/index.ts';
import type { CommandExecutor } from '../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../utils/execution/index.ts';
import { list_devicesLogic } from '../tools/device/list_devices.ts';
// Testable resource logic separated from MCP handler
export async function devicesResourceLogic(
executor: CommandExecutor = getDefaultCommandExecutor(),
): Promise<{ contents: Array<{ text: string }> }> {
try {
log('info', 'Processing devices resource request');
const result = await list_devicesLogic({}, executor);
if (result.isError) {
const errorText = result.content[0]?.text;
throw new Error(typeof errorText === 'string' ? errorText : 'Failed to retrieve device data');
}
return {
contents: [
{
text:
typeof result.content[0]?.text === 'string'
? result.content[0].text
: 'No device data available',
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error in devices resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving device data: ${errorMessage}`,
},
],
};
}
}
export default {
uri: 'xcodebuildmcp://devices',
name: 'devices',
description: 'Connected physical Apple devices with their UUIDs, names, and connection status',
mimeType: 'text/plain',
async handler(): Promise<{ contents: Array<{ text: string }> }> {
return devicesResourceLogic();
},
};
```
--------------------------------------------------------------------------------
/src/mcp/resources/simulators.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Simulator Resource Plugin
*
* Provides access to available iOS simulators through MCP resource system.
* This resource reuses the existing list_sims tool logic to maintain consistency.
*/
import { log } from '../../utils/logging/index.ts';
import { getDefaultCommandExecutor } from '../../utils/execution/index.ts';
import type { CommandExecutor } from '../../utils/execution/index.ts';
import { list_simsLogic } from '../tools/simulator/list_sims.ts';
// Testable resource logic separated from MCP handler
export async function simulatorsResourceLogic(
executor: CommandExecutor = getDefaultCommandExecutor(),
): Promise<{ contents: Array<{ text: string }> }> {
try {
log('info', 'Processing simulators resource request');
const result = await list_simsLogic({}, executor);
if (result.isError) {
const errorText = result.content[0]?.text;
throw new Error(
typeof errorText === 'string' ? errorText : 'Failed to retrieve simulator data',
);
}
return {
contents: [
{
text:
typeof result.content[0]?.text === 'string'
? result.content[0].text
: 'No simulator data available',
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error in simulators resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving simulator data: ${errorMessage}`,
},
],
};
}
}
export default {
uri: 'xcodebuildmcp://simulators',
name: 'simulators',
description: 'Available iOS simulators with their UUIDs and states',
mimeType: 'text/plain',
async handler(): Promise<{ contents: Array<{ text: string }> }> {
return simulatorsResourceLogic();
},
};
```
--------------------------------------------------------------------------------
/src/utils/debugger/dap/types.ts:
--------------------------------------------------------------------------------
```typescript
export type DapRequest<C = unknown> = {
seq: number;
type: 'request';
command: string;
arguments?: C;
};
export type DapResponse<B = unknown> = {
seq: number;
type: 'response';
request_seq: number;
success: boolean;
command: string;
message?: string;
body?: B;
};
export type DapEvent<B = unknown> = {
seq: number;
type: 'event';
event: string;
body?: B;
};
export type InitializeResponseBody = {
supportsConfigurationDoneRequest?: boolean;
supportsFunctionBreakpoints?: boolean;
supportsConditionalBreakpoints?: boolean;
supportsEvaluateForHovers?: boolean;
supportsTerminateRequest?: boolean;
};
export type ThreadsResponseBody = {
threads: Array<{ id: number; name?: string }>;
};
export type StackTraceResponseBody = {
stackFrames: Array<{
id: number;
name: string;
source?: { path?: string; name?: string };
line?: number;
column?: number;
}>;
totalFrames?: number;
};
export type ScopesResponseBody = {
scopes: Array<{ name: string; variablesReference: number; expensive?: boolean }>;
};
export type VariablesResponseBody = {
variables: Array<{
name: string;
value: string;
type?: string;
variablesReference?: number;
}>;
};
export type SetBreakpointsResponseBody = {
breakpoints: Array<{
id?: number;
verified?: boolean;
message?: string;
source?: { path?: string; name?: string };
line?: number;
}>;
};
export type EvaluateResponseBody = {
result?: string;
output?: string;
type?: string;
variablesReference?: number;
};
export type StoppedEventBody = {
reason: string;
threadId?: number;
allThreadsStopped?: boolean;
description?: string;
};
export type OutputEventBody = {
category?: string;
output: string;
data?: unknown;
};
export type TerminatedEventBody = Record<string, never>;
```
--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"name": "com.xcodebuildmcp/XcodeBuildMCP",
"description": "XcodeBuildMCP provides tools for Xcode project management, simulator management, and app utilities.",
"status": "active",
"repository": {
"url": "https://github.com/cameroncooke/XcodeBuildMCP",
"source": "github",
"id": "945551361"
},
"version": "1.15.1",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "xcodebuildmcp",
"version": "1.15.1",
"transport": {
"type": "stdio"
},
"runtimeHint": "npx",
"environmentVariables": [
{
"name": "INCREMENTAL_BUILDS_ENABLED",
"description": "Enable experimental xcodemake incremental builds (true/false or 1/0).",
"format": "boolean",
"default": "false",
"choices": [
"true",
"false",
"1",
"0"
]
},
{
"name": "XCODEBUILDMCP_ENABLED_WORKFLOWS",
"description": "Comma-separated list of workflows to load at startup (e.g., 'simulator,device,project-discovery').",
"format": "string",
"default": ""
},
{
"name": "XCODEBUILDMCP_SENTRY_DISABLED",
"description": "Disable Sentry error reporting (preferred flag).",
"format": "boolean",
"default": "false",
"choices": [
"true",
"false"
]
},
{
"name": "XCODEBUILDMCP_DEBUG",
"description": "Enable verbose debug logging from the server.",
"format": "boolean",
"default": "false",
"choices": [
"true",
"false"
]
}
]
}
]
}
```
--------------------------------------------------------------------------------
/src/mcp/resources/__tests__/session-status.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { getDefaultDebuggerManager } from '../../../utils/debugger/index.ts';
import { activeLogSessions } from '../../../utils/log_capture.ts';
import { activeDeviceLogSessions } from '../../../utils/log-capture/device-log-sessions.ts';
import sessionStatusResource, { sessionStatusResourceLogic } from '../session-status.ts';
describe('session-status resource', () => {
beforeEach(async () => {
activeLogSessions.clear();
activeDeviceLogSessions.clear();
await getDefaultDebuggerManager().disposeAll();
});
afterEach(async () => {
activeLogSessions.clear();
activeDeviceLogSessions.clear();
await getDefaultDebuggerManager().disposeAll();
});
describe('Export Field Validation', () => {
it('should export correct uri', () => {
expect(sessionStatusResource.uri).toBe('xcodebuildmcp://session-status');
});
it('should export correct description', () => {
expect(sessionStatusResource.description).toBe(
'Runtime session state for log capture and debugging',
);
});
it('should export correct mimeType', () => {
expect(sessionStatusResource.mimeType).toBe('application/json');
});
it('should export handler function', () => {
expect(typeof sessionStatusResource.handler).toBe('function');
});
});
describe('Handler Functionality', () => {
it('should return empty status when no sessions exist', async () => {
const result = await sessionStatusResourceLogic();
expect(result.contents).toHaveLength(1);
const parsed = JSON.parse(result.contents[0].text);
expect(parsed.logging.simulator.activeSessionIds).toEqual([]);
expect(parsed.logging.device.activeSessionIds).toEqual([]);
expect(parsed.debug.currentSessionId).toBe(null);
expect(parsed.debug.sessionIds).toEqual([]);
});
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_lldb_command.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
} from '../../../utils/debugger/index.ts';
const debugLldbCommandSchema = z.preprocess(
nullifyEmptyStrings,
z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to target (defaults to current session)'),
command: z.string().describe('LLDB command to run (e.g., "continue", "thread backtrace")'),
timeoutMs: z.number().int().positive().optional().describe('Override command timeout (ms)'),
}),
);
export type DebugLldbCommandParams = z.infer<typeof debugLldbCommandSchema>;
export async function debug_lldb_commandLogic(
params: DebugLldbCommandParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const output = await ctx.debugger.runCommand(params.debugSessionId, params.command, {
timeoutMs: params.timeoutMs,
});
return createTextResponse(output.trim());
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to run LLDB command', message);
}
}
export default {
name: 'debug_lldb_command',
description: 'Run an arbitrary LLDB command within the active debug session.',
schema: z.object({
debugSessionId: z.string().optional(),
command: z.string(),
timeoutMs: z.number().int().positive().optional(),
}).shape,
handler: createTypedToolWithContext<DebugLldbCommandParams, DebuggerToolContext>(
debugLldbCommandSchema as unknown as z.ZodType<DebugLldbCommandParams, unknown>,
debug_lldb_commandLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/logging/stop_sim_log_cap.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Logging Plugin: Stop Simulator Log Capture
*
* Stops an active simulator log capture session and returns the captured logs.
*/
import * as z from 'zod';
import { stopLogCapture as _stopLogCapture } from '../../../utils/log-capture/index.ts';
import { ToolResponse, createTextContent } from '../../../types/common.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
import { getDefaultCommandExecutor, getDefaultFileSystemExecutor } from '../../../utils/command.ts';
import { FileSystemExecutor } from '../../../utils/FileSystemExecutor.ts';
// Define schema as ZodObject
const stopSimLogCapSchema = z.object({
logSessionId: z.string().describe('The session ID returned by start_sim_log_cap.'),
});
// Use z.infer for type safety
type StopSimLogCapParams = z.infer<typeof stopSimLogCapSchema>;
/**
* Business logic for stopping simulator log capture session
*/
export async function stop_sim_log_capLogic(
params: StopSimLogCapParams,
fileSystem: FileSystemExecutor,
): Promise<ToolResponse> {
const { logContent, error } = await _stopLogCapture(params.logSessionId, fileSystem);
if (error) {
return {
content: [
createTextContent(`Error stopping log capture session ${params.logSessionId}: ${error}`),
],
isError: true,
};
}
return {
content: [
createTextContent(
`Log capture session ${params.logSessionId} stopped successfully. Log content follows:\n\n${logContent}`,
),
],
};
}
export default {
name: 'stop_sim_log_cap',
description: 'Stops an active simulator log capture session and returns the captured logs.',
schema: stopSimLogCapSchema.shape, // MCP SDK compatibility
annotations: {
title: 'Stop Simulator Log Capture',
destructiveHint: true,
},
handler: createTypedTool(
stopSimLogCapSchema,
(params: StopSimLogCapParams) => stop_sim_log_capLogic(params, getDefaultFileSystemExecutor()),
getDefaultCommandExecutor,
),
};
```
--------------------------------------------------------------------------------
/src/utils/execution/interactive-process.ts:
--------------------------------------------------------------------------------
```typescript
import { spawn, type ChildProcess } from 'node:child_process';
export interface InteractiveProcess {
readonly process: ChildProcess;
write(data: string): void;
kill(signal?: NodeJS.Signals): void;
dispose(): void;
}
export interface SpawnInteractiveOptions {
cwd?: string;
env?: Record<string, string>;
}
export type InteractiveSpawner = (
command: string[],
opts?: SpawnInteractiveOptions,
) => InteractiveProcess;
class DefaultInteractiveProcess implements InteractiveProcess {
readonly process: ChildProcess;
private disposed = false;
constructor(process: ChildProcess) {
this.process = process;
}
write(data: string): void {
if (this.disposed) {
throw new Error('Interactive process is disposed');
}
if (!this.process.stdin) {
throw new Error('Interactive process stdin is not available');
}
this.process.stdin.write(data);
}
kill(signal?: NodeJS.Signals): void {
if (this.disposed) return;
this.process.kill(signal);
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
this.process.stdin?.end();
this.process.stdout?.removeAllListeners();
this.process.stderr?.removeAllListeners();
this.process.removeAllListeners();
if (!this.process.killed) {
this.process.kill();
}
}
}
function createInteractiveProcess(
command: string[],
opts?: SpawnInteractiveOptions,
): InteractiveProcess {
if (command.length === 0) {
throw new Error('Command array must not be empty');
}
const [executable, ...args] = command;
const childProcess = spawn(executable, args, {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, ...(opts?.env ?? {}) },
cwd: opts?.cwd,
});
return new DefaultInteractiveProcess(childProcess);
}
export function getDefaultInteractiveSpawner(): InteractiveSpawner {
if (process.env.VITEST === 'true' || process.env.NODE_ENV === 'test') {
throw new Error(
'Interactive process spawn blocked in tests. Inject a mock InteractiveSpawner.',
);
}
return createInteractiveProcess;
}
```
--------------------------------------------------------------------------------
/src/mcp/resources/doctor.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Doctor Resource Plugin
*
* Provides access to development environment doctor information through MCP resource system.
* This resource reuses the existing doctor tool logic to maintain consistency.
*/
import { log } from '../../utils/logging/index.ts';
import { getDefaultCommandExecutor, CommandExecutor } from '../../utils/execution/index.ts';
import { doctorLogic } from '../tools/doctor/doctor.ts';
// Testable resource logic separated from MCP handler
export async function doctorResourceLogic(
executor: CommandExecutor = getDefaultCommandExecutor(),
): Promise<{ contents: Array<{ text: string }> }> {
try {
log('info', 'Processing doctor resource request');
const result = await doctorLogic({}, executor);
if (result.isError) {
const textItem = result.content.find((i) => i.type === 'text') as
| { type: 'text'; text: string }
| undefined;
const errorText = textItem?.text;
const errorMessage =
typeof errorText === 'string' ? errorText : 'Failed to retrieve doctor data';
log('error', `Error in doctor resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving doctor data: ${errorMessage}`,
},
],
};
}
const okTextItem = result.content.find((i) => i.type === 'text') as
| { type: 'text'; text: string }
| undefined;
return {
contents: [
{
text: okTextItem?.text ?? 'No doctor data available',
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error in doctor resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving doctor data: ${errorMessage}`,
},
],
};
}
}
export default {
uri: 'xcodebuildmcp://doctor',
name: 'doctor',
description:
'Comprehensive development environment diagnostic information and configuration status',
mimeType: 'text/plain',
async handler(): Promise<{ contents: Array<{ text: string }> }> {
return doctorResourceLogic();
},
};
```
--------------------------------------------------------------------------------
/src/utils/__tests__/build-utils-suppress-warnings.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { executeXcodeBuildCommand } from '../build-utils.ts';
import { XcodePlatform } from '../../types/common.ts';
import { sessionStore } from '../session-store.ts';
import { createMockExecutor } from '../../test-utils/mock-executors.ts';
describe('executeXcodeBuildCommand - suppressWarnings', () => {
beforeEach(() => {
sessionStore.clear();
});
it('should include warnings when suppressWarnings is false', async () => {
sessionStore.setDefaults({ suppressWarnings: false });
const mockExecutor = createMockExecutor({
success: true,
output: 'warning: Some warning\nerror: Some error',
error: '',
exitCode: 0,
});
const result = await executeXcodeBuildCommand(
{
projectPath: '/test/project.xcodeproj',
scheme: 'TestScheme',
configuration: 'Debug',
},
{
platform: XcodePlatform.macOS,
logPrefix: 'Test',
},
false,
'build',
mockExecutor,
);
expect(result.content).toBeDefined();
const textContent = result.content
?.filter((c) => c.type === 'text')
.map((c) => (c as { text: string }).text)
.join('\n');
expect(textContent).toContain('⚠️ Warning:');
});
it('should suppress warnings when suppressWarnings is true', async () => {
sessionStore.setDefaults({ suppressWarnings: true });
const mockExecutor = createMockExecutor({
success: true,
output: 'warning: Some warning\nerror: Some error',
error: '',
exitCode: 0,
});
const result = await executeXcodeBuildCommand(
{
projectPath: '/test/project.xcodeproj',
scheme: 'TestScheme',
configuration: 'Debug',
},
{
platform: XcodePlatform.macOS,
logPrefix: 'Test',
},
false,
'build',
mockExecutor,
);
expect(result.content).toBeDefined();
const textContent = result.content
?.filter((c) => c.type === 'text')
.map((c) => (c as { text: string }).text)
.join('\n');
expect(textContent).not.toContain('⚠️ Warning:');
expect(textContent).toContain('❌ Error:');
});
});
```
--------------------------------------------------------------------------------
/example_projects/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
{
"configurations": [
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:example_projects}/spm",
"name": "Debug spm (spm)",
"program": "${workspaceFolder:example_projects}/spm/.build/debug/spm",
"preLaunchTask": "swift: Build Debug spm (spm)"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:example_projects}/spm",
"name": "Release spm (spm)",
"program": "${workspaceFolder:example_projects}/spm/.build/release/spm",
"preLaunchTask": "swift: Build Release spm (spm)"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:example_projects}/spm",
"name": "Debug quick-task (spm)",
"program": "${workspaceFolder:example_projects}/spm/.build/debug/quick-task",
"preLaunchTask": "swift: Build Debug quick-task (spm)"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:example_projects}/spm",
"name": "Release quick-task (spm)",
"program": "${workspaceFolder:example_projects}/spm/.build/release/quick-task",
"preLaunchTask": "swift: Build Release quick-task (spm)"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:example_projects}/spm",
"name": "Debug long-server (spm)",
"program": "${workspaceFolder:example_projects}/spm/.build/debug/long-server",
"preLaunchTask": "swift: Build Debug long-server (spm)"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:example_projects}/spm",
"name": "Release long-server (spm)",
"program": "${workspaceFolder:example_projects}/spm/.build/release/long-server",
"preLaunchTask": "swift: Build Release long-server (spm)"
}
]
}
```
--------------------------------------------------------------------------------
/src/smithery.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { bootstrapServer } from './server/bootstrap.ts';
import { createServer } from './server/server.ts';
import { log } from './utils/logger.ts';
import { initSentry } from './utils/sentry.ts';
export const configSchema = z.object({
incrementalBuildsEnabled: z
.boolean()
.default(false)
.describe('Enable incremental builds via xcodemake (true/false).'),
enabledWorkflows: z
.string()
.default('')
.describe('Comma-separated list of workflows to load at startup.'),
sentryDisabled: z.boolean().default(false).describe('Disable Sentry error reporting.'),
debug: z.boolean().default(false).describe('Enable debug logging.'),
});
export type SmitheryConfig = z.infer<typeof configSchema>;
function applyConfig(config: SmitheryConfig): string[] {
process.env.INCREMENTAL_BUILDS_ENABLED = config.incrementalBuildsEnabled ? '1' : '0';
process.env.XCODEBUILDMCP_ENABLED_WORKFLOWS = config.enabledWorkflows;
process.env.XCODEBUILDMCP_SENTRY_DISABLED = config.sentryDisabled ? 'true' : 'false';
process.env.XCODEBUILDMCP_DEBUG = config.debug ? 'true' : 'false';
return config.enabledWorkflows
.split(',')
.map((name) => name.trim())
.filter(Boolean);
}
export default function createSmitheryServer({ config }: { config: SmitheryConfig }): McpServer {
const workflowNames = applyConfig(config);
initSentry();
const server = createServer();
const bootstrapPromise = bootstrapServer(server, { enabledWorkflows: workflowNames }).catch(
(error) => {
log(
'error',
`Failed to bootstrap Smithery server: ${error instanceof Error ? error.message : String(error)}`,
);
throw error;
},
);
const handler: ProxyHandler<McpServer> = {
get(target, prop, receiver): unknown {
if (prop === 'connect') {
return async (...args: unknown[]): Promise<unknown> => {
await bootstrapPromise;
const connect = target.connect.bind(target) as (...connectArgs: unknown[]) => unknown;
return connect(...args);
};
}
return Reflect.get(target, prop, receiver) as unknown;
},
};
return new Proxy(server, handler);
}
```
--------------------------------------------------------------------------------
/example_projects/spm/Sources/TestLib/TaskManager.swift:
--------------------------------------------------------------------------------
```swift
import Foundation
public class TaskManager {
private var isServerRunning = false
public init() {}
public func executeQuickTask(name: String, duration: Int, verbose: Bool) async {
if verbose {
print("📝 Task '\(name)' started at \(Date())")
}
// Simulate work with periodic output using Swift Concurrency
for i in 1...duration {
if verbose {
print("⚙️ Working... step \(i)/\(duration)")
}
try? await Task.sleep(for: .seconds(1))
}
if verbose {
print("🎉 Task '\(name)' completed at \(Date())")
} else {
print("Task '\(name)' completed in \(duration)s")
}
}
public func startLongRunningServer(port: Int, verbose: Bool, autoShutdown: Int) async {
if verbose {
print("🔧 Initializing server on port \(port)...")
}
var secondsRunning = 0
let startTime = Date()
isServerRunning = true
// Simulate server startup
try? await Task.sleep(for: .milliseconds(500))
print("✅ Server running on port \(port)")
// Main server loop using Swift Concurrency
while isServerRunning {
try? await Task.sleep(for: .seconds(1))
secondsRunning += 1
if verbose && secondsRunning % 5 == 0 {
print("📊 Server heartbeat: \(secondsRunning)s uptime")
}
// Handle auto-shutdown
if autoShutdown > 0 && secondsRunning >= autoShutdown {
if verbose {
print("⏰ Auto-shutdown triggered after \(autoShutdown)s")
}
break
}
}
let uptime = Date().timeIntervalSince(startTime)
print("🛑 Server stopped after \(String(format: "%.1f", uptime))s uptime")
isServerRunning = false
}
public func stopServer() {
isServerRunning = false
}
public func calculateSum(_ a: Int, _ b: Int) -> Int {
return a + b
}
public func validateInput(_ input: String) -> Bool {
return !input.isEmpty && input.count <= 100
}
}
```
--------------------------------------------------------------------------------
/src/utils/debugger/__tests__/debugger-manager-dap.test.ts:
--------------------------------------------------------------------------------
```typescript
import { afterEach, describe, expect, it } from 'vitest';
import type { BreakpointInfo, BreakpointSpec } from '../types.ts';
import type { DebuggerBackend } from '../backends/DebuggerBackend.ts';
import { DebuggerManager } from '../debugger-manager.ts';
function createBackend(overrides: Partial<DebuggerBackend> = {}): DebuggerBackend {
const base: DebuggerBackend = {
kind: 'dap',
attach: async () => {},
detach: async () => {},
runCommand: async () => '',
resume: async () => {},
addBreakpoint: async (spec: BreakpointSpec): Promise<BreakpointInfo> => ({
id: 1,
spec,
rawOutput: '',
}),
removeBreakpoint: async () => '',
getStack: async () => '',
getVariables: async () => '',
getExecutionState: async () => ({ status: 'unknown' }),
dispose: async () => {},
};
return { ...base, ...overrides };
}
describe('DebuggerManager DAP selection', () => {
const envKey = 'XCODEBUILDMCP_DEBUGGER_BACKEND';
let prevEnv: string | undefined;
afterEach(() => {
if (prevEnv === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = prevEnv;
}
});
it('selects dap backend when env is set', async () => {
prevEnv = process.env[envKey];
process.env[envKey] = 'dap';
let selected: string | null = null;
const backend = createBackend({ kind: 'dap' });
const manager = new DebuggerManager({
backendFactory: async (kind) => {
selected = kind;
return backend;
},
});
await manager.createSession({ simulatorId: 'sim-1', pid: 1000 });
expect(selected).toBe('dap');
});
it('disposes backend when attach fails without masking error', async () => {
const error = new Error('attach failed');
let disposeCalled = false;
const backend = createBackend({
attach: async () => {
throw error;
},
dispose: async () => {
disposeCalled = true;
throw new Error('dispose failed');
},
});
const manager = new DebuggerManager({
backendFactory: async () => backend,
});
await expect(
manager.createSession({ simulatorId: 'sim-1', pid: 2000, backend: 'dap' }),
).rejects.toThrow('attach failed');
expect(disposeCalled).toBe(true);
});
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/swift_package_clean.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import path from 'node:path';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createErrorResponse } from '../../../utils/responses/index.ts';
import { log } from '../../../utils/logging/index.ts';
import { ToolResponse } from '../../../types/common.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
// Define schema as ZodObject
const swiftPackageCleanSchema = z.object({
packagePath: z.string().describe('Path to the Swift package root (Required)'),
});
// Use z.infer for type safety
type SwiftPackageCleanParams = z.infer<typeof swiftPackageCleanSchema>;
export async function swift_package_cleanLogic(
params: SwiftPackageCleanParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
const resolvedPath = path.resolve(params.packagePath);
const swiftArgs = ['package', '--package-path', resolvedPath, 'clean'];
log('info', `Running swift ${swiftArgs.join(' ')}`);
try {
const result = await executor(['swift', ...swiftArgs], 'Swift Package Clean', true, undefined);
if (!result.success) {
const errorMessage = result.error ?? result.output ?? 'Unknown error';
return createErrorResponse('Swift package clean failed', errorMessage);
}
return {
content: [
{ type: 'text', text: '✅ Swift package cleaned successfully.' },
{
type: 'text',
text: '💡 Build artifacts and derived data removed. Ready for fresh build.',
},
{ type: 'text', text: result.output || '(clean completed silently)' },
],
isError: false,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
log('error', `Swift package clean failed: ${message}`);
return createErrorResponse('Failed to execute swift package clean', message);
}
}
export default {
name: 'swift_package_clean',
description: 'Cleans Swift Package build artifacts and derived data',
schema: swiftPackageCleanSchema.shape, // MCP SDK compatibility
annotations: {
title: 'Swift Package Clean',
destructiveHint: true,
},
handler: createTypedTool(
swiftPackageCleanSchema,
swift_package_cleanLogic,
getDefaultCommandExecutor,
),
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/open_sim.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { log } from '../../../utils/logging/index.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
// Define schema as ZodObject
const openSimSchema = z.object({});
// Use z.infer for type safety
type OpenSimParams = z.infer<typeof openSimSchema>;
export async function open_simLogic(
params: OpenSimParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
log('info', 'Starting open simulator request');
try {
const command = ['open', '-a', 'Simulator'];
const result = await executor(command, 'Open Simulator', true);
if (!result.success) {
return {
content: [
{
type: 'text',
text: `Open simulator operation failed: ${result.error}`,
},
],
};
}
return {
content: [
{
type: 'text',
text: `Simulator app opened successfully`,
},
{
type: 'text',
text: `Next Steps:
1. Boot a simulator if needed: boot_sim({ simulatorId: 'UUID_FROM_LIST_SIMULATORS' })
2. Launch your app and interact with it
3. Log capture options:
- Option 1: Capture structured logs only (app continues running):
start_sim_log_cap({ simulatorId: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID' })
- Option 2: Capture both console and structured logs (app will restart):
start_sim_log_cap({ simulatorId: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID', captureConsole: true })
- Option 3: Launch app with logs in one step:
launch_app_logs_sim({ simulatorId: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID' })`,
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error during open simulator operation: ${errorMessage}`);
return {
content: [
{
type: 'text',
text: `Open simulator operation failed: ${errorMessage}`,
},
],
};
}
}
export default {
name: 'open_sim',
description: 'Opens the iOS Simulator app.',
schema: openSimSchema.shape, // MCP SDK compatibility
annotations: {
title: 'Open Simulator',
destructiveHint: true,
},
handler: createTypedTool(openSimSchema, open_simLogic, getDefaultCommandExecutor),
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/macos/stop_mac_app.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { log } from '../../../utils/logging/index.ts';
import { ToolResponse } from '../../../types/common.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
// Define schema as ZodObject
const stopMacAppSchema = z.object({
appName: z
.string()
.optional()
.describe('Name of the application to stop (e.g., "Calculator" or "MyApp")'),
processId: z.number().optional().describe('Process ID (PID) of the application to stop'),
});
// Use z.infer for type safety
type StopMacAppParams = z.infer<typeof stopMacAppSchema>;
export async function stop_mac_appLogic(
params: StopMacAppParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
if (!params.appName && !params.processId) {
return {
content: [
{
type: 'text',
text: 'Either appName or processId must be provided.',
},
],
isError: true,
};
}
log(
'info',
`Stopping macOS app: ${params.processId ? `PID ${params.processId}` : params.appName}`,
);
try {
let command: string[];
if (params.processId) {
// Stop by process ID
command = ['kill', String(params.processId)];
} else {
// Stop by app name - use shell command with fallback for complex logic
command = [
'sh',
'-c',
`pkill -f "${params.appName}" || osascript -e 'tell application "${params.appName}" to quit'`,
];
}
await executor(command, 'Stop macOS App');
return {
content: [
{
type: 'text',
text: `✅ macOS app stopped successfully: ${params.processId ? `PID ${params.processId}` : params.appName}`,
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error stopping macOS app: ${errorMessage}`);
return {
content: [
{
type: 'text',
text: `❌ Stop macOS app operation failed: ${errorMessage}`,
},
],
isError: true,
};
}
}
export default {
name: 'stop_mac_app',
description: 'Stops a running macOS application. Can stop by app name or process ID.',
schema: stopMacAppSchema.shape, // MCP SDK compatibility
annotations: {
title: 'Stop macOS App',
destructiveHint: true,
},
handler: createTypedTool(stopMacAppSchema, stop_mac_appLogic, getDefaultCommandExecutor),
};
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
/**
* XcodeBuildMCP - Main entry point
*
* This file serves as the entry point for the XcodeBuildMCP server, importing and registering
* all tool modules with the MCP server. It follows the platform-specific approach for Xcode tools.
*
* Responsibilities:
* - Creating and starting the MCP server
* - Registering all platform-specific tool modules
* - Configuring server options and logging
* - Handling server lifecycle events
*/
// Import server components
import { createServer, startServer } from './server/server.ts';
// Import utilities
import { log } from './utils/logger.ts';
import { initSentry } from './utils/sentry.ts';
import { getDefaultDebuggerManager } from './utils/debugger/index.ts';
// Import version
import { version } from './version.ts';
// Import xcodemake utilities
import { isXcodemakeEnabled, isXcodemakeAvailable } from './utils/xcodemake.ts';
// Import process for stdout configuration
import process from 'node:process';
import { bootstrapServer } from './server/bootstrap.ts';
/**
* Main function to start the server
*/
async function main(): Promise<void> {
try {
initSentry();
// Check if xcodemake is enabled and available
if (isXcodemakeEnabled()) {
log('info', 'xcodemake is enabled, checking if available...');
const available = await isXcodemakeAvailable();
if (available) {
log('info', 'xcodemake is available and will be used for builds');
} else {
log(
'warn',
'xcodemake is enabled but could not be made available, falling back to xcodebuild',
);
}
} else {
log('debug', 'xcodemake is disabled, using standard xcodebuild');
}
// Create the server
const server = createServer();
await bootstrapServer(server);
// Start the server
await startServer(server);
// Clean up on exit
process.on('SIGTERM', async () => {
await getDefaultDebuggerManager().disposeAll();
await server.close();
process.exit(0);
});
process.on('SIGINT', async () => {
await getDefaultDebuggerManager().disposeAll();
await server.close();
process.exit(0);
});
// Log successful startup
log('info', `XcodeBuildMCP server (version ${version}) started successfully`);
} catch (error) {
console.error('Fatal error in main():', error);
process.exit(1);
}
}
// Start the server
main().catch((error) => {
console.error('Unhandled exception:', error);
// Give Sentry a moment to send the error before exiting
setTimeout(() => process.exit(1), 1000);
});
```
--------------------------------------------------------------------------------
/src/mcp/tools/debugging/debug_breakpoint_add.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { createErrorResponse, createTextResponse } from '../../../utils/responses/index.ts';
import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
import { createTypedToolWithContext } from '../../../utils/typed-tool-factory.ts';
import {
getDefaultDebuggerToolContext,
type DebuggerToolContext,
type BreakpointSpec,
} from '../../../utils/debugger/index.ts';
const baseSchemaObject = z.object({
debugSessionId: z
.string()
.optional()
.describe('Debug session ID to target (defaults to current session)'),
file: z.string().optional().describe('Source file path for breakpoint'),
line: z.number().int().positive().optional().describe('Line number for breakpoint'),
function: z.string().optional().describe('Function name to break on'),
condition: z.string().optional().describe('Optional condition expression for the breakpoint'),
});
const debugBreakpointAddSchema = z.preprocess(
nullifyEmptyStrings,
baseSchemaObject
.refine((val) => !(val.file && val.function), {
message: 'Provide either file/line or function, not both.',
})
.refine((val) => Boolean(val.function ?? (val.file && val.line !== undefined)), {
message: 'Provide file + line or function.',
})
.refine((val) => !(val.line && !val.file), {
message: 'file is required when line is provided.',
}),
);
export type DebugBreakpointAddParams = z.infer<typeof debugBreakpointAddSchema>;
export async function debug_breakpoint_addLogic(
params: DebugBreakpointAddParams,
ctx: DebuggerToolContext,
): Promise<ToolResponse> {
try {
const spec: BreakpointSpec = params.function
? { kind: 'function', name: params.function }
: { kind: 'file-line', file: params.file!, line: params.line! };
const result = await ctx.debugger.addBreakpoint(params.debugSessionId, spec, {
condition: params.condition,
});
return createTextResponse(`✅ Breakpoint ${result.id} set.\n\n${result.rawOutput.trim()}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return createErrorResponse('Failed to add breakpoint', message);
}
}
export default {
name: 'debug_breakpoint_add',
description: 'Add a breakpoint by file/line or function name for the active debug session.',
schema: baseSchemaObject.shape,
handler: createTypedToolWithContext<DebugBreakpointAddParams, DebuggerToolContext>(
debugBreakpointAddSchema as unknown as z.ZodType<DebugBreakpointAddParams, unknown>,
debug_breakpoint_addLogic,
getDefaultDebuggerToolContext,
),
};
```
--------------------------------------------------------------------------------
/src/mcp/tools/simulator/boot_sim.ts:
--------------------------------------------------------------------------------
```typescript
import * as z from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { log } from '../../../utils/logging/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import {
createSessionAwareTool,
getSessionAwareToolSchemaShape,
} from '../../../utils/typed-tool-factory.ts';
const bootSimSchemaObject = z.object({
simulatorId: z.string().describe('UUID of the simulator to use (obtained from list_sims)'),
});
type BootSimParams = z.infer<typeof bootSimSchemaObject>;
const publicSchemaObject = z.strictObject(
bootSimSchemaObject.omit({
simulatorId: true,
} as const).shape,
);
export async function boot_simLogic(
params: BootSimParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
log('info', `Starting xcrun simctl boot request for simulator ${params.simulatorId}`);
try {
const command = ['xcrun', 'simctl', 'boot', params.simulatorId];
const result = await executor(command, 'Boot Simulator', true);
if (!result.success) {
return {
content: [
{
type: 'text',
text: `Boot simulator operation failed: ${result.error}`,
},
],
};
}
return {
content: [
{
type: 'text',
text: `✅ Simulator booted successfully. To make it visible, use: open_sim()
Next steps:
1. Open the Simulator app (makes it visible): open_sim()
2. Install an app: install_app_sim({ simulatorId: "${params.simulatorId}", appPath: "PATH_TO_YOUR_APP" })
3. Launch an app: launch_app_sim({ simulatorId: "${params.simulatorId}", bundleId: "YOUR_APP_BUNDLE_ID" })`,
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error during boot simulator operation: ${errorMessage}`);
return {
content: [
{
type: 'text',
text: `Boot simulator operation failed: ${errorMessage}`,
},
],
};
}
}
export default {
name: 'boot_sim',
description: 'Boots an iOS simulator.',
schema: getSessionAwareToolSchemaShape({
sessionAware: publicSchemaObject,
legacy: bootSimSchemaObject,
}),
annotations: {
title: 'Boot Simulator',
destructiveHint: true,
},
handler: createSessionAwareTool<BootSimParams>({
internalSchema: bootSimSchemaObject as unknown as z.ZodType<BootSimParams, unknown>,
logicFunction: boot_simLogic,
getExecutor: getDefaultCommandExecutor,
requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
}),
};
```
--------------------------------------------------------------------------------
/src/utils/debugger/ui-automation-guard.ts:
--------------------------------------------------------------------------------
```typescript
import type { ToolResponse } from '../../types/common.ts';
import { createErrorResponse } from '../responses/index.ts';
import { log } from '../logging/index.ts';
import { getUiDebuggerGuardMode } from '../environment.ts';
import type { DebugExecutionState } from './types.ts';
import type { DebuggerManager } from './debugger-manager.ts';
type GuardResult = {
blockedResponse?: ToolResponse;
warningText?: string;
};
const LOG_PREFIX = '[UI Automation Guard]';
export async function guardUiAutomationAgainstStoppedDebugger(opts: {
debugger: DebuggerManager;
simulatorId: string;
toolName: string;
mode?: 'error' | 'warn' | 'off';
}): Promise<GuardResult> {
const mode = opts.mode ?? getUiDebuggerGuardMode();
if (mode === 'off') return {};
const session = opts.debugger.findSessionForSimulator(opts.simulatorId);
if (!session) return {};
let state: DebugExecutionState;
try {
state = await opts.debugger.getExecutionState(session.id);
} catch (error) {
log(
'warn',
`${LOG_PREFIX} ${opts.toolName}: unable to read execution state for ${session.id}: ${String(error)}`,
);
return {};
}
if (state.status !== 'stopped') return {};
const details = buildGuardDetails({
toolName: opts.toolName,
simulatorId: opts.simulatorId,
sessionId: session.id,
backend: session.backend,
pid: session.pid,
state,
});
if (mode === 'warn') {
return { warningText: buildGuardWarning(details) };
}
return {
blockedResponse: createErrorResponse(
'UI automation blocked: app is paused in debugger',
details,
),
};
}
function buildGuardDetails(params: {
toolName: string;
simulatorId: string;
sessionId: string;
backend: string;
pid: number;
state: DebugExecutionState;
}): string {
const stateLabel = formatStateLabel(params.state);
const lines = [
`tool=${params.toolName}`,
`simulatorId=${params.simulatorId}`,
`debugSessionId=${params.sessionId}`,
`backend=${params.backend}`,
`pid=${params.pid}`,
`state=${stateLabel}`,
];
if (params.state.description) {
lines.push(`stateDetails=${params.state.description}`);
}
lines.push(
'',
'Resume execution via debug_continue, remove breakpoints, or detach via debug_detach before using UI tools.',
);
return lines.join('\n');
}
function formatStateLabel(state: DebugExecutionState): string {
if (!state.reason) return state.status;
return `${state.status} (${state.reason})`;
}
function buildGuardWarning(details: string): string {
return [
'Warning: debugger reports the app is paused; UI automation may return empty results.',
details,
].join('\n');
}
```