#
tokens: 49663/50000 31/393 files (page 3/16)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 16. Use http://codebase.md/cameroncooke/xcodebuildmcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .axe-version
├── .claude
│   └── agents
│       └── xcodebuild-mcp-qa-tester.md
├── .cursor
│   ├── BUGBOT.md
│   └── environment.json
├── .cursorrules
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   └── workflows
│       ├── ci.yml
│       ├── README.md
│       ├── release.yml
│       ├── sentry.yml
│       └── stale.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   ├── mcp.json
│   ├── settings.json
│   └── tasks.json
├── AGENTS.md
├── banner.png
├── build-plugins
│   ├── plugin-discovery.js
│   ├── plugin-discovery.ts
│   └── tsconfig.json
├── CHANGELOG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── docs
│   ├── CONFIGURATION.md
│   ├── DAP_BACKEND_IMPLEMENTATION_PLAN.md
│   ├── DEBUGGING_ARCHITECTURE.md
│   ├── DEMOS.md
│   ├── dev
│   │   ├── ARCHITECTURE.md
│   │   ├── CODE_QUALITY.md
│   │   ├── CONTRIBUTING.md
│   │   ├── ESLINT_TYPE_SAFETY.md
│   │   ├── MANUAL_TESTING.md
│   │   ├── NODEJS_2025.md
│   │   ├── PLUGIN_DEVELOPMENT.md
│   │   ├── README.md
│   │   ├── RELEASE_PROCESS.md
│   │   ├── RELOADEROO_FOR_XCODEBUILDMCP.md
│   │   ├── RELOADEROO_XCODEBUILDMCP_PRIMER.md
│   │   ├── RELOADEROO.md
│   │   ├── session_management_plan.md
│   │   ├── session-aware-migration-todo.md
│   │   ├── SMITHERY.md
│   │   ├── TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md
│   │   ├── TESTING.md
│   │   └── ZOD_MIGRATION_GUIDE.md
│   ├── DEVICE_CODE_SIGNING.md
│   ├── GETTING_STARTED.md
│   ├── investigations
│   │   ├── issue-154-screenshot-downscaling.md
│   │   ├── issue-163.md
│   │   ├── issue-debugger-attach-stopped.md
│   │   └── issue-describe-ui-empty-after-debugger-resume.md
│   ├── OVERVIEW.md
│   ├── PRIVACY.md
│   ├── README.md
│   ├── SESSION_DEFAULTS.md
│   ├── TOOLS.md
│   └── TROUBLESHOOTING.md
├── eslint.config.js
├── example_projects
│   ├── .vscode
│   │   └── launch.json
│   ├── iOS
│   │   ├── .cursor
│   │   │   └── rules
│   │   │       └── errors.mdc
│   │   ├── .vscode
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── MCPTest
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   ├── MCPTestApp.swift
│   │   │   └── Preview Content
│   │   │       └── Preview Assets.xcassets
│   │   │           └── Contents.json
│   │   ├── MCPTest.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata
│   │   │       └── xcschemes
│   │   │           └── MCPTest.xcscheme
│   │   └── MCPTestUITests
│   │       └── MCPTestUITests.swift
│   ├── iOS_Calculator
│   │   ├── .gitignore
│   │   ├── CalculatorApp
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── CalculatorApp.swift
│   │   │   └── CalculatorApp.xctestplan
│   │   ├── CalculatorApp.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata
│   │   │       └── xcschemes
│   │   │           └── CalculatorApp.xcscheme
│   │   ├── CalculatorApp.xcworkspace
│   │   │   └── contents.xcworkspacedata
│   │   ├── CalculatorAppPackage
│   │   │   ├── .gitignore
│   │   │   ├── Package.swift
│   │   │   ├── Sources
│   │   │   │   └── CalculatorAppFeature
│   │   │   │       ├── BackgroundEffect.swift
│   │   │   │       ├── CalculatorButton.swift
│   │   │   │       ├── CalculatorDisplay.swift
│   │   │   │       ├── CalculatorInputHandler.swift
│   │   │   │       ├── CalculatorService.swift
│   │   │   │       └── ContentView.swift
│   │   │   └── Tests
│   │   │       └── CalculatorAppFeatureTests
│   │   │           └── CalculatorServiceTests.swift
│   │   ├── CalculatorAppTests
│   │   │   └── CalculatorAppTests.swift
│   │   └── Config
│   │       ├── Debug.xcconfig
│   │       ├── Release.xcconfig
│   │       ├── Shared.xcconfig
│   │       └── Tests.xcconfig
│   ├── macOS
│   │   ├── MCPTest
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   ├── MCPTest.entitlements
│   │   │   ├── MCPTestApp.swift
│   │   │   └── Preview Content
│   │   │       └── Preview Assets.xcassets
│   │   │           └── Contents.json
│   │   ├── MCPTest.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata
│   │   │       └── xcschemes
│   │   │           └── MCPTest.xcscheme
│   │   └── MCPTestTests
│   │       └── MCPTestTests.swift
│   └── spm
│       ├── .gitignore
│       ├── Package.resolved
│       ├── Package.swift
│       ├── Sources
│       │   ├── long-server
│       │   │   └── main.swift
│       │   ├── quick-task
│       │   │   └── main.swift
│       │   ├── spm
│       │   │   └── main.swift
│       │   └── TestLib
│       │       └── TaskManager.swift
│       └── Tests
│           └── TestLibTests
│               └── SimpleTests.swift
├── LICENSE
├── mcp-install-dark.png
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── analysis
│   │   └── tools-analysis.ts
│   ├── bundle-axe.sh
│   ├── check-code-patterns.js
│   ├── generate-loaders.ts
│   ├── generate-version.ts
│   ├── release.sh
│   ├── tools-cli.ts
│   ├── update-tools-docs.ts
│   └── verify-smithery-bundle.sh
├── server.json
├── smithery.config.js
├── smithery.yaml
├── src
│   ├── core
│   │   ├── __tests__
│   │   │   └── resources.test.ts
│   │   ├── generated-plugins.ts
│   │   ├── generated-resources.ts
│   │   ├── plugin-registry.ts
│   │   ├── plugin-types.ts
│   │   └── resources.ts
│   ├── doctor-cli.ts
│   ├── index.ts
│   ├── mcp
│   │   ├── resources
│   │   │   ├── __tests__
│   │   │   │   ├── devices.test.ts
│   │   │   │   ├── doctor.test.ts
│   │   │   │   ├── session-status.test.ts
│   │   │   │   └── simulators.test.ts
│   │   │   ├── devices.ts
│   │   │   ├── doctor.ts
│   │   │   ├── session-status.ts
│   │   │   └── simulators.ts
│   │   └── tools
│   │       ├── debugging
│   │       │   ├── debug_attach_sim.ts
│   │       │   ├── debug_breakpoint_add.ts
│   │       │   ├── debug_breakpoint_remove.ts
│   │       │   ├── debug_continue.ts
│   │       │   ├── debug_detach.ts
│   │       │   ├── debug_lldb_command.ts
│   │       │   ├── debug_stack.ts
│   │       │   ├── debug_variables.ts
│   │       │   └── index.ts
│   │       ├── device
│   │       │   ├── __tests__
│   │       │   │   ├── build_device.test.ts
│   │       │   │   ├── get_device_app_path.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── install_app_device.test.ts
│   │       │   │   ├── launch_app_device.test.ts
│   │       │   │   ├── list_devices.test.ts
│   │       │   │   ├── re-exports.test.ts
│   │       │   │   ├── stop_app_device.test.ts
│   │       │   │   └── test_device.test.ts
│   │       │   ├── build_device.ts
│   │       │   ├── clean.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_app_bundle_id.ts
│   │       │   ├── get_device_app_path.ts
│   │       │   ├── index.ts
│   │       │   ├── install_app_device.ts
│   │       │   ├── launch_app_device.ts
│   │       │   ├── list_devices.ts
│   │       │   ├── list_schemes.ts
│   │       │   ├── show_build_settings.ts
│   │       │   ├── start_device_log_cap.ts
│   │       │   ├── stop_app_device.ts
│   │       │   ├── stop_device_log_cap.ts
│   │       │   └── test_device.ts
│   │       ├── doctor
│   │       │   ├── __tests__
│   │       │   │   ├── doctor.test.ts
│   │       │   │   └── index.test.ts
│   │       │   ├── doctor.ts
│   │       │   ├── index.ts
│   │       │   └── lib
│   │       │       └── doctor.deps.ts
│   │       ├── logging
│   │       │   ├── __tests__
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── start_device_log_cap.test.ts
│   │       │   │   ├── start_sim_log_cap.test.ts
│   │       │   │   ├── stop_device_log_cap.test.ts
│   │       │   │   └── stop_sim_log_cap.test.ts
│   │       │   ├── index.ts
│   │       │   ├── start_device_log_cap.ts
│   │       │   ├── start_sim_log_cap.ts
│   │       │   ├── stop_device_log_cap.ts
│   │       │   └── stop_sim_log_cap.ts
│   │       ├── macos
│   │       │   ├── __tests__
│   │       │   │   ├── build_macos.test.ts
│   │       │   │   ├── build_run_macos.test.ts
│   │       │   │   ├── get_mac_app_path.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── launch_mac_app.test.ts
│   │       │   │   ├── re-exports.test.ts
│   │       │   │   ├── stop_mac_app.test.ts
│   │       │   │   └── test_macos.test.ts
│   │       │   ├── build_macos.ts
│   │       │   ├── build_run_macos.ts
│   │       │   ├── clean.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_mac_app_path.ts
│   │       │   ├── get_mac_bundle_id.ts
│   │       │   ├── index.ts
│   │       │   ├── launch_mac_app.ts
│   │       │   ├── list_schemes.ts
│   │       │   ├── show_build_settings.ts
│   │       │   ├── stop_mac_app.ts
│   │       │   └── test_macos.ts
│   │       ├── project-discovery
│   │       │   ├── __tests__
│   │       │   │   ├── discover_projs.test.ts
│   │       │   │   ├── get_app_bundle_id.test.ts
│   │       │   │   ├── get_mac_bundle_id.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── list_schemes.test.ts
│   │       │   │   └── show_build_settings.test.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_app_bundle_id.ts
│   │       │   ├── get_mac_bundle_id.ts
│   │       │   ├── index.ts
│   │       │   ├── list_schemes.ts
│   │       │   └── show_build_settings.ts
│   │       ├── project-scaffolding
│   │       │   ├── __tests__
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── scaffold_ios_project.test.ts
│   │       │   │   └── scaffold_macos_project.test.ts
│   │       │   ├── index.ts
│   │       │   ├── scaffold_ios_project.ts
│   │       │   └── scaffold_macos_project.ts
│   │       ├── session-management
│   │       │   ├── __tests__
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── session_clear_defaults.test.ts
│   │       │   │   ├── session_set_defaults.test.ts
│   │       │   │   └── session_show_defaults.test.ts
│   │       │   ├── index.ts
│   │       │   ├── session_clear_defaults.ts
│   │       │   ├── session_set_defaults.ts
│   │       │   └── session_show_defaults.ts
│   │       ├── simulator
│   │       │   ├── __tests__
│   │       │   │   ├── boot_sim.test.ts
│   │       │   │   ├── build_run_sim.test.ts
│   │       │   │   ├── build_sim.test.ts
│   │       │   │   ├── get_sim_app_path.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── install_app_sim.test.ts
│   │       │   │   ├── launch_app_logs_sim.test.ts
│   │       │   │   ├── launch_app_sim.test.ts
│   │       │   │   ├── list_sims.test.ts
│   │       │   │   ├── open_sim.test.ts
│   │       │   │   ├── record_sim_video.test.ts
│   │       │   │   ├── screenshot.test.ts
│   │       │   │   ├── stop_app_sim.test.ts
│   │       │   │   └── test_sim.test.ts
│   │       │   ├── boot_sim.ts
│   │       │   ├── build_run_sim.ts
│   │       │   ├── build_sim.ts
│   │       │   ├── clean.ts
│   │       │   ├── describe_ui.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_app_bundle_id.ts
│   │       │   ├── get_sim_app_path.ts
│   │       │   ├── index.ts
│   │       │   ├── install_app_sim.ts
│   │       │   ├── launch_app_logs_sim.ts
│   │       │   ├── launch_app_sim.ts
│   │       │   ├── list_schemes.ts
│   │       │   ├── list_sims.ts
│   │       │   ├── open_sim.ts
│   │       │   ├── record_sim_video.ts
│   │       │   ├── screenshot.ts
│   │       │   ├── show_build_settings.ts
│   │       │   ├── stop_app_sim.ts
│   │       │   └── test_sim.ts
│   │       ├── simulator-management
│   │       │   ├── __tests__
│   │       │   │   ├── erase_sims.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── reset_sim_location.test.ts
│   │       │   │   ├── set_sim_appearance.test.ts
│   │       │   │   ├── set_sim_location.test.ts
│   │       │   │   └── sim_statusbar.test.ts
│   │       │   ├── boot_sim.ts
│   │       │   ├── erase_sims.ts
│   │       │   ├── index.ts
│   │       │   ├── list_sims.ts
│   │       │   ├── open_sim.ts
│   │       │   ├── reset_sim_location.ts
│   │       │   ├── set_sim_appearance.ts
│   │       │   ├── set_sim_location.ts
│   │       │   └── sim_statusbar.ts
│   │       ├── swift-package
│   │       │   ├── __tests__
│   │       │   │   ├── active-processes.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── swift_package_build.test.ts
│   │       │   │   ├── swift_package_clean.test.ts
│   │       │   │   ├── swift_package_list.test.ts
│   │       │   │   ├── swift_package_run.test.ts
│   │       │   │   ├── swift_package_stop.test.ts
│   │       │   │   └── swift_package_test.test.ts
│   │       │   ├── active-processes.ts
│   │       │   ├── index.ts
│   │       │   ├── swift_package_build.ts
│   │       │   ├── swift_package_clean.ts
│   │       │   ├── swift_package_list.ts
│   │       │   ├── swift_package_run.ts
│   │       │   ├── swift_package_stop.ts
│   │       │   └── swift_package_test.ts
│   │       ├── ui-testing
│   │       │   ├── __tests__
│   │       │   │   ├── button.test.ts
│   │       │   │   ├── describe_ui.test.ts
│   │       │   │   ├── gesture.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── key_press.test.ts
│   │       │   │   ├── key_sequence.test.ts
│   │       │   │   ├── long_press.test.ts
│   │       │   │   ├── screenshot.test.ts
│   │       │   │   ├── swipe.test.ts
│   │       │   │   ├── tap.test.ts
│   │       │   │   ├── touch.test.ts
│   │       │   │   └── type_text.test.ts
│   │       │   ├── button.ts
│   │       │   ├── describe_ui.ts
│   │       │   ├── gesture.ts
│   │       │   ├── index.ts
│   │       │   ├── key_press.ts
│   │       │   ├── key_sequence.ts
│   │       │   ├── long_press.ts
│   │       │   ├── screenshot.ts
│   │       │   ├── swipe.ts
│   │       │   ├── tap.ts
│   │       │   ├── touch.ts
│   │       │   └── type_text.ts
│   │       └── utilities
│   │           ├── __tests__
│   │           │   ├── clean.test.ts
│   │           │   └── index.test.ts
│   │           ├── clean.ts
│   │           └── index.ts
│   ├── server
│   │   ├── bootstrap.ts
│   │   └── server.ts
│   ├── smithery.ts
│   ├── test-utils
│   │   └── mock-executors.ts
│   ├── types
│   │   └── common.ts
│   ├── utils
│   │   ├── __tests__
│   │   │   ├── build-utils-suppress-warnings.test.ts
│   │   │   ├── build-utils.test.ts
│   │   │   ├── debugger-simctl.test.ts
│   │   │   ├── environment.test.ts
│   │   │   ├── session-aware-tool-factory.test.ts
│   │   │   ├── session-store.test.ts
│   │   │   ├── simulator-utils.test.ts
│   │   │   ├── test-runner-env-integration.test.ts
│   │   │   ├── typed-tool-factory.test.ts
│   │   │   └── workflow-selection.test.ts
│   │   ├── axe
│   │   │   └── index.ts
│   │   ├── axe-helpers.ts
│   │   ├── build
│   │   │   └── index.ts
│   │   ├── build-utils.ts
│   │   ├── capabilities.ts
│   │   ├── command.ts
│   │   ├── CommandExecutor.ts
│   │   ├── debugger
│   │   │   ├── __tests__
│   │   │   │   └── debugger-manager-dap.test.ts
│   │   │   ├── backends
│   │   │   │   ├── __tests__
│   │   │   │   │   └── dap-backend.test.ts
│   │   │   │   ├── dap-backend.ts
│   │   │   │   ├── DebuggerBackend.ts
│   │   │   │   └── lldb-cli-backend.ts
│   │   │   ├── dap
│   │   │   │   ├── __tests__
│   │   │   │   │   └── transport-framing.test.ts
│   │   │   │   ├── adapter-discovery.ts
│   │   │   │   ├── transport.ts
│   │   │   │   └── types.ts
│   │   │   ├── debugger-manager.ts
│   │   │   ├── index.ts
│   │   │   ├── simctl.ts
│   │   │   ├── tool-context.ts
│   │   │   ├── types.ts
│   │   │   └── ui-automation-guard.ts
│   │   ├── environment.ts
│   │   ├── errors.ts
│   │   ├── execution
│   │   │   ├── index.ts
│   │   │   └── interactive-process.ts
│   │   ├── FileSystemExecutor.ts
│   │   ├── log_capture.ts
│   │   ├── log-capture
│   │   │   ├── device-log-sessions.ts
│   │   │   └── index.ts
│   │   ├── logger.ts
│   │   ├── logging
│   │   │   └── index.ts
│   │   ├── plugin-registry
│   │   │   └── index.ts
│   │   ├── responses
│   │   │   └── index.ts
│   │   ├── runtime-registry.ts
│   │   ├── schema-helpers.ts
│   │   ├── sentry.ts
│   │   ├── session-status.ts
│   │   ├── session-store.ts
│   │   ├── simulator-utils.ts
│   │   ├── template
│   │   │   └── index.ts
│   │   ├── template-manager.ts
│   │   ├── test
│   │   │   └── index.ts
│   │   ├── test-common.ts
│   │   ├── tool-registry.ts
│   │   ├── typed-tool-factory.ts
│   │   ├── validation
│   │   │   └── index.ts
│   │   ├── validation.ts
│   │   ├── version
│   │   │   └── index.ts
│   │   ├── video_capture.ts
│   │   ├── video-capture
│   │   │   └── index.ts
│   │   ├── workflow-selection.ts
│   │   ├── xcode.ts
│   │   ├── xcodemake
│   │   │   └── index.ts
│   │   └── xcodemake.ts
│   └── version.ts
├── tsconfig.json
├── tsconfig.test.json
├── tsconfig.tests.json
├── tsup.config.ts
├── vitest.config.ts
└── XcodeBuildMCP.code-workspace
```

# Files

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "xcodebuildmcp",
  3 |   "version": "1.15.1",
  4 |   "mcpName": "com.xcodebuildmcp/XcodeBuildMCP",
  5 |   "iOSTemplateVersion": "v1.0.8",
  6 |   "macOSTemplateVersion": "v1.0.5",
  7 |   "type": "module",
  8 |   "module": "src/smithery.ts",
  9 |   "exports": {
 10 |     ".": "./build/index.js",
 11 |     "./package.json": "./package.json"
 12 |   },
 13 |   "bin": {
 14 |     "xcodebuildmcp": "build/index.js",
 15 |     "xcodebuildmcp-doctor": "build/doctor-cli.js"
 16 |   },
 17 |   "scripts": {
 18 |     "build": "npm run build:tsup && npx smithery build",
 19 |     "dev": "npm run generate:version && npm run generate:loaders && npx smithery dev",
 20 |     "build:tsup": "npm run generate:version && npm run generate:loaders && tsup",
 21 |     "dev:tsup": "npm run build:tsup && tsup --watch",
 22 |     "generate:version": "npx tsx scripts/generate-version.ts",
 23 |     "generate:loaders": "npx tsx scripts/generate-loaders.ts",
 24 |     "bundle:axe": "scripts/bundle-axe.sh",
 25 |     "lint": "eslint 'src/**/*.{js,ts}'",
 26 |     "lint:fix": "eslint 'src/**/*.{js,ts}' --fix",
 27 |     "format": "prettier --write 'src/**/*.{js,ts}'",
 28 |     "format:check": "prettier --check 'src/**/*.{js,ts}'",
 29 |     "typecheck": "npx tsc --noEmit && npx tsc -p tsconfig.test.json",
 30 |     "typecheck:tests": "npx tsc -p tsconfig.test.json",
 31 |     "verify:smithery-bundle": "bash scripts/verify-smithery-bundle.sh",
 32 |     "inspect": "npx @modelcontextprotocol/inspector node build/index.js",
 33 |     "doctor": "node build/doctor-cli.js",
 34 |     "tools": "npx tsx scripts/tools-cli.ts",
 35 |     "tools:list": "npx tsx scripts/tools-cli.ts list",
 36 |     "tools:static": "npx tsx scripts/tools-cli.ts static",
 37 |     "tools:count": "npx tsx scripts/tools-cli.ts count --static",
 38 |     "tools:analysis": "npx tsx scripts/analysis/tools-analysis.ts",
 39 |     "docs:update": "npx tsx scripts/update-tools-docs.ts",
 40 |     "docs:update:dry-run": "npx tsx scripts/update-tools-docs.ts --dry-run --verbose",
 41 |     "test": "vitest run",
 42 |     "test:watch": "vitest",
 43 |     "test:ui": "vitest --ui",
 44 |     "test:coverage": "vitest run --coverage"
 45 |   },
 46 |   "files": [
 47 |     "build",
 48 |     "bundled",
 49 |     "plugins"
 50 |   ],
 51 |   "keywords": [
 52 |     "xcodebuild",
 53 |     "mcp",
 54 |     "modelcontextprotocol",
 55 |     "xcode",
 56 |     "ios",
 57 |     "macos",
 58 |     "simulator"
 59 |   ],
 60 |   "author": "Cameron Cooke",
 61 |   "license": "MIT",
 62 |   "description": "XcodeBuildMCP is a ModelContextProtocol server that provides tools for Xcode project management, simulator management, and app utilities.",
 63 |   "repository": {
 64 |     "type": "git",
 65 |     "url": "git+https://github.com/cameroncooke/XcodeBuildMCP.git"
 66 |   },
 67 |   "homepage": "https://www.async-let.com/blog/xcodebuild-mcp/",
 68 |   "bugs": {
 69 |     "url": "https://github.com/cameroncooke/XcodeBuildMCP/issues"
 70 |   },
 71 |   "dependencies": {
 72 |     "@modelcontextprotocol/sdk": "^1.25.1",
 73 |     "@sentry/cli": "^2.43.1",
 74 |     "@sentry/node": "^10.5.0",
 75 |     "uuid": "^11.1.0",
 76 |     "zod": "^4.0.0"
 77 |   },
 78 |   "devDependencies": {
 79 |     "@bacons/xcode": "^1.0.0-alpha.24",
 80 |     "@eslint/eslintrc": "^3.3.1",
 81 |     "@eslint/js": "^9.23.0",
 82 |     "@smithery/cli": "^1.4.6",
 83 |     "@types/node": "^22.13.6",
 84 |     "@typescript-eslint/eslint-plugin": "^8.28.0",
 85 |     "@typescript-eslint/parser": "^8.28.0",
 86 |     "@vitest/coverage-v8": "^3.2.4",
 87 |     "@vitest/ui": "^3.2.4",
 88 |     "eslint": "^9.23.0",
 89 |     "eslint-config-prettier": "^10.1.1",
 90 |     "eslint-plugin-prettier": "^5.2.5",
 91 |     "playwright": "^1.53.0",
 92 |     "prettier": "3.6.2",
 93 |     "ts-node": "^10.9.2",
 94 |     "tsup": "^8.5.0",
 95 |     "tsx": "^4.20.4",
 96 |     "typescript": "^5.8.2",
 97 |     "typescript-eslint": "^8.28.0",
 98 |     "vitest": "^3.2.4",
 99 |     "xcode": "^3.0.1"
100 |   }
101 | }
```

--------------------------------------------------------------------------------
/src/utils/xcode.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Xcode Utilities - Core infrastructure for interacting with Xcode tools
 3 |  *
 4 |  * This utility module provides the foundation for all Xcode interactions across the codebase.
 5 |  * It offers platform-specific utilities, and common functionality that can be used by any module
 6 |  * requiring Xcode tool integration.
 7 |  *
 8 |  * Responsibilities:
 9 |  * - Constructing platform-specific destination strings (constructDestinationString)
10 |  *
11 |  * This file serves as the foundation layer for more specialized utilities like build-utils.ts,
12 |  * which build upon these core functions to provide higher-level abstractions.
13 |  */
14 | 
15 | import { log } from './logger.ts';
16 | import { XcodePlatform } from '../types/common.ts';
17 | 
18 | // Re-export XcodePlatform for use in other modules
19 | export { XcodePlatform };
20 | 
21 | /**
22 |  * Constructs a destination string for xcodebuild from platform and simulator parameters
23 |  * @param platform The target platform
24 |  * @param simulatorName Optional simulator name
25 |  * @param simulatorId Optional simulator UUID
26 |  * @param useLatest Whether to use the latest simulator version (primarily for named simulators)
27 |  * @param arch Optional architecture for macOS builds (arm64 or x86_64)
28 |  * @returns Properly formatted destination string for xcodebuild
29 |  */
30 | export function constructDestinationString(
31 |   platform: XcodePlatform,
32 |   simulatorName?: string,
33 |   simulatorId?: string,
34 |   useLatest: boolean = true,
35 |   arch?: string,
36 | ): string {
37 |   const isSimulatorPlatform = [
38 |     XcodePlatform.iOSSimulator,
39 |     XcodePlatform.watchOSSimulator,
40 |     XcodePlatform.tvOSSimulator,
41 |     XcodePlatform.visionOSSimulator,
42 |   ].includes(platform);
43 | 
44 |   // If ID is provided for a simulator, it takes precedence and uniquely identifies it.
45 |   if (isSimulatorPlatform && simulatorId) {
46 |     return `platform=${platform},id=${simulatorId}`;
47 |   }
48 | 
49 |   // If name is provided for a simulator
50 |   if (isSimulatorPlatform && simulatorName) {
51 |     return `platform=${platform},name=${simulatorName}${useLatest ? ',OS=latest' : ''}`;
52 |   }
53 | 
54 |   // If it's a simulator platform but neither ID nor name is provided (should be prevented by callers now)
55 |   if (isSimulatorPlatform && !simulatorId && !simulatorName) {
56 |     // Throw error as specific simulator is needed unless it's a generic build action
57 |     // Allow fallback for generic simulator builds if needed, but generally require specifics for build/run
58 |     log(
59 |       'warning',
60 |       `Constructing generic destination for ${platform} without name or ID. This might not be specific enough.`,
61 |     );
62 |     // Example: return `platform=${platform},name=Any ${platform} Device`; // Or similar generic target
63 |     throw new Error(`Simulator name or ID is required for specific ${platform} operations`);
64 |   }
65 | 
66 |   // Handle non-simulator platforms
67 |   switch (platform) {
68 |     case XcodePlatform.macOS:
69 |       return arch ? `platform=macOS,arch=${arch}` : 'platform=macOS';
70 |     case XcodePlatform.iOS:
71 |       return 'generic/platform=iOS';
72 |     case XcodePlatform.watchOS:
73 |       return 'generic/platform=watchOS';
74 |     case XcodePlatform.tvOS:
75 |       return 'generic/platform=tvOS';
76 |     case XcodePlatform.visionOS:
77 |       return 'generic/platform=visionOS';
78 |     // No default needed as enum covers all cases unless extended
79 |     // default:
80 |     //   throw new Error(`Unsupported platform for destination string: ${platform}`);
81 |   }
82 |   // Fallback just in case (shouldn't be reached with enum)
83 |   log('error', `Reached unexpected point in constructDestinationString for platform: ${platform}`);
84 |   return `platform=${platform}`;
85 | }
86 | 
```

--------------------------------------------------------------------------------
/src/mcp/resources/__tests__/doctor.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from 'vitest';
 2 | 
 3 | import doctorResource, { doctorResourceLogic } from '../doctor.ts';
 4 | import { createMockExecutor } from '../../../test-utils/mock-executors.ts';
 5 | 
 6 | describe('doctor resource', () => {
 7 |   describe('Export Field Validation', () => {
 8 |     it('should export correct uri', () => {
 9 |       expect(doctorResource.uri).toBe('xcodebuildmcp://doctor');
10 |     });
11 | 
12 |     it('should export correct description', () => {
13 |       expect(doctorResource.description).toBe(
14 |         'Comprehensive development environment diagnostic information and configuration status',
15 |       );
16 |     });
17 | 
18 |     it('should export correct mimeType', () => {
19 |       expect(doctorResource.mimeType).toBe('text/plain');
20 |     });
21 | 
22 |     it('should export handler function', () => {
23 |       expect(typeof doctorResource.handler).toBe('function');
24 |     });
25 |   });
26 | 
27 |   describe('Handler Functionality', () => {
28 |     it('should handle successful environment data retrieval', async () => {
29 |       const mockExecutor = createMockExecutor({
30 |         success: true,
31 |         output: 'Mock command output',
32 |       });
33 | 
34 |       const result = await doctorResourceLogic(mockExecutor);
35 | 
36 |       expect(result.contents).toHaveLength(1);
37 |       expect(result.contents[0].text).toContain('XcodeBuildMCP Doctor');
38 |       expect(result.contents[0].text).toContain('## System Information');
39 |       expect(result.contents[0].text).toContain('## Node.js Information');
40 |       expect(result.contents[0].text).toContain('## Dependencies');
41 |       expect(result.contents[0].text).toContain('## Environment Variables');
42 |       expect(result.contents[0].text).toContain('## Feature Status');
43 |     });
44 | 
45 |     it('should handle spawn errors by showing doctor info', async () => {
46 |       const mockExecutor = createMockExecutor(new Error('spawn xcrun ENOENT'));
47 | 
48 |       const result = await doctorResourceLogic(mockExecutor);
49 | 
50 |       expect(result.contents).toHaveLength(1);
51 |       expect(result.contents[0].text).toContain('XcodeBuildMCP Doctor');
52 |       expect(result.contents[0].text).toContain('Error: spawn xcrun ENOENT');
53 |     });
54 | 
55 |     it('should include required doctor sections', async () => {
56 |       const mockExecutor = createMockExecutor({
57 |         success: true,
58 |         output: 'Mock output',
59 |       });
60 | 
61 |       const result = await doctorResourceLogic(mockExecutor);
62 | 
63 |       expect(result.contents[0].text).toContain('## Troubleshooting Tips');
64 |       expect(result.contents[0].text).toContain('brew tap cameroncooke/axe');
65 |       expect(result.contents[0].text).toContain('INCREMENTAL_BUILDS_ENABLED=1');
66 |     });
67 | 
68 |     it('should provide feature status information', async () => {
69 |       const mockExecutor = createMockExecutor({
70 |         success: true,
71 |         output: 'Mock output',
72 |       });
73 | 
74 |       const result = await doctorResourceLogic(mockExecutor);
75 | 
76 |       expect(result.contents[0].text).toContain('### UI Automation (axe)');
77 |       expect(result.contents[0].text).toContain('### Incremental Builds');
78 |       expect(result.contents[0].text).toContain('### Mise Integration');
79 |       expect(result.contents[0].text).toContain('## Tool Availability Summary');
80 |     });
81 | 
82 |     it('should handle error conditions gracefully', async () => {
83 |       const mockExecutor = createMockExecutor({
84 |         success: false,
85 |         output: '',
86 |         error: 'Command failed',
87 |       });
88 | 
89 |       const result = await doctorResourceLogic(mockExecutor);
90 | 
91 |       expect(result.contents).toHaveLength(1);
92 |       expect(result.contents[0].text).toContain('XcodeBuildMCP Doctor');
93 |     });
94 |   });
95 | });
96 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/swift_package_test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import path from 'node:path';
  3 | import type { CommandExecutor } from '../../../utils/execution/index.ts';
  4 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  5 | import { createTextResponse, createErrorResponse } from '../../../utils/responses/index.ts';
  6 | import { log } from '../../../utils/logging/index.ts';
  7 | import { ToolResponse } from '../../../types/common.ts';
  8 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
  9 | 
 10 | // Define schema as ZodObject
 11 | const swiftPackageTestSchema = z.object({
 12 |   packagePath: z.string().describe('Path to the Swift package root (Required)'),
 13 |   testProduct: z.string().optional().describe('Optional specific test product to run'),
 14 |   filter: z.string().optional().describe('Filter tests by name (regex pattern)'),
 15 |   configuration: z
 16 |     .enum(['debug', 'release'])
 17 |     .optional()
 18 |     .describe('Swift package configuration (debug, release)'),
 19 |   parallel: z.boolean().optional().describe('Run tests in parallel (default: true)'),
 20 |   showCodecov: z.boolean().optional().describe('Show code coverage (default: false)'),
 21 |   parseAsLibrary: z
 22 |     .boolean()
 23 |     .optional()
 24 |     .describe('Add -parse-as-library flag for @main support (default: false)'),
 25 | });
 26 | 
 27 | // Use z.infer for type safety
 28 | type SwiftPackageTestParams = z.infer<typeof swiftPackageTestSchema>;
 29 | 
 30 | export async function swift_package_testLogic(
 31 |   params: SwiftPackageTestParams,
 32 |   executor: CommandExecutor,
 33 | ): Promise<ToolResponse> {
 34 |   const resolvedPath = path.resolve(params.packagePath);
 35 |   const swiftArgs = ['test', '--package-path', resolvedPath];
 36 | 
 37 |   if (params.configuration && params.configuration.toLowerCase() === 'release') {
 38 |     swiftArgs.push('-c', 'release');
 39 |   } else if (params.configuration && params.configuration.toLowerCase() !== 'debug') {
 40 |     return createTextResponse("Invalid configuration. Use 'debug' or 'release'.", true);
 41 |   }
 42 | 
 43 |   if (params.testProduct) {
 44 |     swiftArgs.push('--test-product', params.testProduct);
 45 |   }
 46 | 
 47 |   if (params.filter) {
 48 |     swiftArgs.push('--filter', params.filter);
 49 |   }
 50 | 
 51 |   if (params.parallel === false) {
 52 |     swiftArgs.push('--no-parallel');
 53 |   }
 54 | 
 55 |   if (params.showCodecov) {
 56 |     swiftArgs.push('--show-code-coverage');
 57 |   }
 58 | 
 59 |   if (params.parseAsLibrary) {
 60 |     swiftArgs.push('-Xswiftc', '-parse-as-library');
 61 |   }
 62 | 
 63 |   log('info', `Running swift ${swiftArgs.join(' ')}`);
 64 |   try {
 65 |     const result = await executor(['swift', ...swiftArgs], 'Swift Package Test', true, undefined);
 66 |     if (!result.success) {
 67 |       const errorMessage = result.error ?? result.output ?? 'Unknown error';
 68 |       return createErrorResponse('Swift package tests failed', errorMessage);
 69 |     }
 70 | 
 71 |     return {
 72 |       content: [
 73 |         { type: 'text', text: '✅ Swift package tests completed.' },
 74 |         {
 75 |           type: 'text',
 76 |           text: '💡 Next: Execute your app with swift_package_run if tests passed',
 77 |         },
 78 |         { type: 'text', text: result.output },
 79 |       ],
 80 |       isError: false,
 81 |     };
 82 |   } catch (error) {
 83 |     const message = error instanceof Error ? error.message : String(error);
 84 |     log('error', `Swift package test failed: ${message}`);
 85 |     return createErrorResponse('Failed to execute swift test', message);
 86 |   }
 87 | }
 88 | 
 89 | export default {
 90 |   name: 'swift_package_test',
 91 |   description: 'Runs tests for a Swift Package with swift test',
 92 |   schema: swiftPackageTestSchema.shape, // MCP SDK compatibility
 93 |   annotations: {
 94 |     title: 'Swift Package Test',
 95 |     destructiveHint: true,
 96 |   },
 97 |   handler: createTypedTool(
 98 |     swiftPackageTestSchema,
 99 |     swift_package_testLogic,
100 |     getDefaultCommandExecutor,
101 |   ),
102 | };
103 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/__tests__/reset_sim_location.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from 'vitest';
  2 | import * as z from 'zod';
  3 | import resetSimLocationPlugin, { reset_sim_locationLogic } from '../reset_sim_location.ts';
  4 | import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
  5 | 
  6 | describe('reset_sim_location plugin', () => {
  7 |   describe('Export Field Validation (Literal)', () => {
  8 |     it('should have correct name field', () => {
  9 |       expect(resetSimLocationPlugin.name).toBe('reset_sim_location');
 10 |     });
 11 | 
 12 |     it('should have correct description field', () => {
 13 |       expect(resetSimLocationPlugin.description).toBe(
 14 |         "Resets the simulator's location to default.",
 15 |       );
 16 |     });
 17 | 
 18 |     it('should have handler function', () => {
 19 |       expect(typeof resetSimLocationPlugin.handler).toBe('function');
 20 |     });
 21 | 
 22 |     it('should hide simulatorId from public schema', () => {
 23 |       const schema = z.object(resetSimLocationPlugin.schema);
 24 | 
 25 |       expect(schema.safeParse({}).success).toBe(true);
 26 | 
 27 |       const withSimId = schema.safeParse({ simulatorId: 'abc123' });
 28 |       expect(withSimId.success).toBe(true);
 29 |       expect('simulatorId' in (withSimId.data as any)).toBe(false);
 30 |     });
 31 |   });
 32 | 
 33 |   describe('Handler Behavior (Complete Literal Returns)', () => {
 34 |     it('should successfully reset simulator location', async () => {
 35 |       const mockExecutor = createMockExecutor({
 36 |         success: true,
 37 |         output: 'Location reset successfully',
 38 |       });
 39 | 
 40 |       const result = await reset_sim_locationLogic(
 41 |         {
 42 |           simulatorId: 'test-uuid-123',
 43 |         },
 44 |         mockExecutor,
 45 |       );
 46 | 
 47 |       expect(result).toEqual({
 48 |         content: [
 49 |           {
 50 |             type: 'text',
 51 |             text: 'Successfully reset simulator test-uuid-123 location.',
 52 |           },
 53 |         ],
 54 |       });
 55 |     });
 56 | 
 57 |     it('should handle command failure', async () => {
 58 |       const mockExecutor = createMockExecutor({
 59 |         success: false,
 60 |         error: 'Command failed',
 61 |       });
 62 | 
 63 |       const result = await reset_sim_locationLogic(
 64 |         {
 65 |           simulatorId: 'test-uuid-123',
 66 |         },
 67 |         mockExecutor,
 68 |       );
 69 | 
 70 |       expect(result).toEqual({
 71 |         content: [
 72 |           {
 73 |             type: 'text',
 74 |             text: 'Failed to reset simulator location: Command failed',
 75 |           },
 76 |         ],
 77 |       });
 78 |     });
 79 | 
 80 |     it('should handle exception during execution', async () => {
 81 |       const mockExecutor = createMockExecutor(new Error('Network error'));
 82 | 
 83 |       const result = await reset_sim_locationLogic(
 84 |         {
 85 |           simulatorId: 'test-uuid-123',
 86 |         },
 87 |         mockExecutor,
 88 |       );
 89 | 
 90 |       expect(result).toEqual({
 91 |         content: [
 92 |           {
 93 |             type: 'text',
 94 |             text: 'Failed to reset simulator location: Network error',
 95 |           },
 96 |         ],
 97 |       });
 98 |     });
 99 | 
100 |     it('should call correct command', async () => {
101 |       let capturedCommand: string[] = [];
102 |       let capturedLogPrefix: string | undefined;
103 | 
104 |       const mockExecutor = createMockExecutor({
105 |         success: true,
106 |         output: 'Location reset successfully',
107 |       });
108 | 
109 |       // Create a wrapper to capture the command arguments
110 |       const capturingExecutor = async (command: string[], logPrefix?: string) => {
111 |         capturedCommand = command;
112 |         capturedLogPrefix = logPrefix;
113 |         return mockExecutor(command, logPrefix);
114 |       };
115 | 
116 |       await reset_sim_locationLogic(
117 |         {
118 |           simulatorId: 'test-uuid-123',
119 |         },
120 |         capturingExecutor,
121 |       );
122 | 
123 |       expect(capturedCommand).toEqual(['xcrun', 'simctl', 'location', 'test-uuid-123', 'clear']);
124 |       expect(capturedLogPrefix).toBe('Reset Simulator Location');
125 |     });
126 |   });
127 | });
128 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/reset_sim_location.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import { ToolResponse } from '../../../types/common.ts';
  3 | import { log } from '../../../utils/logging/index.ts';
  4 | import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  5 | import {
  6 |   createSessionAwareTool,
  7 |   getSessionAwareToolSchemaShape,
  8 | } from '../../../utils/typed-tool-factory.ts';
  9 | 
 10 | // Define schema as ZodObject
 11 | const resetSimulatorLocationSchema = z.object({
 12 |   simulatorId: z.uuid().describe('UUID of the simulator to use (obtained from list_simulators)'),
 13 | });
 14 | 
 15 | // Use z.infer for type safety
 16 | type ResetSimulatorLocationParams = z.infer<typeof resetSimulatorLocationSchema>;
 17 | 
 18 | // Helper function to execute simctl commands and handle responses
 19 | async function executeSimctlCommandAndRespond(
 20 |   params: ResetSimulatorLocationParams,
 21 |   simctlSubCommand: string[],
 22 |   operationDescriptionForXcodeCommand: string,
 23 |   successMessage: string,
 24 |   failureMessagePrefix: string,
 25 |   operationLogContext: string,
 26 |   executor: CommandExecutor,
 27 |   extraValidation?: () => ToolResponse | undefined,
 28 | ): Promise<ToolResponse> {
 29 |   if (extraValidation) {
 30 |     const validationResult = extraValidation();
 31 |     if (validationResult) {
 32 |       return validationResult;
 33 |     }
 34 |   }
 35 | 
 36 |   try {
 37 |     const command = ['xcrun', 'simctl', ...simctlSubCommand];
 38 |     const result = await executor(command, operationDescriptionForXcodeCommand, true, {});
 39 | 
 40 |     if (!result.success) {
 41 |       const fullFailureMessage = `${failureMessagePrefix}: ${result.error}`;
 42 |       log(
 43 |         'error',
 44 |         `${fullFailureMessage} (operation: ${operationLogContext}, simulator: ${params.simulatorId})`,
 45 |       );
 46 |       return {
 47 |         content: [{ type: 'text', text: fullFailureMessage }],
 48 |       };
 49 |     }
 50 | 
 51 |     log(
 52 |       'info',
 53 |       `${successMessage} (operation: ${operationLogContext}, simulator: ${params.simulatorId})`,
 54 |     );
 55 |     return {
 56 |       content: [{ type: 'text', text: successMessage }],
 57 |     };
 58 |   } catch (error) {
 59 |     const errorMessage = error instanceof Error ? error.message : String(error);
 60 |     const fullFailureMessage = `${failureMessagePrefix}: ${errorMessage}`;
 61 |     log(
 62 |       'error',
 63 |       `Error during ${operationLogContext} for simulator ${params.simulatorId}: ${errorMessage}`,
 64 |     );
 65 |     return {
 66 |       content: [{ type: 'text', text: fullFailureMessage }],
 67 |     };
 68 |   }
 69 | }
 70 | 
 71 | export async function reset_sim_locationLogic(
 72 |   params: ResetSimulatorLocationParams,
 73 |   executor: CommandExecutor,
 74 | ): Promise<ToolResponse> {
 75 |   log('info', `Resetting simulator ${params.simulatorId} location`);
 76 | 
 77 |   return executeSimctlCommandAndRespond(
 78 |     params,
 79 |     ['location', params.simulatorId, 'clear'],
 80 |     'Reset Simulator Location',
 81 |     `Successfully reset simulator ${params.simulatorId} location.`,
 82 |     'Failed to reset simulator location',
 83 |     'reset simulator location',
 84 |     executor,
 85 |   );
 86 | }
 87 | 
 88 | const publicSchemaObject = z.strictObject(
 89 |   resetSimulatorLocationSchema.omit({ simulatorId: true } as const).shape,
 90 | );
 91 | 
 92 | export default {
 93 |   name: 'reset_sim_location',
 94 |   description: "Resets the simulator's location to default.",
 95 |   schema: getSessionAwareToolSchemaShape({
 96 |     sessionAware: publicSchemaObject,
 97 |     legacy: resetSimulatorLocationSchema,
 98 |   }),
 99 |   annotations: {
100 |     title: 'Reset Simulator Location',
101 |     destructiveHint: true,
102 |   },
103 |   handler: createSessionAwareTool<ResetSimulatorLocationParams>({
104 |     internalSchema: resetSimulatorLocationSchema as unknown as z.ZodType<
105 |       ResetSimulatorLocationParams,
106 |       unknown
107 |     >,
108 |     logicFunction: reset_sim_locationLogic,
109 |     getExecutor: getDefaultCommandExecutor,
110 |     requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
111 |   }),
112 | };
113 | 
```

--------------------------------------------------------------------------------
/src/utils/errors.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolResponse } from '../types/common.ts';
  2 | 
  3 | /**
  4 |  * Error Utilities - Type-safe error hierarchy for the application
  5 |  *
  6 |  * This utility module defines a structured error hierarchy for the application,
  7 |  * providing specialized error types for different failure scenarios. Using these
  8 |  * typed errors enables more precise error handling, improves debugging, and
  9 |  * provides better error messages to users.
 10 |  *
 11 |  * Responsibilities:
 12 |  * - Providing a base error class (XcodeBuildMCPError) for all application errors
 13 |  * - Defining specialized error subtypes for different error categories:
 14 |  *   - ValidationError: Parameter validation failures
 15 |  *   - SystemError: Underlying system/OS issues
 16 |  *   - ConfigurationError: Application configuration problems
 17 |  *   - SimulatorError: iOS simulator-specific failures
 18 |  *   - AxeError: axe-specific errors
 19 |  *
 20 |  * The structured hierarchy allows error consumers to handle errors with the
 21 |  * appropriate level of specificity using instanceof checks or catch clauses.
 22 |  */
 23 | 
 24 | /**
 25 |  * Custom error types for XcodeBuildMCP
 26 |  */
 27 | 
 28 | /**
 29 |  * Base error class for XcodeBuildMCP errors
 30 |  */
 31 | export class XcodeBuildMCPError extends Error {
 32 |   constructor(message: string) {
 33 |     super(message);
 34 |     this.name = 'XcodeBuildMCPError';
 35 |     // This is necessary for proper inheritance in TypeScript
 36 |     Object.setPrototypeOf(this, XcodeBuildMCPError.prototype);
 37 |   }
 38 | }
 39 | 
 40 | /**
 41 |  * Error thrown when validation of parameters fails
 42 |  */
 43 | export class ValidationError extends XcodeBuildMCPError {
 44 |   constructor(
 45 |     message: string,
 46 |     public paramName?: string,
 47 |   ) {
 48 |     super(message);
 49 |     this.name = 'ValidationError';
 50 |     Object.setPrototypeOf(this, ValidationError.prototype);
 51 |   }
 52 | }
 53 | 
 54 | /**
 55 |  * Error thrown for system-level errors (file access, permissions, etc.)
 56 |  */
 57 | export class SystemError extends XcodeBuildMCPError {
 58 |   constructor(
 59 |     message: string,
 60 |     public originalError?: Error,
 61 |   ) {
 62 |     super(message);
 63 |     this.name = 'SystemError';
 64 |     Object.setPrototypeOf(this, SystemError.prototype);
 65 |   }
 66 | }
 67 | 
 68 | /**
 69 |  * Error thrown for configuration issues
 70 |  */
 71 | export class ConfigurationError extends XcodeBuildMCPError {
 72 |   constructor(message: string) {
 73 |     super(message);
 74 |     this.name = 'ConfigurationError';
 75 |     Object.setPrototypeOf(this, ConfigurationError.prototype);
 76 |   }
 77 | }
 78 | 
 79 | /**
 80 |  * Error thrown for simulator-specific errors
 81 |  */
 82 | export class SimulatorError extends XcodeBuildMCPError {
 83 |   constructor(
 84 |     message: string,
 85 |     public simulatorName?: string,
 86 |     public simulatorId?: string,
 87 |   ) {
 88 |     super(message);
 89 |     this.name = 'SimulatorError';
 90 |     Object.setPrototypeOf(this, SimulatorError.prototype);
 91 |   }
 92 | }
 93 | 
 94 | /**
 95 |  * Error thrown for axe-specific errors
 96 |  */
 97 | export class AxeError extends XcodeBuildMCPError {
 98 |   constructor(
 99 |     message: string,
100 |     public command?: string, // The axe command that failed
101 |     public axeOutput?: string, // Output from axe
102 |     public simulatorId?: string,
103 |   ) {
104 |     super(message);
105 |     this.name = 'AxeError';
106 |     Object.setPrototypeOf(this, AxeError.prototype);
107 |   }
108 | }
109 | 
110 | // Helper to create a standard error response
111 | export function createErrorResponse(message: string, details?: string): ToolResponse {
112 |   const detailText = details ? `\nDetails: ${details}` : '';
113 |   return {
114 |     content: [
115 |       {
116 |         type: 'text',
117 |         text: `Error: ${message}${detailText}`,
118 |       },
119 |     ],
120 |     isError: true,
121 |   };
122 | }
123 | 
124 | /**
125 |  * Error class for missing dependencies
126 |  */
127 | export class DependencyError extends ConfigurationError {
128 |   constructor(
129 |     message: string,
130 |     public details?: string,
131 |   ) {
132 |     super(message);
133 |     this.name = 'DependencyError';
134 |     Object.setPrototypeOf(this, DependencyError.prototype);
135 |   }
136 | }
137 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator/install_app_sim.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import { ToolResponse } from '../../../types/common.ts';
  3 | import { log } from '../../../utils/logging/index.ts';
  4 | import { validateFileExists } from '../../../utils/validation/index.ts';
  5 | import type { CommandExecutor, FileSystemExecutor } from '../../../utils/execution/index.ts';
  6 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  7 | import {
  8 |   createSessionAwareTool,
  9 |   getSessionAwareToolSchemaShape,
 10 | } from '../../../utils/typed-tool-factory.ts';
 11 | 
 12 | const installAppSimSchemaObject = z.object({
 13 |   simulatorId: z.string().describe('UUID of the simulator to use (obtained from list_sims)'),
 14 |   appPath: z
 15 |     .string()
 16 |     .describe('Path to the .app bundle to install (full path to the .app directory)'),
 17 | });
 18 | 
 19 | type InstallAppSimParams = z.infer<typeof installAppSimSchemaObject>;
 20 | 
 21 | const publicSchemaObject = z.strictObject(
 22 |   installAppSimSchemaObject.omit({
 23 |     simulatorId: true,
 24 |   } as const).shape,
 25 | );
 26 | 
 27 | export async function install_app_simLogic(
 28 |   params: InstallAppSimParams,
 29 |   executor: CommandExecutor,
 30 |   fileSystem?: FileSystemExecutor,
 31 | ): Promise<ToolResponse> {
 32 |   const appPathExistsValidation = validateFileExists(params.appPath, fileSystem);
 33 |   if (!appPathExistsValidation.isValid) {
 34 |     return appPathExistsValidation.errorResponse!;
 35 |   }
 36 | 
 37 |   log('info', `Starting xcrun simctl install request for simulator ${params.simulatorId}`);
 38 | 
 39 |   try {
 40 |     const command = ['xcrun', 'simctl', 'install', params.simulatorId, params.appPath];
 41 |     const result = await executor(command, 'Install App in Simulator', true, undefined);
 42 | 
 43 |     if (!result.success) {
 44 |       return {
 45 |         content: [
 46 |           {
 47 |             type: 'text',
 48 |             text: `Install app in simulator operation failed: ${result.error}`,
 49 |           },
 50 |         ],
 51 |       };
 52 |     }
 53 | 
 54 |     let bundleId = '';
 55 |     try {
 56 |       const bundleIdResult = await executor(
 57 |         ['defaults', 'read', `${params.appPath}/Info`, 'CFBundleIdentifier'],
 58 |         'Extract Bundle ID',
 59 |         false,
 60 |         undefined,
 61 |       );
 62 |       if (bundleIdResult.success) {
 63 |         bundleId = bundleIdResult.output.trim();
 64 |       }
 65 |     } catch (error) {
 66 |       log('warning', `Could not extract bundle ID from app: ${error}`);
 67 |     }
 68 | 
 69 |     return {
 70 |       content: [
 71 |         {
 72 |           type: 'text',
 73 |           text: `App installed successfully in simulator ${params.simulatorId}`,
 74 |         },
 75 |         {
 76 |           type: 'text',
 77 |           text: `Next Steps:
 78 | 1. Open the Simulator app: open_sim({})
 79 | 2. Launch the app: launch_app_sim({ simulatorId: "${params.simulatorId}"${
 80 |             bundleId ? `, bundleId: "${bundleId}"` : ', bundleId: "YOUR_APP_BUNDLE_ID"'
 81 |           } })`,
 82 |         },
 83 |       ],
 84 |     };
 85 |   } catch (error) {
 86 |     const errorMessage = error instanceof Error ? error.message : String(error);
 87 |     log('error', `Error during install app in simulator operation: ${errorMessage}`);
 88 |     return {
 89 |       content: [
 90 |         {
 91 |           type: 'text',
 92 |           text: `Install app in simulator operation failed: ${errorMessage}`,
 93 |         },
 94 |       ],
 95 |     };
 96 |   }
 97 | }
 98 | 
 99 | export default {
100 |   name: 'install_app_sim',
101 |   description: 'Installs an app in an iOS simulator.',
102 |   schema: getSessionAwareToolSchemaShape({
103 |     sessionAware: publicSchemaObject,
104 |     legacy: installAppSimSchemaObject,
105 |   }),
106 |   annotations: {
107 |     title: 'Install App Simulator',
108 |     destructiveHint: true,
109 |   },
110 |   handler: createSessionAwareTool<InstallAppSimParams>({
111 |     internalSchema: installAppSimSchemaObject as unknown as z.ZodType<InstallAppSimParams, unknown>,
112 |     logicFunction: install_app_simLogic,
113 |     getExecutor: getDefaultCommandExecutor,
114 |     requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
115 |   }),
116 | };
117 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/sim_statusbar.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import { ToolResponse } from '../../../types/common.ts';
  3 | import { log } from '../../../utils/logging/index.ts';
  4 | import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  5 | import {
  6 |   createSessionAwareTool,
  7 |   getSessionAwareToolSchemaShape,
  8 | } from '../../../utils/typed-tool-factory.ts';
  9 | 
 10 | // Define schema as ZodObject
 11 | const simStatusbarSchema = z.object({
 12 |   simulatorId: z.uuid().describe('UUID of the simulator to use (obtained from list_simulators)'),
 13 |   dataNetwork: z
 14 |     .enum([
 15 |       'clear',
 16 |       'hide',
 17 |       'wifi',
 18 |       '3g',
 19 |       '4g',
 20 |       'lte',
 21 |       'lte-a',
 22 |       'lte+',
 23 |       '5g',
 24 |       '5g+',
 25 |       '5g-uwb',
 26 |       '5g-uc',
 27 |     ])
 28 |     .describe(
 29 |       'Data network type to display in status bar. Use "clear" to reset all overrides. Valid values: clear, hide, wifi, 3g, 4g, lte, lte-a, lte+, 5g, 5g+, 5g-uwb, 5g-uc.',
 30 |     ),
 31 | });
 32 | 
 33 | // Use z.infer for type safety
 34 | type SimStatusbarParams = z.infer<typeof simStatusbarSchema>;
 35 | 
 36 | export async function sim_statusbarLogic(
 37 |   params: SimStatusbarParams,
 38 |   executor: CommandExecutor,
 39 | ): Promise<ToolResponse> {
 40 |   log(
 41 |     'info',
 42 |     `Setting simulator ${params.simulatorId} status bar data network to ${params.dataNetwork}`,
 43 |   );
 44 | 
 45 |   try {
 46 |     let command: string[];
 47 |     let successMessage: string;
 48 | 
 49 |     if (params.dataNetwork === 'clear') {
 50 |       command = ['xcrun', 'simctl', 'status_bar', params.simulatorId, 'clear'];
 51 |       successMessage = `Successfully cleared status bar overrides for simulator ${params.simulatorId}`;
 52 |     } else {
 53 |       command = [
 54 |         'xcrun',
 55 |         'simctl',
 56 |         'status_bar',
 57 |         params.simulatorId,
 58 |         'override',
 59 |         '--dataNetwork',
 60 |         params.dataNetwork,
 61 |       ];
 62 |       successMessage = `Successfully set simulator ${params.simulatorId} status bar data network to ${params.dataNetwork}`;
 63 |     }
 64 | 
 65 |     const result = await executor(command, 'Set Status Bar', true, undefined);
 66 | 
 67 |     if (!result.success) {
 68 |       const failureMessage = `Failed to set status bar: ${result.error}`;
 69 |       log('error', `${failureMessage} (simulator: ${params.simulatorId})`);
 70 |       return {
 71 |         content: [{ type: 'text', text: failureMessage }],
 72 |         isError: true,
 73 |       };
 74 |     }
 75 | 
 76 |     log('info', `${successMessage} (simulator: ${params.simulatorId})`);
 77 |     return {
 78 |       content: [{ type: 'text', text: successMessage }],
 79 |     };
 80 |   } catch (error) {
 81 |     const errorMessage = error instanceof Error ? error.message : String(error);
 82 |     const failureMessage = `Failed to set status bar: ${errorMessage}`;
 83 |     log('error', `Error setting status bar for simulator ${params.simulatorId}: ${errorMessage}`);
 84 |     return {
 85 |       content: [{ type: 'text', text: failureMessage }],
 86 |       isError: true,
 87 |     };
 88 |   }
 89 | }
 90 | 
 91 | const publicSchemaObject = z.strictObject(
 92 |   simStatusbarSchema.omit({ simulatorId: true } as const).shape,
 93 | );
 94 | 
 95 | export default {
 96 |   name: 'sim_statusbar',
 97 |   description:
 98 |     'Sets the data network indicator in the iOS simulator status bar. Use "clear" to reset all overrides, or specify a network type (hide, wifi, 3g, 4g, lte, lte-a, lte+, 5g, 5g+, 5g-uwb, 5g-uc).',
 99 |   schema: getSessionAwareToolSchemaShape({
100 |     sessionAware: publicSchemaObject,
101 |     legacy: simStatusbarSchema,
102 |   }), // MCP SDK compatibility
103 |   annotations: {
104 |     title: 'Simulator Statusbar',
105 |     destructiveHint: true,
106 |   },
107 |   handler: createSessionAwareTool<SimStatusbarParams>({
108 |     internalSchema: simStatusbarSchema as unknown as z.ZodType<SimStatusbarParams, unknown>,
109 |     logicFunction: sim_statusbarLogic,
110 |     getExecutor: getDefaultCommandExecutor,
111 |     requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
112 |   }),
113 | };
114 | 
```

--------------------------------------------------------------------------------
/src/core/__tests__/resources.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, beforeEach } from 'vitest';
  2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  3 | 
  4 | import { registerResources, getAvailableResources, loadResources } from '../resources.ts';
  5 | 
  6 | describe('resources', () => {
  7 |   let mockServer: McpServer;
  8 |   let registeredResources: Array<{
  9 |     name: string;
 10 |     uri: string;
 11 |     metadata: { mimeType: string; title: string };
 12 |     handler: any;
 13 |   }>;
 14 | 
 15 |   beforeEach(() => {
 16 |     registeredResources = [];
 17 |     // Create a mock MCP server using simple object structure
 18 |     mockServer = {
 19 |       resource: (
 20 |         name: string,
 21 |         uri: string,
 22 |         metadata: { mimeType: string; title: string },
 23 |         handler: any,
 24 |       ) => {
 25 |         registeredResources.push({ name, uri, metadata, handler });
 26 |       },
 27 |     } as unknown as McpServer;
 28 |   });
 29 | 
 30 |   describe('Exports', () => {
 31 |     it('should export registerResources function', () => {
 32 |       expect(typeof registerResources).toBe('function');
 33 |     });
 34 | 
 35 |     it('should export getAvailableResources function', () => {
 36 |       expect(typeof getAvailableResources).toBe('function');
 37 |     });
 38 | 
 39 |     it('should export loadResources function', () => {
 40 |       expect(typeof loadResources).toBe('function');
 41 |     });
 42 |   });
 43 | 
 44 |   describe('loadResources', () => {
 45 |     it('should load resources from generated loaders', async () => {
 46 |       const resources = await loadResources();
 47 | 
 48 |       // Should have at least the simulators resource
 49 |       expect(resources.size).toBeGreaterThan(0);
 50 |       expect(resources.has('xcodebuildmcp://simulators')).toBe(true);
 51 |     });
 52 | 
 53 |     it('should validate resource structure', async () => {
 54 |       const resources = await loadResources();
 55 | 
 56 |       for (const [uri, resource] of resources) {
 57 |         expect(resource.uri).toBe(uri);
 58 |         expect(typeof resource.description).toBe('string');
 59 |         expect(typeof resource.mimeType).toBe('string');
 60 |         expect(typeof resource.handler).toBe('function');
 61 |       }
 62 |     });
 63 |   });
 64 | 
 65 |   describe('registerResources', () => {
 66 |     it('should register all loaded resources with the server and return true', async () => {
 67 |       const result = await registerResources(mockServer);
 68 | 
 69 |       expect(result).toBe(true);
 70 | 
 71 |       // Should have registered at least one resource
 72 |       expect(registeredResources.length).toBeGreaterThan(0);
 73 | 
 74 |       // Check simulators resource was registered
 75 |       const simulatorsResource = registeredResources.find(
 76 |         (r) => r.uri === 'xcodebuildmcp://simulators',
 77 |       );
 78 |       expect(typeof simulatorsResource?.handler).toBe('function');
 79 |       expect(simulatorsResource?.metadata.title).toBe(
 80 |         'Available iOS simulators with their UUIDs and states',
 81 |       );
 82 |       expect(simulatorsResource?.metadata.mimeType).toBe('text/plain');
 83 |       expect(simulatorsResource?.name).toBe('simulators');
 84 |     });
 85 | 
 86 |     it('should register resources with correct handlers', async () => {
 87 |       const result = await registerResources(mockServer);
 88 | 
 89 |       expect(result).toBe(true);
 90 | 
 91 |       const simulatorsResource = registeredResources.find(
 92 |         (r) => r.uri === 'xcodebuildmcp://simulators',
 93 |       );
 94 |       expect(typeof simulatorsResource?.handler).toBe('function');
 95 |     });
 96 |   });
 97 | 
 98 |   describe('getAvailableResources', () => {
 99 |     it('should return array of available resource URIs', async () => {
100 |       const resources = await getAvailableResources();
101 | 
102 |       expect(Array.isArray(resources)).toBe(true);
103 |       expect(resources.length).toBeGreaterThan(0);
104 |       expect(resources).toContain('xcodebuildmcp://simulators');
105 |     });
106 | 
107 |     it('should return unique URIs', async () => {
108 |       const resources = await getAvailableResources();
109 |       const uniqueResources = [...new Set(resources)];
110 | 
111 |       expect(resources.length).toBe(uniqueResources.length);
112 |     });
113 |   });
114 | });
115 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/set_sim_appearance.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import { ToolResponse } from '../../../types/common.ts';
  3 | import { log } from '../../../utils/logging/index.ts';
  4 | import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  5 | import {
  6 |   createSessionAwareTool,
  7 |   getSessionAwareToolSchemaShape,
  8 | } from '../../../utils/typed-tool-factory.ts';
  9 | 
 10 | // Define schema as ZodObject
 11 | const setSimAppearanceSchema = z.object({
 12 |   simulatorId: z.uuid().describe('UUID of the simulator to use (obtained from list_simulators)'),
 13 |   mode: z.enum(['dark', 'light']).describe('The appearance mode to set (either "dark" or "light")'),
 14 | });
 15 | 
 16 | // Use z.infer for type safety
 17 | type SetSimAppearanceParams = z.infer<typeof setSimAppearanceSchema>;
 18 | 
 19 | // Helper function to execute simctl commands and handle responses
 20 | async function executeSimctlCommandAndRespond(
 21 |   params: SetSimAppearanceParams,
 22 |   simctlSubCommand: string[],
 23 |   operationDescriptionForXcodeCommand: string,
 24 |   successMessage: string,
 25 |   failureMessagePrefix: string,
 26 |   operationLogContext: string,
 27 |   extraValidation?: () => ToolResponse | undefined,
 28 |   executor: CommandExecutor = getDefaultCommandExecutor(),
 29 | ): Promise<ToolResponse> {
 30 |   if (extraValidation) {
 31 |     const validationResult = extraValidation();
 32 |     if (validationResult) {
 33 |       return validationResult;
 34 |     }
 35 |   }
 36 | 
 37 |   try {
 38 |     const command = ['xcrun', 'simctl', ...simctlSubCommand];
 39 |     const result = await executor(command, operationDescriptionForXcodeCommand, true, undefined);
 40 | 
 41 |     if (!result.success) {
 42 |       const fullFailureMessage = `${failureMessagePrefix}: ${result.error}`;
 43 |       log(
 44 |         'error',
 45 |         `${fullFailureMessage} (operation: ${operationLogContext}, simulator: ${params.simulatorId})`,
 46 |       );
 47 |       return {
 48 |         content: [{ type: 'text', text: fullFailureMessage }],
 49 |       };
 50 |     }
 51 | 
 52 |     log(
 53 |       'info',
 54 |       `${successMessage} (operation: ${operationLogContext}, simulator: ${params.simulatorId})`,
 55 |     );
 56 |     return {
 57 |       content: [{ type: 'text', text: successMessage }],
 58 |     };
 59 |   } catch (error) {
 60 |     const errorMessage = error instanceof Error ? error.message : String(error);
 61 |     const fullFailureMessage = `${failureMessagePrefix}: ${errorMessage}`;
 62 |     log(
 63 |       'error',
 64 |       `Error during ${operationLogContext} for simulator ${params.simulatorId}: ${errorMessage}`,
 65 |     );
 66 |     return {
 67 |       content: [{ type: 'text', text: fullFailureMessage }],
 68 |     };
 69 |   }
 70 | }
 71 | 
 72 | export async function set_sim_appearanceLogic(
 73 |   params: SetSimAppearanceParams,
 74 |   executor: CommandExecutor,
 75 | ): Promise<ToolResponse> {
 76 |   log('info', `Setting simulator ${params.simulatorId} appearance to ${params.mode} mode`);
 77 | 
 78 |   return executeSimctlCommandAndRespond(
 79 |     params,
 80 |     ['ui', params.simulatorId, 'appearance', params.mode],
 81 |     'Set Simulator Appearance',
 82 |     `Successfully set simulator ${params.simulatorId} appearance to ${params.mode} mode`,
 83 |     'Failed to set simulator appearance',
 84 |     'set simulator appearance',
 85 |     undefined,
 86 |     executor,
 87 |   );
 88 | }
 89 | 
 90 | const publicSchemaObject = z.strictObject(
 91 |   setSimAppearanceSchema.omit({ simulatorId: true } as const).shape,
 92 | );
 93 | 
 94 | export default {
 95 |   name: 'set_sim_appearance',
 96 |   description: 'Sets the appearance mode (dark/light) of an iOS simulator.',
 97 |   schema: getSessionAwareToolSchemaShape({
 98 |     sessionAware: publicSchemaObject,
 99 |     legacy: setSimAppearanceSchema,
100 |   }),
101 |   annotations: {
102 |     title: 'Set Simulator Appearance',
103 |     destructiveHint: true,
104 |   },
105 |   handler: createSessionAwareTool<SetSimAppearanceParams>({
106 |     internalSchema: setSimAppearanceSchema as unknown as z.ZodType<SetSimAppearanceParams, unknown>,
107 |     logicFunction: set_sim_appearanceLogic,
108 |     getExecutor: getDefaultCommandExecutor,
109 |     requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
110 |   }),
111 | };
112 | 
```

--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/Sources/CalculatorAppFeature/CalculatorButton.swift:
--------------------------------------------------------------------------------

```swift
  1 | import SwiftUI
  2 | 
  3 | // MARK: - Calculator Button Component
  4 | struct CalculatorButton: View {
  5 |     let title: String
  6 |     let buttonType: CalculatorButtonType
  7 |     let isWideButton: Bool
  8 |     let action: () -> Void
  9 |     
 10 |     @State private var isPressed = false
 11 |     
 12 |     var body: some View {
 13 |         if buttonType == .hidden {
 14 |             // Empty space for layout
 15 |             Color.clear
 16 |                 .frame(height: 80)
 17 |         } else {
 18 |             Button(action: {
 19 |                 withAnimation(.easeInOut(duration: 0.1)) {
 20 |                     isPressed = true
 21 |                 }
 22 |                 action()
 23 |                 
 24 |                 Task {
 25 |                     try await Task.sleep(for: .seconds(0.1))
 26 |                     await MainActor.run {
 27 |                         withAnimation(.easeInOut(duration: 0.1)) {
 28 |                             isPressed = false
 29 |                         }
 30 |                     }
 31 |                 }
 32 |             }) {
 33 |                 ZStack {
 34 |                     // Frosted glass background
 35 |                     RoundedRectangle(cornerRadius: 20)
 36 |                         .fill(.ultraThinMaterial)
 37 |                         .overlay(
 38 |                             RoundedRectangle(cornerRadius: 20)
 39 |                                 .stroke(buttonType.borderColor, lineWidth: 1)
 40 |                         )
 41 |                         .overlay(
 42 |                             // Subtle inner glow
 43 |                             RoundedRectangle(cornerRadius: 20)
 44 |                                 .fill(
 45 |                                     RadialGradient(
 46 |                                         colors: [buttonType.glowColor.opacity(0.3), Color.clear],
 47 |                                         center: .topLeading,
 48 |                                         startRadius: 0,
 49 |                                         endRadius: 50
 50 |                                     )
 51 |                                 )
 52 |                         )
 53 |                         .scaleEffect(isPressed ? 0.95 : 1.0)
 54 |                         .shadow(color: buttonType.shadowColor.opacity(0.3), radius: isPressed ? 2 : 8, x: 0, y: isPressed ? 1 : 4)
 55 |                     
 56 |                     // Button text
 57 |                     Text(title)
 58 |                         .font(.system(size: 32, weight: .medium, design: .rounded))
 59 |                         .foregroundColor(buttonType.textColor)
 60 |                         .scaleEffect(isPressed ? 0.9 : 1.0)
 61 |                 }
 62 |             }
 63 |             .frame(height: 80)
 64 |             .gridCellColumns(isWideButton ? 2 : 1)
 65 |             .buttonStyle(PlainButtonStyle())
 66 |         }
 67 |     }
 68 | }
 69 | 
 70 | // MARK: - Button Type Configuration
 71 | enum CalculatorButtonType {
 72 |     case number, operation, function, hidden
 73 |     
 74 |     var textColor: Color {
 75 |         switch self {
 76 |         case .number:
 77 |             return .white
 78 |         case .operation:
 79 |             return .white
 80 |         case .function:
 81 |             return .white
 82 |         case .hidden:
 83 |             return .clear
 84 |         }
 85 |     }
 86 |     
 87 |     var borderColor: Color {
 88 |         switch self {
 89 |         case .number:
 90 |             return .white.opacity(0.3)
 91 |         case .operation:
 92 |             return .orange.opacity(0.6)
 93 |         case .function:
 94 |             return .gray.opacity(0.5)
 95 |         case .hidden:
 96 |             return .clear
 97 |         }
 98 |     }
 99 |     
100 |     var glowColor: Color {
101 |         switch self {
102 |         case .number:
103 |             return .blue
104 |         case .operation:
105 |             return .orange
106 |         case .function:
107 |             return .gray
108 |         case .hidden:
109 |             return .clear
110 |         }
111 |     }
112 |     
113 |     var shadowColor: Color {
114 |         switch self {
115 |         case .number:
116 |             return .blue
117 |         case .operation:
118 |             return .orange
119 |         case .function:
120 |             return .gray
121 |         case .hidden:
122 |             return .clear
123 |         }
124 |     }
125 | }
```

--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/get_mac_bundle_id.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Project Discovery Plugin: Get macOS Bundle ID
  3 |  *
  4 |  * Extracts the bundle identifier from a macOS app bundle (.app).
  5 |  */
  6 | 
  7 | import * as z from 'zod';
  8 | import { log } from '../../../utils/logging/index.ts';
  9 | import { ToolResponse } from '../../../types/common.ts';
 10 | import {
 11 |   CommandExecutor,
 12 |   getDefaultFileSystemExecutor,
 13 |   getDefaultCommandExecutor,
 14 | } from '../../../utils/command.ts';
 15 | import { FileSystemExecutor } from '../../../utils/FileSystemExecutor.ts';
 16 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
 17 | 
 18 | /**
 19 |  * Sync wrapper for CommandExecutor to handle synchronous commands
 20 |  */
 21 | async function executeSyncCommand(command: string, executor: CommandExecutor): Promise<string> {
 22 |   const result = await executor(['/bin/sh', '-c', command], 'macOS Bundle ID Extraction');
 23 |   if (!result.success) {
 24 |     throw new Error(result.error ?? 'Command failed');
 25 |   }
 26 |   return result.output || '';
 27 | }
 28 | 
 29 | // Define schema as ZodObject
 30 | const getMacBundleIdSchema = z.object({
 31 |   appPath: z
 32 |     .string()
 33 |     .describe(
 34 |       'Path to the macOS .app bundle to extract bundle ID from (full path to the .app directory)',
 35 |     ),
 36 | });
 37 | 
 38 | // Use z.infer for type safety
 39 | type GetMacBundleIdParams = z.infer<typeof getMacBundleIdSchema>;
 40 | 
 41 | /**
 42 |  * Business logic for extracting macOS bundle ID
 43 |  */
 44 | export async function get_mac_bundle_idLogic(
 45 |   params: GetMacBundleIdParams,
 46 |   executor: CommandExecutor,
 47 |   fileSystemExecutor: FileSystemExecutor,
 48 | ): Promise<ToolResponse> {
 49 |   const appPath = params.appPath;
 50 | 
 51 |   if (!fileSystemExecutor.existsSync(appPath)) {
 52 |     return {
 53 |       content: [
 54 |         {
 55 |           type: 'text',
 56 |           text: `File not found: '${appPath}'. Please check the path and try again.`,
 57 |         },
 58 |       ],
 59 |       isError: true,
 60 |     };
 61 |   }
 62 | 
 63 |   log('info', `Starting bundle ID extraction for macOS app: ${appPath}`);
 64 | 
 65 |   try {
 66 |     let bundleId;
 67 | 
 68 |     try {
 69 |       bundleId = await executeSyncCommand(
 70 |         `defaults read "${appPath}/Contents/Info" CFBundleIdentifier`,
 71 |         executor,
 72 |       );
 73 |     } catch {
 74 |       try {
 75 |         bundleId = await executeSyncCommand(
 76 |           `/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${appPath}/Contents/Info.plist"`,
 77 |           executor,
 78 |         );
 79 |       } catch (innerError) {
 80 |         throw new Error(
 81 |           `Could not extract bundle ID from Info.plist: ${innerError instanceof Error ? innerError.message : String(innerError)}`,
 82 |         );
 83 |       }
 84 |     }
 85 | 
 86 |     log('info', `Extracted macOS bundle ID: ${bundleId}`);
 87 | 
 88 |     return {
 89 |       content: [
 90 |         {
 91 |           type: 'text',
 92 |           text: `✅ Bundle ID: ${bundleId}`,
 93 |         },
 94 |         {
 95 |           type: 'text',
 96 |           text: `Next Steps:
 97 | - Launch: launch_mac_app({ appPath: "${appPath}" })
 98 | - Build again: build_macos({ scheme: "SCHEME_NAME" })`,
 99 |         },
100 |       ],
101 |       isError: false,
102 |     };
103 |   } catch (error) {
104 |     const errorMessage = error instanceof Error ? error.message : String(error);
105 |     log('error', `Error extracting macOS bundle ID: ${errorMessage}`);
106 | 
107 |     return {
108 |       content: [
109 |         {
110 |           type: 'text',
111 |           text: `Error extracting macOS bundle ID: ${errorMessage}`,
112 |         },
113 |         {
114 |           type: 'text',
115 |           text: `Make sure the path points to a valid macOS app bundle (.app directory).`,
116 |         },
117 |       ],
118 |       isError: true,
119 |     };
120 |   }
121 | }
122 | 
123 | export default {
124 |   name: 'get_mac_bundle_id',
125 |   description:
126 |     "Extracts the bundle identifier from a macOS app bundle (.app). IMPORTANT: You MUST provide the appPath parameter. Example: get_mac_bundle_id({ appPath: '/path/to/your/app.app' }) Note: In some environments, this tool may be prefixed as mcp0_get_macos_bundle_id.",
127 |   schema: getMacBundleIdSchema.shape, // MCP SDK compatibility
128 |   annotations: {
129 |     title: 'Get Mac Bundle ID',
130 |     readOnlyHint: true,
131 |   },
132 |   handler: createTypedTool(
133 |     getMacBundleIdSchema,
134 |     (params: GetMacBundleIdParams) =>
135 |       get_mac_bundle_idLogic(params, getDefaultCommandExecutor(), getDefaultFileSystemExecutor()),
136 |     getDefaultCommandExecutor,
137 |   ),
138 | };
139 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/get_app_bundle_id.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Project Discovery Plugin: Get App Bundle ID
  3 |  *
  4 |  * Extracts the bundle identifier from an app bundle (.app) for any Apple platform
  5 |  * (iOS, iPadOS, watchOS, tvOS, visionOS).
  6 |  */
  7 | 
  8 | import * as z from 'zod';
  9 | import { log } from '../../../utils/logging/index.ts';
 10 | import { ToolResponse } from '../../../types/common.ts';
 11 | import {
 12 |   CommandExecutor,
 13 |   getDefaultFileSystemExecutor,
 14 |   getDefaultCommandExecutor,
 15 | } from '../../../utils/command.ts';
 16 | import { FileSystemExecutor } from '../../../utils/FileSystemExecutor.ts';
 17 | import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
 18 | 
 19 | // Define schema as ZodObject
 20 | const getAppBundleIdSchema = z.object({
 21 |   appPath: z
 22 |     .string()
 23 |     .describe(
 24 |       'Path to the .app bundle to extract bundle ID from (full path to the .app directory)',
 25 |     ),
 26 | });
 27 | 
 28 | // Use z.infer for type safety
 29 | type GetAppBundleIdParams = z.infer<typeof getAppBundleIdSchema>;
 30 | 
 31 | /**
 32 |  * Sync wrapper for CommandExecutor to handle synchronous commands
 33 |  */
 34 | async function executeSyncCommand(command: string, executor: CommandExecutor): Promise<string> {
 35 |   const result = await executor(['/bin/sh', '-c', command], 'Bundle ID Extraction');
 36 |   if (!result.success) {
 37 |     throw new Error(result.error ?? 'Command failed');
 38 |   }
 39 |   return result.output || '';
 40 | }
 41 | 
 42 | /**
 43 |  * Business logic for extracting bundle ID from app.
 44 |  * Separated for testing and reusability.
 45 |  */
 46 | export async function get_app_bundle_idLogic(
 47 |   params: GetAppBundleIdParams,
 48 |   executor: CommandExecutor,
 49 |   fileSystemExecutor: FileSystemExecutor,
 50 | ): Promise<ToolResponse> {
 51 |   // Zod validation is handled by createTypedTool, so params.appPath is guaranteed to be a string
 52 |   const appPath = params.appPath;
 53 | 
 54 |   if (!fileSystemExecutor.existsSync(appPath)) {
 55 |     return {
 56 |       content: [
 57 |         {
 58 |           type: 'text',
 59 |           text: `File not found: '${appPath}'. Please check the path and try again.`,
 60 |         },
 61 |       ],
 62 |       isError: true,
 63 |     };
 64 |   }
 65 | 
 66 |   log('info', `Starting bundle ID extraction for app: ${appPath}`);
 67 | 
 68 |   try {
 69 |     let bundleId;
 70 | 
 71 |     try {
 72 |       bundleId = await executeSyncCommand(
 73 |         `defaults read "${appPath}/Info" CFBundleIdentifier`,
 74 |         executor,
 75 |       );
 76 |     } catch {
 77 |       try {
 78 |         bundleId = await executeSyncCommand(
 79 |           `/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${appPath}/Info.plist"`,
 80 |           executor,
 81 |         );
 82 |       } catch (innerError) {
 83 |         throw new Error(
 84 |           `Could not extract bundle ID from Info.plist: ${innerError instanceof Error ? innerError.message : String(innerError)}`,
 85 |         );
 86 |       }
 87 |     }
 88 | 
 89 |     log('info', `Extracted app bundle ID: ${bundleId}`);
 90 | 
 91 |     return {
 92 |       content: [
 93 |         {
 94 |           type: 'text',
 95 |           text: `✅ Bundle ID: ${bundleId}`,
 96 |         },
 97 |         {
 98 |           type: 'text',
 99 |           text: `Next Steps:
100 | - Simulator: install_app_sim + launch_app_sim
101 | - Device: install_app_device + launch_app_device`,
102 |         },
103 |       ],
104 |       isError: false,
105 |     };
106 |   } catch (error) {
107 |     const errorMessage = error instanceof Error ? error.message : String(error);
108 |     log('error', `Error extracting app bundle ID: ${errorMessage}`);
109 | 
110 |     return {
111 |       content: [
112 |         {
113 |           type: 'text',
114 |           text: `Error extracting app bundle ID: ${errorMessage}`,
115 |         },
116 |         {
117 |           type: 'text',
118 |           text: `Make sure the path points to a valid app bundle (.app directory).`,
119 |         },
120 |       ],
121 |       isError: true,
122 |     };
123 |   }
124 | }
125 | 
126 | export default {
127 |   name: 'get_app_bundle_id',
128 |   description:
129 |     "Extracts the bundle identifier from an app bundle (.app) for any Apple platform (iOS, iPadOS, watchOS, tvOS, visionOS). IMPORTANT: You MUST provide the appPath parameter. Example: get_app_bundle_id({ appPath: '/path/to/your/app.app' })",
130 |   schema: getAppBundleIdSchema.shape, // MCP SDK compatibility
131 |   annotations: {
132 |     title: 'Get App Bundle ID',
133 |     readOnlyHint: true,
134 |   },
135 |   handler: createTypedTool(
136 |     getAppBundleIdSchema,
137 |     (params: GetAppBundleIdParams) =>
138 |       get_app_bundle_idLogic(params, getDefaultCommandExecutor(), getDefaultFileSystemExecutor()),
139 |     getDefaultCommandExecutor,
140 |   ),
141 | };
142 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/macos/build_macos.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * macOS Shared Plugin: Build macOS (Unified)
  3 |  *
  4 |  * Builds a macOS app using xcodebuild from a project or workspace.
  5 |  * Accepts mutually exclusive `projectPath` or `workspacePath`.
  6 |  */
  7 | 
  8 | import * as z from 'zod';
  9 | import { log } from '../../../utils/logging/index.ts';
 10 | import { executeXcodeBuildCommand } from '../../../utils/build/index.ts';
 11 | import { ToolResponse, XcodePlatform } from '../../../types/common.ts';
 12 | import type { CommandExecutor } from '../../../utils/execution/index.ts';
 13 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
 14 | import {
 15 |   createSessionAwareTool,
 16 |   getSessionAwareToolSchemaShape,
 17 | } from '../../../utils/typed-tool-factory.ts';
 18 | import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
 19 | 
 20 | // Types for dependency injection
 21 | export interface BuildUtilsDependencies {
 22 |   executeXcodeBuildCommand: typeof executeXcodeBuildCommand;
 23 | }
 24 | 
 25 | // Default implementations
 26 | const defaultBuildUtilsDependencies: BuildUtilsDependencies = {
 27 |   executeXcodeBuildCommand,
 28 | };
 29 | 
 30 | // Unified schema: XOR between projectPath and workspacePath
 31 | const baseSchemaObject = z.object({
 32 |   projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
 33 |   workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
 34 |   scheme: z.string().describe('The scheme to use'),
 35 |   configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
 36 |   derivedDataPath: z
 37 |     .string()
 38 |     .optional()
 39 |     .describe('Path where build products and other derived data will go'),
 40 |   arch: z
 41 |     .enum(['arm64', 'x86_64'])
 42 |     .optional()
 43 |     .describe('Architecture to build for (arm64 or x86_64). For macOS only.'),
 44 |   extraArgs: z.array(z.string()).optional().describe('Additional xcodebuild arguments'),
 45 |   preferXcodebuild: z
 46 |     .boolean()
 47 |     .optional()
 48 |     .describe('If true, prefers xcodebuild over the experimental incremental build system'),
 49 | });
 50 | 
 51 | const publicSchemaObject = baseSchemaObject.omit({
 52 |   projectPath: true,
 53 |   workspacePath: true,
 54 |   scheme: true,
 55 |   configuration: true,
 56 |   arch: true,
 57 | } as const);
 58 | 
 59 | const buildMacOSSchema = z.preprocess(
 60 |   nullifyEmptyStrings,
 61 |   baseSchemaObject
 62 |     .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
 63 |       message: 'Either projectPath or workspacePath is required.',
 64 |     })
 65 |     .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
 66 |       message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
 67 |     }),
 68 | );
 69 | 
 70 | export type BuildMacOSParams = z.infer<typeof buildMacOSSchema>;
 71 | 
 72 | /**
 73 |  * Business logic for building macOS apps from project or workspace with dependency injection.
 74 |  * Exported for direct testing and reuse.
 75 |  */
 76 | export async function buildMacOSLogic(
 77 |   params: BuildMacOSParams,
 78 |   executor: CommandExecutor,
 79 |   buildUtilsDeps: BuildUtilsDependencies = defaultBuildUtilsDependencies,
 80 | ): Promise<ToolResponse> {
 81 |   log('info', `Starting macOS build for scheme ${params.scheme} (internal)`);
 82 | 
 83 |   const processedParams = {
 84 |     ...params,
 85 |     configuration: params.configuration ?? 'Debug',
 86 |     preferXcodebuild: params.preferXcodebuild ?? false,
 87 |   };
 88 | 
 89 |   return buildUtilsDeps.executeXcodeBuildCommand(
 90 |     processedParams,
 91 |     {
 92 |       platform: XcodePlatform.macOS,
 93 |       arch: params.arch,
 94 |       logPrefix: 'macOS Build',
 95 |     },
 96 |     processedParams.preferXcodebuild ?? false,
 97 |     'build',
 98 |     executor,
 99 |   );
100 | }
101 | 
102 | export default {
103 |   name: 'build_macos',
104 |   description: 'Builds a macOS app.',
105 |   schema: getSessionAwareToolSchemaShape({
106 |     sessionAware: publicSchemaObject,
107 |     legacy: baseSchemaObject,
108 |   }),
109 |   annotations: {
110 |     title: 'Build macOS',
111 |     destructiveHint: true,
112 |   },
113 |   handler: createSessionAwareTool<BuildMacOSParams>({
114 |     internalSchema: buildMacOSSchema as unknown as z.ZodType<BuildMacOSParams, unknown>,
115 |     logicFunction: buildMacOSLogic,
116 |     getExecutor: getDefaultCommandExecutor,
117 |     requirements: [
118 |       { allOf: ['scheme'], message: 'scheme is required' },
119 |       { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' },
120 |     ],
121 |     exclusivePairs: [['projectPath', 'workspacePath']],
122 |   }),
123 | };
124 | 
```

--------------------------------------------------------------------------------
/src/utils/sentry.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Sentry instrumentation for XcodeBuildMCP
  3 |  *
  4 |  * This file initializes Sentry when explicitly called to avoid side effects
  5 |  * during module import (needed for Smithery's module-based entry).
  6 |  */
  7 | 
  8 | import * as Sentry from '@sentry/node';
  9 | import { execSync } from 'child_process';
 10 | import { version } from '../version.ts';
 11 | 
 12 | // Inlined system info functions to avoid circular dependencies
 13 | function getXcodeInfo(): { version: string; path: string; selectedXcode: string; error?: string } {
 14 |   try {
 15 |     const xcodebuildOutput = execSync('xcodebuild -version', { encoding: 'utf8' }).trim();
 16 |     const version = xcodebuildOutput.split('\n').slice(0, 2).join(' - ');
 17 |     const path = execSync('xcode-select -p', { encoding: 'utf8' }).trim();
 18 |     const selectedXcode = execSync('xcrun --find xcodebuild', { encoding: 'utf8' }).trim();
 19 | 
 20 |     return { version, path, selectedXcode };
 21 |   } catch (error) {
 22 |     return {
 23 |       version: 'Not available',
 24 |       path: 'Not available',
 25 |       selectedXcode: 'Not available',
 26 |       error: error instanceof Error ? error.message : String(error),
 27 |     };
 28 |   }
 29 | }
 30 | 
 31 | function getEnvironmentVariables(): Record<string, string> {
 32 |   const relevantVars = [
 33 |     'INCREMENTAL_BUILDS_ENABLED',
 34 |     'PATH',
 35 |     'DEVELOPER_DIR',
 36 |     'HOME',
 37 |     'USER',
 38 |     'TMPDIR',
 39 |     'NODE_ENV',
 40 |     'SENTRY_DISABLED',
 41 |   ];
 42 | 
 43 |   const envVars: Record<string, string> = {};
 44 |   relevantVars.forEach((varName) => {
 45 |     envVars[varName] = process.env[varName] ?? '';
 46 |   });
 47 | 
 48 |   Object.keys(process.env).forEach((key) => {
 49 |     if (key.startsWith('XCODEBUILDMCP_')) {
 50 |       envVars[key] = process.env[key] ?? '';
 51 |     }
 52 |   });
 53 | 
 54 |   return envVars;
 55 | }
 56 | 
 57 | function checkBinaryAvailability(binary: string): { available: boolean; version?: string } {
 58 |   try {
 59 |     execSync(`which ${binary}`, { stdio: 'ignore' });
 60 |   } catch {
 61 |     return { available: false };
 62 |   }
 63 | 
 64 |   let version: string | undefined;
 65 |   const versionCommands: Record<string, string> = {
 66 |     axe: 'axe --version',
 67 |     mise: 'mise --version',
 68 |   };
 69 | 
 70 |   if (binary in versionCommands) {
 71 |     try {
 72 |       version = execSync(versionCommands[binary], {
 73 |         encoding: 'utf8',
 74 |         stdio: ['ignore', 'pipe', 'ignore'],
 75 |       }).trim();
 76 |     } catch {
 77 |       // Version command failed, but binary exists
 78 |     }
 79 |   }
 80 | 
 81 |   return { available: true, version };
 82 | }
 83 | 
 84 | let initialized = false;
 85 | 
 86 | function isSentryDisabled(): boolean {
 87 |   return (
 88 |     process.env.SENTRY_DISABLED === 'true' || process.env.XCODEBUILDMCP_SENTRY_DISABLED === 'true'
 89 |   );
 90 | }
 91 | 
 92 | function isTestEnv(): boolean {
 93 |   return process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
 94 | }
 95 | 
 96 | export function initSentry(): void {
 97 |   if (initialized || isSentryDisabled() || isTestEnv()) {
 98 |     return;
 99 |   }
100 | 
101 |   initialized = true;
102 | 
103 |   Sentry.init({
104 |     dsn:
105 |       process.env.SENTRY_DSN ??
106 |       'https://798607831167c7b9fe2f2912f5d3c665@o4509258288332800.ingest.de.sentry.io/4509258293837904',
107 | 
108 |     // Setting this option to true will send default PII data to Sentry
109 |     // For example, automatic IP address collection on events
110 |     sendDefaultPii: true,
111 | 
112 |     // Set release version to match application version
113 |     release: `xcodebuildmcp@${version}`,
114 | 
115 |     // Always report under production environment
116 |     environment: 'production',
117 | 
118 |     // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring
119 |     // We recommend adjusting this value in production
120 |     tracesSampleRate: 1.0,
121 |   });
122 | 
123 |   const axeAvailable = checkBinaryAvailability('axe');
124 |   const miseAvailable = checkBinaryAvailability('mise');
125 |   const envVars = getEnvironmentVariables();
126 |   const xcodeInfo = getXcodeInfo();
127 | 
128 |   // Add additional context that might be helpful for debugging
129 |   const tags: Record<string, string> = {
130 |     nodeVersion: process.version,
131 |     platform: process.platform,
132 |     arch: process.arch,
133 |     axeAvailable: axeAvailable.available ? 'true' : 'false',
134 |     axeVersion: axeAvailable.version ?? 'Unknown',
135 |     miseAvailable: miseAvailable.available ? 'true' : 'false',
136 |     miseVersion: miseAvailable.version ?? 'Unknown',
137 |     ...Object.fromEntries(Object.entries(envVars).map(([k, v]) => [`env_${k}`, v ?? ''])),
138 |     xcodeVersion: xcodeInfo.version ?? 'Unknown',
139 |     xcodePath: xcodeInfo.path ?? 'Unknown',
140 |   };
141 | 
142 |   Sentry.setTags(tags);
143 | }
144 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/__tests__/set_sim_appearance.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from 'vitest';
  2 | import * as z from 'zod';
  3 | import setSimAppearancePlugin, { set_sim_appearanceLogic } from '../set_sim_appearance.ts';
  4 | import {
  5 |   createMockCommandResponse,
  6 |   createMockExecutor,
  7 | } from '../../../../test-utils/mock-executors.ts';
  8 | 
  9 | describe('set_sim_appearance plugin', () => {
 10 |   describe('Export Field Validation (Literal)', () => {
 11 |     it('should have correct name field', () => {
 12 |       expect(setSimAppearancePlugin.name).toBe('set_sim_appearance');
 13 |     });
 14 | 
 15 |     it('should have correct description field', () => {
 16 |       expect(setSimAppearancePlugin.description).toBe(
 17 |         'Sets the appearance mode (dark/light) of an iOS simulator.',
 18 |       );
 19 |     });
 20 | 
 21 |     it('should have handler function', () => {
 22 |       expect(typeof setSimAppearancePlugin.handler).toBe('function');
 23 |     });
 24 | 
 25 |     it('should expose public schema without simulatorId field', () => {
 26 |       const schema = z.object(setSimAppearancePlugin.schema);
 27 | 
 28 |       expect(schema.safeParse({ mode: 'dark' }).success).toBe(true);
 29 |       expect(schema.safeParse({ mode: 'light' }).success).toBe(true);
 30 |       expect(schema.safeParse({ mode: 'invalid' }).success).toBe(false);
 31 | 
 32 |       const withSimId = schema.safeParse({ simulatorId: 'abc123', mode: 'dark' });
 33 |       expect(withSimId.success).toBe(true);
 34 |       expect('simulatorId' in (withSimId.data as any)).toBe(false);
 35 |     });
 36 |   });
 37 | 
 38 |   describe('Handler Behavior (Complete Literal Returns)', () => {
 39 |     it('should handle successful appearance change', async () => {
 40 |       const mockExecutor = createMockExecutor({
 41 |         success: true,
 42 |         output: '',
 43 |         error: '',
 44 |       });
 45 | 
 46 |       const result = await set_sim_appearanceLogic(
 47 |         {
 48 |           simulatorId: 'test-uuid-123',
 49 |           mode: 'dark',
 50 |         },
 51 |         mockExecutor,
 52 |       );
 53 | 
 54 |       expect(result).toEqual({
 55 |         content: [
 56 |           {
 57 |             type: 'text',
 58 |             text: 'Successfully set simulator test-uuid-123 appearance to dark mode',
 59 |           },
 60 |         ],
 61 |       });
 62 |     });
 63 | 
 64 |     it('should handle appearance change failure', async () => {
 65 |       const mockExecutor = createMockExecutor({
 66 |         success: false,
 67 |         error: 'Invalid device: invalid-uuid',
 68 |       });
 69 | 
 70 |       const result = await set_sim_appearanceLogic(
 71 |         {
 72 |           simulatorId: 'invalid-uuid',
 73 |           mode: 'light',
 74 |         },
 75 |         mockExecutor,
 76 |       );
 77 | 
 78 |       expect(result).toEqual({
 79 |         content: [
 80 |           {
 81 |             type: 'text',
 82 |             text: 'Failed to set simulator appearance: Invalid device: invalid-uuid',
 83 |           },
 84 |         ],
 85 |       });
 86 |     });
 87 | 
 88 |     it('should surface session default requirement when simulatorId is missing', async () => {
 89 |       const result = await setSimAppearancePlugin.handler({ mode: 'dark' });
 90 | 
 91 |       const message = result.content?.[0]?.text ?? '';
 92 |       expect(message).toContain('Error: Missing required session defaults');
 93 |       expect(message).toContain('simulatorId is required');
 94 |       expect(result.isError).toBe(true);
 95 |     });
 96 | 
 97 |     it('should handle exception during execution', async () => {
 98 |       const mockExecutor = createMockExecutor(new Error('Network error'));
 99 | 
100 |       const result = await set_sim_appearanceLogic(
101 |         {
102 |           simulatorId: 'test-uuid-123',
103 |           mode: 'dark',
104 |         },
105 |         mockExecutor,
106 |       );
107 | 
108 |       expect(result).toEqual({
109 |         content: [
110 |           {
111 |             type: 'text',
112 |             text: 'Failed to set simulator appearance: Network error',
113 |           },
114 |         ],
115 |       });
116 |     });
117 | 
118 |     it('should call correct command', async () => {
119 |       const commandCalls: any[] = [];
120 |       const mockExecutor = (...args: any[]) => {
121 |         commandCalls.push(args);
122 |         return Promise.resolve(
123 |           createMockCommandResponse({
124 |             success: true,
125 |             output: '',
126 |             error: '',
127 |           }),
128 |         );
129 |       };
130 | 
131 |       await set_sim_appearanceLogic(
132 |         {
133 |           simulatorId: 'test-uuid-123',
134 |           mode: 'dark',
135 |         },
136 |         mockExecutor,
137 |       );
138 | 
139 |       expect(commandCalls).toEqual([
140 |         [
141 |           ['xcrun', 'simctl', 'ui', 'test-uuid-123', 'appearance', 'dark'],
142 |           'Set Simulator Appearance',
143 |           true,
144 |           undefined,
145 |         ],
146 |       ]);
147 |     });
148 |   });
149 | });
150 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator-management/set_sim_location.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import { ToolResponse } from '../../../types/common.ts';
  3 | import { log } from '../../../utils/logging/index.ts';
  4 | import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  5 | import {
  6 |   createSessionAwareTool,
  7 |   getSessionAwareToolSchemaShape,
  8 | } from '../../../utils/typed-tool-factory.ts';
  9 | 
 10 | // Define schema as ZodObject
 11 | const setSimulatorLocationSchema = z.object({
 12 |   simulatorId: z.uuid().describe('UUID of the simulator to use (obtained from list_simulators)'),
 13 |   latitude: z.number().describe('The latitude for the custom location.'),
 14 |   longitude: z.number().describe('The longitude for the custom location.'),
 15 | });
 16 | 
 17 | // Use z.infer for type safety
 18 | type SetSimulatorLocationParams = z.infer<typeof setSimulatorLocationSchema>;
 19 | 
 20 | // Helper function to execute simctl commands and handle responses
 21 | async function executeSimctlCommandAndRespond(
 22 |   params: SetSimulatorLocationParams,
 23 |   simctlSubCommand: string[],
 24 |   operationDescriptionForXcodeCommand: string,
 25 |   successMessage: string,
 26 |   failureMessagePrefix: string,
 27 |   operationLogContext: string,
 28 |   executor: CommandExecutor = getDefaultCommandExecutor(),
 29 |   extraValidation?: () => ToolResponse | null,
 30 | ): Promise<ToolResponse> {
 31 |   if (extraValidation) {
 32 |     const validationResult = extraValidation();
 33 |     if (validationResult) {
 34 |       return validationResult;
 35 |     }
 36 |   }
 37 | 
 38 |   try {
 39 |     const command = ['xcrun', 'simctl', ...simctlSubCommand];
 40 |     const result = await executor(command, operationDescriptionForXcodeCommand, true, {});
 41 | 
 42 |     if (!result.success) {
 43 |       const fullFailureMessage = `${failureMessagePrefix}: ${result.error}`;
 44 |       log(
 45 |         'error',
 46 |         `${fullFailureMessage} (operation: ${operationLogContext}, simulator: ${params.simulatorId})`,
 47 |       );
 48 |       return {
 49 |         content: [{ type: 'text', text: fullFailureMessage }],
 50 |       };
 51 |     }
 52 | 
 53 |     log(
 54 |       'info',
 55 |       `${successMessage} (operation: ${operationLogContext}, simulator: ${params.simulatorId})`,
 56 |     );
 57 |     return {
 58 |       content: [{ type: 'text', text: successMessage }],
 59 |     };
 60 |   } catch (error) {
 61 |     const errorMessage = error instanceof Error ? error.message : String(error);
 62 |     const fullFailureMessage = `${failureMessagePrefix}: ${errorMessage}`;
 63 |     log(
 64 |       'error',
 65 |       `Error during ${operationLogContext} for simulator ${params.simulatorId}: ${errorMessage}`,
 66 |     );
 67 |     return {
 68 |       content: [{ type: 'text', text: fullFailureMessage }],
 69 |     };
 70 |   }
 71 | }
 72 | 
 73 | export async function set_sim_locationLogic(
 74 |   params: SetSimulatorLocationParams,
 75 |   executor: CommandExecutor,
 76 | ): Promise<ToolResponse> {
 77 |   const extraValidation = (): ToolResponse | null => {
 78 |     if (params.latitude < -90 || params.latitude > 90) {
 79 |       return {
 80 |         content: [
 81 |           {
 82 |             type: 'text',
 83 |             text: 'Latitude must be between -90 and 90 degrees',
 84 |           },
 85 |         ],
 86 |       };
 87 |     }
 88 |     if (params.longitude < -180 || params.longitude > 180) {
 89 |       return {
 90 |         content: [
 91 |           {
 92 |             type: 'text',
 93 |             text: 'Longitude must be between -180 and 180 degrees',
 94 |           },
 95 |         ],
 96 |       };
 97 |     }
 98 |     return null;
 99 |   };
100 | 
101 |   log(
102 |     'info',
103 |     `Setting simulator ${params.simulatorId} location to ${params.latitude},${params.longitude}`,
104 |   );
105 | 
106 |   return executeSimctlCommandAndRespond(
107 |     params,
108 |     ['location', params.simulatorId, 'set', `${params.latitude},${params.longitude}`],
109 |     'Set Simulator Location',
110 |     `Successfully set simulator ${params.simulatorId} location to ${params.latitude},${params.longitude}`,
111 |     'Failed to set simulator location',
112 |     'set simulator location',
113 |     executor,
114 |     extraValidation,
115 |   );
116 | }
117 | 
118 | const publicSchemaObject = z.strictObject(
119 |   setSimulatorLocationSchema.omit({ simulatorId: true } as const).shape,
120 | );
121 | 
122 | export default {
123 |   name: 'set_sim_location',
124 |   description: 'Sets a custom GPS location for the simulator.',
125 |   schema: getSessionAwareToolSchemaShape({
126 |     sessionAware: publicSchemaObject,
127 |     legacy: setSimulatorLocationSchema,
128 |   }),
129 |   annotations: {
130 |     title: 'Set Simulator Location',
131 |     destructiveHint: true,
132 |   },
133 |   handler: createSessionAwareTool<SetSimulatorLocationParams>({
134 |     internalSchema: setSimulatorLocationSchema as unknown as z.ZodType<
135 |       SetSimulatorLocationParams,
136 |       unknown
137 |     >,
138 |     logicFunction: set_sim_locationLogic,
139 |     getExecutor: getDefaultCommandExecutor,
140 |     requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
141 |   }),
142 | };
143 | 
```

--------------------------------------------------------------------------------
/src/utils/simulator-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Simulator utility functions for name to UUID resolution
  3 |  */
  4 | 
  5 | import type { CommandExecutor } from './execution/index.ts';
  6 | import { ToolResponse } from '../types/common.ts';
  7 | import { log } from './logging/index.ts';
  8 | import { createErrorResponse } from './responses/index.ts';
  9 | 
 10 | /**
 11 |  * UUID regex pattern to check if a string looks like a UUID
 12 |  */
 13 | const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
 14 | 
 15 | /**
 16 |  * Determines the simulator UUID from either a UUID or name.
 17 |  *
 18 |  * Behavior:
 19 |  * - If simulatorUuid provided: return it directly
 20 |  * - Else if simulatorName looks like a UUID (regex): treat it as UUID and return it
 21 |  * - Else: resolve name → UUID via simctl and return the match (isAvailable === true)
 22 |  *
 23 |  * @param params Object containing optional simulatorUuid or simulatorName
 24 |  * @param executor Command executor for running simctl commands
 25 |  * @returns Object with uuid, optional warning, or error
 26 |  */
 27 | export async function determineSimulatorUuid(
 28 |   params: { simulatorUuid?: string; simulatorId?: string; simulatorName?: string },
 29 |   executor: CommandExecutor,
 30 | ): Promise<{ uuid?: string; warning?: string; error?: ToolResponse }> {
 31 |   const directUuid = params.simulatorUuid ?? params.simulatorId;
 32 | 
 33 |   // If UUID is provided directly, use it
 34 |   if (directUuid) {
 35 |     log('info', `Using provided simulator UUID: ${directUuid}`);
 36 |     return { uuid: directUuid };
 37 |   }
 38 | 
 39 |   // If name is provided, check if it's actually a UUID
 40 |   if (params.simulatorName) {
 41 |     // Check if the "name" is actually a UUID string
 42 |     if (UUID_REGEX.test(params.simulatorName)) {
 43 |       log(
 44 |         'info',
 45 |         `Simulator name '${params.simulatorName}' appears to be a UUID, using it directly`,
 46 |       );
 47 |       return {
 48 |         uuid: params.simulatorName,
 49 |         warning: `The simulatorName '${params.simulatorName}' appears to be a UUID. Consider using simulatorUuid parameter instead.`,
 50 |       };
 51 |     }
 52 | 
 53 |     // Resolve name to UUID via simctl
 54 |     log('info', `Looking up simulator UUID for name: ${params.simulatorName}`);
 55 | 
 56 |     const listResult = await executor(
 57 |       ['xcrun', 'simctl', 'list', 'devices', 'available', '-j'],
 58 |       'List available simulators',
 59 |     );
 60 | 
 61 |     if (!listResult.success) {
 62 |       return {
 63 |         error: createErrorResponse(
 64 |           'Failed to list simulators',
 65 |           listResult.error ?? 'Unknown error',
 66 |         ),
 67 |       };
 68 |     }
 69 | 
 70 |     try {
 71 |       interface SimulatorDevice {
 72 |         udid: string;
 73 |         name: string;
 74 |         isAvailable: boolean;
 75 |       }
 76 | 
 77 |       interface DevicesData {
 78 |         devices: Record<string, SimulatorDevice[]>;
 79 |       }
 80 | 
 81 |       const devicesData = JSON.parse(listResult.output ?? '{}') as DevicesData;
 82 | 
 83 |       // Search through all runtime sections for the named device
 84 |       for (const runtime of Object.keys(devicesData.devices)) {
 85 |         const devices = devicesData.devices[runtime];
 86 |         if (!Array.isArray(devices)) continue;
 87 | 
 88 |         // Look for exact name match with isAvailable === true
 89 |         const device = devices.find(
 90 |           (d) => d.name === params.simulatorName && d.isAvailable === true,
 91 |         );
 92 | 
 93 |         if (device) {
 94 |           log('info', `Found simulator '${params.simulatorName}' with UUID: ${device.udid}`);
 95 |           return { uuid: device.udid };
 96 |         }
 97 |       }
 98 | 
 99 |       // If no available device found, check if device exists but is unavailable
100 |       for (const runtime of Object.keys(devicesData.devices)) {
101 |         const devices = devicesData.devices[runtime];
102 |         if (!Array.isArray(devices)) continue;
103 | 
104 |         const unavailableDevice = devices.find(
105 |           (d) => d.name === params.simulatorName && d.isAvailable === false,
106 |         );
107 | 
108 |         if (unavailableDevice) {
109 |           return {
110 |             error: createErrorResponse(
111 |               `Simulator '${params.simulatorName}' exists but is not available`,
112 |               'The simulator may need to be downloaded or is incompatible with the current Xcode version',
113 |             ),
114 |           };
115 |         }
116 |       }
117 | 
118 |       // Device not found at all
119 |       return {
120 |         error: createErrorResponse(
121 |           `Simulator '${params.simulatorName}' not found`,
122 |           'Please check the simulator name or use "xcrun simctl list devices" to see available simulators',
123 |         ),
124 |       };
125 |     } catch (parseError) {
126 |       return {
127 |         error: createErrorResponse(
128 |           'Failed to parse simulator list',
129 |           parseError instanceof Error ? parseError.message : String(parseError),
130 |         ),
131 |       };
132 |     }
133 |   }
134 | 
135 |   // Neither UUID nor name provided
136 |   return {
137 |     error: createErrorResponse(
138 |       'No simulator identifier provided',
139 |       'Either simulatorUuid or simulatorName is required',
140 |     ),
141 |   };
142 | }
143 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator/__tests__/open_sim.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for open_sim plugin
  3 |  * Following CLAUDE.md testing standards with literal validation
  4 |  * Using dependency injection for deterministic testing
  5 |  */
  6 | 
  7 | import { describe, it, expect } from 'vitest';
  8 | import * as z from 'zod';
  9 | import {
 10 |   createMockCommandResponse,
 11 |   createMockExecutor,
 12 |   type CommandExecutor,
 13 | } from '../../../../test-utils/mock-executors.ts';
 14 | import openSim, { open_simLogic } from '../open_sim.ts';
 15 | 
 16 | describe('open_sim tool', () => {
 17 |   describe('Export Field Validation (Literal)', () => {
 18 |     it('should have correct name field', () => {
 19 |       expect(openSim.name).toBe('open_sim');
 20 |     });
 21 | 
 22 |     it('should have correct description field', () => {
 23 |       expect(openSim.description).toBe('Opens the iOS Simulator app.');
 24 |     });
 25 | 
 26 |     it('should have handler function', () => {
 27 |       expect(typeof openSim.handler).toBe('function');
 28 |     });
 29 | 
 30 |     it('should have correct schema validation', () => {
 31 |       const schema = z.object(openSim.schema);
 32 | 
 33 |       // Schema is empty, so any object should pass
 34 |       expect(schema.safeParse({}).success).toBe(true);
 35 | 
 36 |       expect(
 37 |         schema.safeParse({
 38 |           anyProperty: 'value',
 39 |         }).success,
 40 |       ).toBe(true);
 41 | 
 42 |       // Empty schema should accept anything
 43 |       expect(
 44 |         schema.safeParse({
 45 |           enabled: true,
 46 |         }).success,
 47 |       ).toBe(true);
 48 |     });
 49 |   });
 50 | 
 51 |   describe('Handler Behavior (Complete Literal Returns)', () => {
 52 |     it('should return exact successful open simulator response', async () => {
 53 |       const mockExecutor = createMockExecutor({
 54 |         success: true,
 55 |         output: '',
 56 |       });
 57 | 
 58 |       const result = await open_simLogic({}, mockExecutor);
 59 | 
 60 |       expect(result).toEqual({
 61 |         content: [
 62 |           {
 63 |             type: 'text',
 64 |             text: 'Simulator app opened successfully',
 65 |           },
 66 |           {
 67 |             type: 'text',
 68 |             text: `Next Steps:
 69 | 1. Boot a simulator if needed: boot_sim({ simulatorId: 'UUID_FROM_LIST_SIMULATORS' })
 70 | 2. Launch your app and interact with it
 71 | 3. Log capture options:
 72 |    - Option 1: Capture structured logs only (app continues running):
 73 |      start_sim_log_cap({ simulatorId: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID' })
 74 |    - Option 2: Capture both console and structured logs (app will restart):
 75 |      start_sim_log_cap({ simulatorId: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID', captureConsole: true })
 76 |    - Option 3: Launch app with logs in one step:
 77 |      launch_app_logs_sim({ simulatorId: 'UUID', bundleId: 'YOUR_APP_BUNDLE_ID' })`,
 78 |           },
 79 |         ],
 80 |       });
 81 |     });
 82 | 
 83 |     it('should return exact command failure response', async () => {
 84 |       const mockExecutor = createMockExecutor({
 85 |         success: false,
 86 |         error: 'Command failed',
 87 |       });
 88 | 
 89 |       const result = await open_simLogic({}, mockExecutor);
 90 | 
 91 |       expect(result).toEqual({
 92 |         content: [
 93 |           {
 94 |             type: 'text',
 95 |             text: 'Open simulator operation failed: Command failed',
 96 |           },
 97 |         ],
 98 |       });
 99 |     });
100 | 
101 |     it('should return exact exception handling response', async () => {
102 |       const mockExecutor: CommandExecutor = async () => {
103 |         throw new Error('Test error');
104 |       };
105 | 
106 |       const result = await open_simLogic({}, mockExecutor);
107 | 
108 |       expect(result).toEqual({
109 |         content: [
110 |           {
111 |             type: 'text',
112 |             text: 'Open simulator operation failed: Test error',
113 |           },
114 |         ],
115 |       });
116 |     });
117 | 
118 |     it('should return exact string error handling response', async () => {
119 |       const mockExecutor: CommandExecutor = async () => {
120 |         throw 'String error';
121 |       };
122 | 
123 |       const result = await open_simLogic({}, mockExecutor);
124 | 
125 |       expect(result).toEqual({
126 |         content: [
127 |           {
128 |             type: 'text',
129 |             text: 'Open simulator operation failed: String error',
130 |           },
131 |         ],
132 |       });
133 |     });
134 | 
135 |     it('should verify command generation with mock executor', async () => {
136 |       const calls: Array<{
137 |         command: string[];
138 |         description?: string;
139 |         hideOutput?: boolean;
140 |         opts?: { cwd?: string };
141 |       }> = [];
142 | 
143 |       const mockExecutor: CommandExecutor = async (
144 |         command,
145 |         description,
146 |         hideOutput,
147 |         opts,
148 |         detached,
149 |       ) => {
150 |         calls.push({ command, description, hideOutput, opts });
151 |         void detached;
152 |         return createMockCommandResponse({
153 |           success: true,
154 |           output: '',
155 |           error: undefined,
156 |         });
157 |       };
158 | 
159 |       await open_simLogic({}, mockExecutor);
160 | 
161 |       expect(calls).toHaveLength(1);
162 |       expect(calls[0]).toEqual({
163 |         command: ['open', '-a', 'Simulator'],
164 |         description: 'Open Simulator',
165 |         hideOutput: true,
166 |         opts: undefined,
167 |       });
168 |     });
169 |   });
170 | });
171 | 
```

--------------------------------------------------------------------------------
/docs/dev/session-aware-migration-todo.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Session-Aware Migration TODO
 2 | 
 3 | _Audit date: October 6, 2025_
 4 | 
 5 | Reference: [session_management_plan.md](session_management_plan.md)
 6 | 
 7 | ## Utilities
 8 | - [x] `src/mcp/tools/utilities/clean.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
 9 | 
10 | ## Project Discovery
11 | - [x] `src/mcp/tools/project-discovery/list_schemes.ts` — session defaults: `projectPath`, `workspacePath`.
12 | - [x] `src/mcp/tools/project-discovery/show_build_settings.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`.
13 | 
14 | ## Device Workflows
15 | - [x] `src/mcp/tools/device/build_device.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
16 | - [x] `src/mcp/tools/device/test_device.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `deviceId`, `configuration`.
17 | - [x] `src/mcp/tools/device/get_device_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
18 | - [x] `src/mcp/tools/device/install_app_device.ts` — session defaults: `deviceId`.
19 | - [x] `src/mcp/tools/device/launch_app_device.ts` — session defaults: `deviceId`.
20 | - [x] `src/mcp/tools/device/stop_app_device.ts` — session defaults: `deviceId`.
21 | 
22 | ## Device Logging
23 | - [x] `src/mcp/tools/logging/start_device_log_cap.ts` — session defaults: `deviceId`.
24 | 
25 | ## macOS Workflows
26 | - [x] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
27 | - [x] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
28 | - [x] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
29 | - [x] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
30 | 
31 | ## Simulator Build/Test/Path
32 | - [x] `src/mcp/tools/simulator/test_sim.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`.
33 | - [x] `src/mcp/tools/simulator/get_sim_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`, `arch`.
34 | 
35 | ## Simulator Runtime Actions
36 | - [x] `src/mcp/tools/simulator/boot_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
37 | - [x] `src/mcp/tools/simulator/install_app_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
38 | - [x] `src/mcp/tools/simulator/launch_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
39 | - [x] `src/mcp/tools/simulator/launch_app_logs_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
40 | - [x] `src/mcp/tools/simulator/stop_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
41 | - [x] `src/mcp/tools/simulator/record_sim_video.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
42 | 
43 | ## Simulator Management
44 | - [x] `src/mcp/tools/simulator-management/erase_sims.ts` — session defaults: `simulatorId` (covers `simulatorUdid`).
45 | - [x] `src/mcp/tools/simulator-management/set_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
46 | - [x] `src/mcp/tools/simulator-management/reset_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
47 | - [x] `src/mcp/tools/simulator-management/set_sim_appearance.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
48 | - [x] `src/mcp/tools/simulator-management/sim_statusbar.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
49 | 
50 | ## Simulator Logging
51 | - [x] `src/mcp/tools/logging/start_sim_log_cap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
52 | 
53 | ## AXe UI Testing Tools
54 | - [x] `src/mcp/tools/ui-testing/button.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
55 | - [x] `src/mcp/tools/ui-testing/describe_ui.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
56 | - [x] `src/mcp/tools/ui-testing/gesture.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
57 | - [x] `src/mcp/tools/ui-testing/key_press.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
58 | - [x] `src/mcp/tools/ui-testing/key_sequence.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
59 | - [x] `src/mcp/tools/ui-testing/long_press.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
60 | - [x] `src/mcp/tools/ui-testing/screenshot.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
61 | - [x] `src/mcp/tools/ui-testing/swipe.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
62 | - [x] `src/mcp/tools/ui-testing/tap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
63 | - [x] `src/mcp/tools/ui-testing/touch.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
64 | - [x] `src/mcp/tools/ui-testing/type_text.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
65 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/show_build_settings.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Project Discovery Plugin: Show Build Settings (Unified)
  3 |  *
  4 |  * Shows build settings from either a project or workspace using xcodebuild.
  5 |  * Accepts mutually exclusive `projectPath` or `workspacePath`.
  6 |  */
  7 | 
  8 | import * as z from 'zod';
  9 | import { log } from '../../../utils/logging/index.ts';
 10 | import type { CommandExecutor } from '../../../utils/execution/index.ts';
 11 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
 12 | import { createTextResponse } from '../../../utils/responses/index.ts';
 13 | import { ToolResponse } from '../../../types/common.ts';
 14 | import {
 15 |   createSessionAwareTool,
 16 |   getSessionAwareToolSchemaShape,
 17 | } from '../../../utils/typed-tool-factory.ts';
 18 | import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
 19 | 
 20 | // Unified schema: XOR between projectPath and workspacePath
 21 | const baseSchemaObject = z.object({
 22 |   projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
 23 |   workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
 24 |   scheme: z.string().describe('Scheme name to show build settings for (Required)'),
 25 | });
 26 | 
 27 | const showBuildSettingsSchema = z.preprocess(
 28 |   nullifyEmptyStrings,
 29 |   baseSchemaObject
 30 |     .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
 31 |       message: 'Either projectPath or workspacePath is required.',
 32 |     })
 33 |     .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
 34 |       message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
 35 |     }),
 36 | );
 37 | 
 38 | export type ShowBuildSettingsParams = z.infer<typeof showBuildSettingsSchema>;
 39 | 
 40 | /**
 41 |  * Business logic for showing build settings from a project or workspace.
 42 |  * Exported for direct testing and reuse.
 43 |  */
 44 | export async function showBuildSettingsLogic(
 45 |   params: ShowBuildSettingsParams,
 46 |   executor: CommandExecutor,
 47 | ): Promise<ToolResponse> {
 48 |   log('info', `Showing build settings for scheme ${params.scheme}`);
 49 | 
 50 |   try {
 51 |     // Create the command array for xcodebuild
 52 |     const command = ['xcodebuild', '-showBuildSettings']; // -showBuildSettings as an option, not an action
 53 | 
 54 |     const hasProjectPath = typeof params.projectPath === 'string';
 55 |     const path = hasProjectPath ? params.projectPath : params.workspacePath;
 56 | 
 57 |     if (hasProjectPath) {
 58 |       command.push('-project', params.projectPath!);
 59 |     } else {
 60 |       command.push('-workspace', params.workspacePath!);
 61 |     }
 62 | 
 63 |     // Add the scheme
 64 |     command.push('-scheme', params.scheme);
 65 | 
 66 |     // Execute the command directly
 67 |     const result = await executor(command, 'Show Build Settings', true);
 68 | 
 69 |     if (!result.success) {
 70 |       return createTextResponse(`Failed to show build settings: ${result.error}`, true);
 71 |     }
 72 | 
 73 |     // Create response based on which type was used (similar to workspace version with next steps)
 74 |     const content: Array<{ type: 'text'; text: string }> = [
 75 |       {
 76 |         type: 'text',
 77 |         text: hasProjectPath
 78 |           ? `✅ Build settings for scheme ${params.scheme}:`
 79 |           : '✅ Build settings retrieved successfully',
 80 |       },
 81 |       {
 82 |         type: 'text',
 83 |         text: result.output || 'Build settings retrieved successfully.',
 84 |       },
 85 |     ];
 86 | 
 87 |     // Add next steps for workspace (similar to original workspace implementation)
 88 |     if (!hasProjectPath && path) {
 89 |       content.push({
 90 |         type: 'text',
 91 |         text: `Next Steps:
 92 | - Build the workspace: build_macos({ workspacePath: "${path}", scheme: "${params.scheme}" })
 93 | - For iOS: build_sim({ workspacePath: "${path}", scheme: "${params.scheme}", simulatorName: "iPhone 16" })
 94 | - List schemes: list_schemes({ workspacePath: "${path}" })`,
 95 |       });
 96 |     }
 97 | 
 98 |     return {
 99 |       content,
100 |       isError: false,
101 |     };
102 |   } catch (error) {
103 |     const errorMessage = error instanceof Error ? error.message : String(error);
104 |     log('error', `Error showing build settings: ${errorMessage}`);
105 |     return createTextResponse(`Error showing build settings: ${errorMessage}`, true);
106 |   }
107 | }
108 | 
109 | const publicSchemaObject = baseSchemaObject.omit({
110 |   projectPath: true,
111 |   workspacePath: true,
112 |   scheme: true,
113 | } as const);
114 | 
115 | export default {
116 |   name: 'show_build_settings',
117 |   description: 'Shows xcodebuild build settings.',
118 |   schema: getSessionAwareToolSchemaShape({
119 |     sessionAware: publicSchemaObject,
120 |     legacy: baseSchemaObject,
121 |   }),
122 |   annotations: {
123 |     title: 'Show Build Settings',
124 |     readOnlyHint: true,
125 |   },
126 |   handler: createSessionAwareTool<ShowBuildSettingsParams>({
127 |     internalSchema: showBuildSettingsSchema as unknown as z.ZodType<
128 |       ShowBuildSettingsParams,
129 |       unknown
130 |     >,
131 |     logicFunction: showBuildSettingsLogic,
132 |     getExecutor: getDefaultCommandExecutor,
133 |     requirements: [
134 |       { allOf: ['scheme'], message: 'scheme is required' },
135 |       { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' },
136 |     ],
137 |     exclusivePairs: [['projectPath', 'workspacePath']],
138 |   }),
139 | };
140 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator/__tests__/boot_sim.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for boot_sim plugin (session-aware version)
  3 |  * Follows CLAUDE.md guidance: dependency injection, no vi-mocks, literal validation.
  4 |  */
  5 | 
  6 | import { describe, it, expect, beforeEach } from 'vitest';
  7 | import * as z from 'zod';
  8 | import {
  9 |   createMockCommandResponse,
 10 |   createMockExecutor,
 11 | } from '../../../../test-utils/mock-executors.ts';
 12 | import { sessionStore } from '../../../../utils/session-store.ts';
 13 | import bootSim, { boot_simLogic } from '../boot_sim.ts';
 14 | 
 15 | describe('boot_sim tool', () => {
 16 |   beforeEach(() => {
 17 |     sessionStore.clear();
 18 |   });
 19 | 
 20 |   describe('Export Field Validation (Literal)', () => {
 21 |     it('should have correct name', () => {
 22 |       expect(bootSim.name).toBe('boot_sim');
 23 |     });
 24 | 
 25 |     it('should have concise description', () => {
 26 |       expect(bootSim.description).toBe('Boots an iOS simulator.');
 27 |     });
 28 | 
 29 |     it('should expose empty public schema', () => {
 30 |       const schema = z.object(bootSim.schema);
 31 |       expect(schema.safeParse({}).success).toBe(true);
 32 |       expect(Object.keys(bootSim.schema)).toHaveLength(0);
 33 | 
 34 |       const withSimId = schema.safeParse({ simulatorId: 'abc' });
 35 |       expect(withSimId.success).toBe(true);
 36 |       expect('simulatorId' in (withSimId.data as Record<string, unknown>)).toBe(false);
 37 |     });
 38 |   });
 39 | 
 40 |   describe('Handler Requirements', () => {
 41 |     it('should require simulatorId when not provided', async () => {
 42 |       const result = await bootSim.handler({});
 43 | 
 44 |       expect(result.isError).toBe(true);
 45 |       const message = result.content[0].text;
 46 |       expect(message).toContain('Missing required session defaults');
 47 |       expect(message).toContain('simulatorId is required');
 48 |       expect(message).toContain('session-set-defaults');
 49 |     });
 50 |   });
 51 | 
 52 |   describe('Logic Behavior (Literal Results)', () => {
 53 |     it('should handle successful boot', async () => {
 54 |       const mockExecutor = createMockExecutor({
 55 |         success: true,
 56 |         output: 'Simulator booted successfully',
 57 |       });
 58 | 
 59 |       const result = await boot_simLogic({ simulatorId: 'test-uuid-123' }, mockExecutor);
 60 | 
 61 |       expect(result).toEqual({
 62 |         content: [
 63 |           {
 64 |             type: 'text',
 65 |             text: `✅ Simulator booted successfully. To make it visible, use: open_sim()\n\nNext steps:\n1. Open the Simulator app (makes it visible): open_sim()\n2. Install an app: install_app_sim({ simulatorId: "test-uuid-123", appPath: "PATH_TO_YOUR_APP" })\n3. Launch an app: launch_app_sim({ simulatorId: "test-uuid-123", bundleId: "YOUR_APP_BUNDLE_ID" })`,
 66 |           },
 67 |         ],
 68 |       });
 69 |     });
 70 | 
 71 |     it('should handle command failure', async () => {
 72 |       const mockExecutor = createMockExecutor({
 73 |         success: false,
 74 |         error: 'Simulator not found',
 75 |       });
 76 | 
 77 |       const result = await boot_simLogic({ simulatorId: 'invalid-uuid' }, mockExecutor);
 78 | 
 79 |       expect(result).toEqual({
 80 |         content: [
 81 |           {
 82 |             type: 'text',
 83 |             text: 'Boot simulator operation failed: Simulator not found',
 84 |           },
 85 |         ],
 86 |       });
 87 |     });
 88 | 
 89 |     it('should handle exception with Error object', async () => {
 90 |       const mockExecutor = async () => {
 91 |         throw new Error('Connection failed');
 92 |       };
 93 | 
 94 |       const result = await boot_simLogic({ simulatorId: 'test-uuid-123' }, mockExecutor);
 95 | 
 96 |       expect(result).toEqual({
 97 |         content: [
 98 |           {
 99 |             type: 'text',
100 |             text: 'Boot simulator operation failed: Connection failed',
101 |           },
102 |         ],
103 |       });
104 |     });
105 | 
106 |     it('should handle exception with string error', async () => {
107 |       const mockExecutor = async () => {
108 |         throw 'String error';
109 |       };
110 | 
111 |       const result = await boot_simLogic({ simulatorId: 'test-uuid-123' }, mockExecutor);
112 | 
113 |       expect(result).toEqual({
114 |         content: [
115 |           {
116 |             type: 'text',
117 |             text: 'Boot simulator operation failed: String error',
118 |           },
119 |         ],
120 |       });
121 |     });
122 | 
123 |     it('should verify command generation with mock executor', async () => {
124 |       const calls: Array<{
125 |         command: string[];
126 |         description?: string;
127 |         allowStderr?: boolean;
128 |         opts?: { cwd?: string };
129 |       }> = [];
130 |       const mockExecutor = async (
131 |         command: string[],
132 |         description?: string,
133 |         allowStderr?: boolean,
134 |         opts?: { cwd?: string },
135 |         detached?: boolean,
136 |       ) => {
137 |         calls.push({ command, description, allowStderr, opts });
138 |         void detached;
139 |         return createMockCommandResponse({
140 |           success: true,
141 |           output: 'Simulator booted successfully',
142 |           error: undefined,
143 |         });
144 |       };
145 | 
146 |       await boot_simLogic({ simulatorId: 'test-uuid-123' }, mockExecutor);
147 | 
148 |       expect(calls).toHaveLength(1);
149 |       expect(calls[0]).toEqual({
150 |         command: ['xcrun', 'simctl', 'boot', 'test-uuid-123'],
151 |         description: 'Boot Simulator',
152 |         allowStderr: true,
153 |         opts: undefined,
154 |       });
155 |     });
156 |   });
157 | });
158 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/device/launch_app_device.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Device Workspace Plugin: Launch App Device
  3 |  *
  4 |  * Launches an app on a physical Apple device (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro).
  5 |  * Requires deviceId and bundleId.
  6 |  */
  7 | 
  8 | import * as z from 'zod';
  9 | import { ToolResponse } from '../../../types/common.ts';
 10 | import { log } from '../../../utils/logging/index.ts';
 11 | import type { CommandExecutor, FileSystemExecutor } from '../../../utils/execution/index.ts';
 12 | import {
 13 |   getDefaultCommandExecutor,
 14 |   getDefaultFileSystemExecutor,
 15 | } from '../../../utils/execution/index.ts';
 16 | import {
 17 |   createSessionAwareTool,
 18 |   getSessionAwareToolSchemaShape,
 19 | } from '../../../utils/typed-tool-factory.ts';
 20 | import { join } from 'path';
 21 | 
 22 | // Type for the launch JSON response
 23 | type LaunchDataResponse = {
 24 |   result?: {
 25 |     process?: {
 26 |       processIdentifier?: number;
 27 |     };
 28 |   };
 29 | };
 30 | 
 31 | // Define schema as ZodObject
 32 | const launchAppDeviceSchema = z.object({
 33 |   deviceId: z.string().describe('UDID of the device (obtained from list_devices)'),
 34 |   bundleId: z
 35 |     .string()
 36 |     .describe('Bundle identifier of the app to launch (e.g., "com.example.MyApp")'),
 37 | });
 38 | 
 39 | const publicSchemaObject = launchAppDeviceSchema.omit({ deviceId: true } as const);
 40 | 
 41 | // Use z.infer for type safety
 42 | type LaunchAppDeviceParams = z.infer<typeof launchAppDeviceSchema>;
 43 | 
 44 | export async function launch_app_deviceLogic(
 45 |   params: LaunchAppDeviceParams,
 46 |   executor: CommandExecutor,
 47 |   fileSystem: FileSystemExecutor,
 48 | ): Promise<ToolResponse> {
 49 |   const { deviceId, bundleId } = params;
 50 | 
 51 |   log('info', `Launching app ${bundleId} on device ${deviceId}`);
 52 | 
 53 |   try {
 54 |     // Use JSON output to capture process ID
 55 |     const tempJsonPath = join(fileSystem.tmpdir(), `launch-${Date.now()}.json`);
 56 | 
 57 |     const result = await executor(
 58 |       [
 59 |         'xcrun',
 60 |         'devicectl',
 61 |         'device',
 62 |         'process',
 63 |         'launch',
 64 |         '--device',
 65 |         deviceId,
 66 |         '--json-output',
 67 |         tempJsonPath,
 68 |         '--terminate-existing',
 69 |         bundleId,
 70 |       ],
 71 |       'Launch app on device',
 72 |       true, // useShell
 73 |       undefined, // env
 74 |     );
 75 | 
 76 |     if (!result.success) {
 77 |       return {
 78 |         content: [
 79 |           {
 80 |             type: 'text',
 81 |             text: `Failed to launch app: ${result.error}`,
 82 |           },
 83 |         ],
 84 |         isError: true,
 85 |       };
 86 |     }
 87 | 
 88 |     // Parse JSON to extract process ID
 89 |     let processId: number | undefined;
 90 |     try {
 91 |       const jsonContent = await fileSystem.readFile(tempJsonPath, 'utf8');
 92 |       const parsedData: unknown = JSON.parse(jsonContent);
 93 | 
 94 |       // Type guard to validate the parsed data structure
 95 |       if (
 96 |         parsedData &&
 97 |         typeof parsedData === 'object' &&
 98 |         'result' in parsedData &&
 99 |         parsedData.result &&
100 |         typeof parsedData.result === 'object' &&
101 |         'process' in parsedData.result &&
102 |         parsedData.result.process &&
103 |         typeof parsedData.result.process === 'object' &&
104 |         'processIdentifier' in parsedData.result.process &&
105 |         typeof parsedData.result.process.processIdentifier === 'number'
106 |       ) {
107 |         const launchData = parsedData as LaunchDataResponse;
108 |         processId = launchData.result?.process?.processIdentifier;
109 |       }
110 | 
111 |       // Clean up temp file
112 |       await fileSystem.rm(tempJsonPath, { force: true }).catch(() => {});
113 |     } catch (error) {
114 |       log('warn', `Failed to parse launch JSON output: ${error}`);
115 |     }
116 | 
117 |     let responseText = `✅ App launched successfully\n\n${result.output}`;
118 | 
119 |     if (processId) {
120 |       responseText += `\n\nProcess ID: ${processId}`;
121 |       responseText += `\n\nNext Steps:`;
122 |       responseText += `\n1. Interact with your app on the device`;
123 |       responseText += `\n2. Stop the app: stop_app_device({ deviceId: "${deviceId}", processId: ${processId} })`;
124 |     }
125 | 
126 |     return {
127 |       content: [
128 |         {
129 |           type: 'text',
130 |           text: responseText,
131 |         },
132 |       ],
133 |     };
134 |   } catch (error) {
135 |     const errorMessage = error instanceof Error ? error.message : String(error);
136 |     log('error', `Error launching app on device: ${errorMessage}`);
137 |     return {
138 |       content: [
139 |         {
140 |           type: 'text',
141 |           text: `Failed to launch app on device: ${errorMessage}`,
142 |         },
143 |       ],
144 |       isError: true,
145 |     };
146 |   }
147 | }
148 | 
149 | export default {
150 |   name: 'launch_app_device',
151 |   description: 'Launches an app on a connected device.',
152 |   schema: getSessionAwareToolSchemaShape({
153 |     sessionAware: publicSchemaObject,
154 |     legacy: launchAppDeviceSchema,
155 |   }),
156 |   annotations: {
157 |     title: 'Launch App Device',
158 |     destructiveHint: true,
159 |   },
160 |   handler: createSessionAwareTool<LaunchAppDeviceParams>({
161 |     internalSchema: launchAppDeviceSchema as unknown as z.ZodType<LaunchAppDeviceParams>,
162 |     logicFunction: (params, executor) =>
163 |       launch_app_deviceLogic(params, executor, getDefaultFileSystemExecutor()),
164 |     getExecutor: getDefaultCommandExecutor,
165 |     requirements: [{ allOf: ['deviceId'], message: 'deviceId is required' }],
166 |   }),
167 | };
168 | 
```

--------------------------------------------------------------------------------
/src/utils/debugger/dap/__tests__/transport-framing.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { ChildProcess } from 'node:child_process';
  2 | import { EventEmitter } from 'node:events';
  3 | import { PassThrough } from 'node:stream';
  4 | import type { InteractiveProcess, InteractiveSpawner } from '../../../execution/index.ts';
  5 | import { describe, expect, it } from 'vitest';
  6 | 
  7 | import { DapTransport } from '../transport.ts';
  8 | import type { DapEvent, DapResponse } from '../types.ts';
  9 | type TestSession = {
 10 |   stdout: PassThrough;
 11 |   stderr: PassThrough;
 12 |   stdin: PassThrough;
 13 |   emitExit: (code?: number | null, signal?: NodeJS.Signals | null) => void;
 14 |   emitError: (error: Error) => void;
 15 | };
 16 | 
 17 | function encodeMessage(message: Record<string, unknown>): string {
 18 |   const payload = JSON.stringify(message);
 19 |   return `Content-Length: ${Buffer.byteLength(payload, 'utf8')}\r\n\r\n${payload}`;
 20 | }
 21 | 
 22 | function buildResponse(
 23 |   requestSeq: number,
 24 |   command: string,
 25 |   body?: Record<string, unknown>,
 26 | ): DapResponse {
 27 |   return {
 28 |     seq: requestSeq + 100,
 29 |     type: 'response',
 30 |     request_seq: requestSeq,
 31 |     success: true,
 32 |     command,
 33 |     body,
 34 |   };
 35 | }
 36 | 
 37 | function createTestSpawner(): { spawner: InteractiveSpawner; session: TestSession } {
 38 |   const stdout = new PassThrough();
 39 |   const stderr = new PassThrough();
 40 |   const stdin = new PassThrough();
 41 |   const emitter = new EventEmitter();
 42 |   const mockProcess = emitter as unknown as ChildProcess;
 43 |   const mutableProcess = mockProcess as unknown as {
 44 |     stdout: PassThrough | null;
 45 |     stderr: PassThrough | null;
 46 |     stdin: PassThrough | null;
 47 |     killed: boolean;
 48 |     exitCode: number | null;
 49 |     signalCode: NodeJS.Signals | null;
 50 |     spawnargs: string[];
 51 |     spawnfile: string;
 52 |     pid: number;
 53 |   };
 54 | 
 55 |   mutableProcess.stdout = stdout;
 56 |   mutableProcess.stderr = stderr;
 57 |   mutableProcess.stdin = stdin;
 58 |   mutableProcess.killed = false;
 59 |   mutableProcess.exitCode = null;
 60 |   mutableProcess.signalCode = null;
 61 |   mutableProcess.spawnargs = [];
 62 |   mutableProcess.spawnfile = 'mock';
 63 |   mutableProcess.pid = 12345;
 64 |   mockProcess.kill = ((signal?: NodeJS.Signals): boolean => {
 65 |     mutableProcess.killed = true;
 66 |     emitter.emit('exit', 0, signal ?? null);
 67 |     return true;
 68 |   }) as ChildProcess['kill'];
 69 | 
 70 |   const session: TestSession = {
 71 |     stdout,
 72 |     stderr,
 73 |     stdin,
 74 |     emitExit: (code = 0, signal = null) => {
 75 |       emitter.emit('exit', code, signal);
 76 |     },
 77 |     emitError: (error) => {
 78 |       emitter.emit('error', error);
 79 |     },
 80 |   };
 81 | 
 82 |   const spawner: InteractiveSpawner = (): InteractiveProcess => ({
 83 |     process: mockProcess,
 84 |     write(data: string): void {
 85 |       stdin.write(data);
 86 |     },
 87 |     kill(signal?: NodeJS.Signals): void {
 88 |       mockProcess.kill?.(signal);
 89 |     },
 90 |     dispose(): void {
 91 |       stdout.end();
 92 |       stderr.end();
 93 |       stdin.end();
 94 |       emitter.removeAllListeners();
 95 |     },
 96 |   });
 97 | 
 98 |   return { spawner, session };
 99 | }
100 | 
101 | describe('DapTransport framing', () => {
102 |   it('parses responses across chunk boundaries', async () => {
103 |     const { spawner, session } = createTestSpawner();
104 | 
105 |     const transport = new DapTransport({ spawner, adapterCommand: ['lldb-dap'] });
106 | 
107 |     const responsePromise = transport.sendRequest<undefined, { ok: boolean }>(
108 |       'initialize',
109 |       undefined,
110 |       { timeoutMs: 1_000 },
111 |     );
112 | 
113 |     const response = encodeMessage(buildResponse(1, 'initialize', { ok: true }));
114 |     session.stdout.write(response.slice(0, 12));
115 |     session.stdout.write(response.slice(12));
116 | 
117 |     await expect(responsePromise).resolves.toEqual({ ok: true });
118 |     transport.dispose();
119 |   });
120 | 
121 |   it('handles multiple messages in a single chunk', async () => {
122 |     const { spawner, session } = createTestSpawner();
123 | 
124 |     const transport = new DapTransport({ spawner, adapterCommand: ['lldb-dap'] });
125 |     const events: DapEvent[] = [];
126 |     transport.onEvent((event) => events.push(event));
127 | 
128 |     const responsePromise = transport.sendRequest<undefined, { ok: boolean }>(
129 |       'threads',
130 |       undefined,
131 |       { timeoutMs: 1_000 },
132 |     );
133 | 
134 |     const eventMessage = encodeMessage({
135 |       seq: 55,
136 |       type: 'event',
137 |       event: 'output',
138 |       body: { output: 'hello' },
139 |     });
140 |     const responseMessage = encodeMessage(buildResponse(1, 'threads', { ok: true }));
141 | 
142 |     session.stdout.write(`${eventMessage}${responseMessage}`);
143 | 
144 |     await expect(responsePromise).resolves.toEqual({ ok: true });
145 |     expect(events).toHaveLength(1);
146 |     expect(events[0]?.event).toBe('output');
147 |     transport.dispose();
148 |   });
149 | 
150 |   it('continues after invalid headers', async () => {
151 |     const { spawner, session } = createTestSpawner();
152 | 
153 |     const transport = new DapTransport({ spawner, adapterCommand: ['lldb-dap'] });
154 | 
155 |     const responsePromise = transport.sendRequest<undefined, { ok: boolean }>(
156 |       'stackTrace',
157 |       undefined,
158 |       { timeoutMs: 1_000 },
159 |     );
160 | 
161 |     session.stdout.write('Content-Length: nope\r\n\r\n');
162 |     const responseMessage = encodeMessage(buildResponse(1, 'stackTrace', { ok: true }));
163 |     session.stdout.write(responseMessage);
164 | 
165 |     await expect(responsePromise).resolves.toEqual({ ok: true });
166 |     transport.dispose();
167 |   });
168 | });
169 | 
```

--------------------------------------------------------------------------------
/src/utils/__tests__/typed-tool-factory.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for the createTypedTool factory
  3 |  */
  4 | 
  5 | import { describe, it, expect } from 'vitest';
  6 | import * as z from 'zod';
  7 | import { createTypedTool } from '../typed-tool-factory.ts';
  8 | import { createMockExecutor } from '../../test-utils/mock-executors.ts';
  9 | import { ToolResponse } from '../../types/common.ts';
 10 | 
 11 | // Test schema and types
 12 | const testSchema = z.object({
 13 |   requiredParam: z.string().describe('A required string parameter'),
 14 |   optionalParam: z.number().optional().describe('An optional number parameter'),
 15 | });
 16 | 
 17 | type TestParams = z.infer<typeof testSchema>;
 18 | 
 19 | // Mock logic function for testing
 20 | async function testLogic(params: TestParams): Promise<ToolResponse> {
 21 |   return {
 22 |     content: [{ type: 'text', text: `Logic executed with: ${params.requiredParam}` }],
 23 |     isError: false,
 24 |   };
 25 | }
 26 | 
 27 | describe('createTypedTool', () => {
 28 |   describe('Type Safety and Validation', () => {
 29 |     it('should accept valid parameters and call logic function', async () => {
 30 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
 31 |       const handler = createTypedTool(testSchema, testLogic, () => mockExecutor);
 32 | 
 33 |       const result = await handler({
 34 |         requiredParam: 'valid-value',
 35 |         optionalParam: 42,
 36 |       });
 37 | 
 38 |       expect(result.isError).toBe(false);
 39 |       expect(result.content[0].text).toContain('Logic executed with: valid-value');
 40 |     });
 41 | 
 42 |     it('should reject parameters with missing required fields', async () => {
 43 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
 44 |       const handler = createTypedTool(testSchema, testLogic, () => mockExecutor);
 45 | 
 46 |       const result = await handler({
 47 |         // Missing requiredParam
 48 |         optionalParam: 42,
 49 |       });
 50 | 
 51 |       expect(result.isError).toBe(true);
 52 |       expect(result.content[0].text).toContain('Parameter validation failed');
 53 |       expect(result.content[0].text).toContain('requiredParam');
 54 |     });
 55 | 
 56 |     it('should reject parameters with wrong types', async () => {
 57 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
 58 |       const handler = createTypedTool(testSchema, testLogic, () => mockExecutor);
 59 | 
 60 |       const result = await handler({
 61 |         requiredParam: 123, // Should be string, not number
 62 |         optionalParam: 42,
 63 |       });
 64 | 
 65 |       expect(result.isError).toBe(true);
 66 |       expect(result.content[0].text).toContain('Parameter validation failed');
 67 |       expect(result.content[0].text).toContain('requiredParam');
 68 |     });
 69 | 
 70 |     it('should accept parameters with only required fields', async () => {
 71 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
 72 |       const handler = createTypedTool(testSchema, testLogic, () => mockExecutor);
 73 | 
 74 |       const result = await handler({
 75 |         requiredParam: 'valid-value',
 76 |         // optionalParam omitted
 77 |       });
 78 | 
 79 |       expect(result.isError).toBe(false);
 80 |       expect(result.content[0].text).toContain('Logic executed with: valid-value');
 81 |     });
 82 | 
 83 |     it('should provide detailed validation error messages', async () => {
 84 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
 85 |       const handler = createTypedTool(testSchema, testLogic, () => mockExecutor);
 86 | 
 87 |       const result = await handler({
 88 |         requiredParam: 123, // Wrong type
 89 |         optionalParam: 'should-be-number', // Wrong type
 90 |       });
 91 | 
 92 |       expect(result.isError).toBe(true);
 93 |       const errorText = result.content[0].text;
 94 |       expect(errorText).toContain('Parameter validation failed');
 95 |       expect(errorText).toContain('requiredParam');
 96 |       expect(errorText).toContain('optionalParam');
 97 |     });
 98 |   });
 99 | 
100 |   describe('Error Handling', () => {
101 |     it('should re-throw non-Zod errors from logic function', async () => {
102 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
103 | 
104 |       // Logic function that throws a non-Zod error
105 |       async function errorLogic(): Promise<ToolResponse> {
106 |         throw new Error('Unexpected error');
107 |       }
108 | 
109 |       const handler = createTypedTool(testSchema, errorLogic, () => mockExecutor);
110 | 
111 |       await expect(handler({ requiredParam: 'valid' })).rejects.toThrow('Unexpected error');
112 |     });
113 |   });
114 | 
115 |   describe('Executor Integration', () => {
116 |     it('should pass the provided executor to logic function', async () => {
117 |       const mockExecutor = createMockExecutor({ success: true, output: 'test' });
118 | 
119 |       async function executorTestLogic(params: TestParams, executor: any): Promise<ToolResponse> {
120 |         // Verify executor is passed correctly
121 |         expect(executor).toBe(mockExecutor);
122 |         return {
123 |           content: [{ type: 'text', text: 'Executor passed correctly' }],
124 |           isError: false,
125 |         };
126 |       }
127 | 
128 |       const handler = createTypedTool(testSchema, executorTestLogic, () => mockExecutor);
129 | 
130 |       const result = await handler({ requiredParam: 'valid' });
131 | 
132 |       expect(result.isError).toBe(false);
133 |       expect(result.content[0].text).toBe('Executor passed correctly');
134 |     });
135 |   });
136 | });
137 | 
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
  1 | import eslint from '@eslint/js';
  2 | import tseslint from 'typescript-eslint';
  3 | import prettierPlugin from 'eslint-plugin-prettier';
  4 | 
  5 | export default [
  6 |   eslint.configs.recommended,
  7 |   ...tseslint.configs.recommended,
  8 |   {
  9 |     ignores: ['node_modules/**', 'build/**', 'dist/**', 'coverage/**', 'src/core/generated-plugins.ts', 'src/core/generated-resources.ts'],
 10 |   },
 11 |   {
 12 |     // TypeScript files in src/ directory (covered by tsconfig.json)
 13 |     files: ['src/**/*.ts'],
 14 |     languageOptions: {
 15 |       ecmaVersion: 2020,
 16 |       sourceType: 'module',
 17 |       parser: tseslint.parser,
 18 |       parserOptions: {
 19 |         project: ['./tsconfig.json'],
 20 |       },
 21 |     },
 22 |     plugins: {
 23 |       '@typescript-eslint': tseslint.plugin,
 24 |       'prettier': prettierPlugin,
 25 |     },
 26 |     rules: {
 27 |       'prettier/prettier': 'error',
 28 |       '@typescript-eslint/explicit-function-return-type': 'warn',
 29 |       '@typescript-eslint/no-explicit-any': 'error',
 30 |       '@typescript-eslint/no-unused-vars': ['error', { 
 31 |         argsIgnorePattern: 'never',
 32 |         varsIgnorePattern: 'never' 
 33 |       }],
 34 |       'no-console': ['warn', { allow: ['error'] }],
 35 |       
 36 |       // Prevent dangerous type casting anti-patterns (errors)
 37 |       '@typescript-eslint/consistent-type-assertions': ['error', {
 38 |         assertionStyle: 'as',
 39 |         objectLiteralTypeAssertions: 'never'
 40 |       }],
 41 |       '@typescript-eslint/no-unsafe-argument': 'error',
 42 |       '@typescript-eslint/no-unsafe-assignment': 'error',
 43 |       '@typescript-eslint/no-unsafe-call': 'error',
 44 |       '@typescript-eslint/no-unsafe-member-access': 'error',
 45 |       '@typescript-eslint/no-unsafe-return': 'error',
 46 |       
 47 |       // Prevent specific anti-patterns we found
 48 |       '@typescript-eslint/ban-ts-comment': ['error', {
 49 |         'ts-expect-error': 'allow-with-description',
 50 |         'ts-ignore': true,
 51 |         'ts-nocheck': true,
 52 |         'ts-check': false,
 53 |       }],
 54 |       
 55 |       // Encourage best practices (warnings - can be gradually fixed)
 56 |       '@typescript-eslint/prefer-as-const': 'warn',
 57 |       '@typescript-eslint/prefer-nullish-coalescing': 'warn',
 58 |       '@typescript-eslint/prefer-optional-chain': 'warn',
 59 |       
 60 |       // Prevent barrel imports to maintain architectural improvements
 61 |       'no-restricted-imports': ['error', {
 62 |         patterns: [
 63 |           {
 64 |             group: ['**/utils/index.js', '../utils/index.js', '../../utils/index.js', '../../../utils/index.js', '**/utils/index.ts', '../utils/index.ts', '../../utils/index.ts', '../../../utils/index.ts'],
 65 |             message: 'Barrel imports from utils/index are prohibited. Use focused facade imports instead (e.g., utils/logging/index.ts, utils/execution/index.ts).'
 66 |           },
 67 |           {
 68 |             group: ['./**/*.js', '../**/*.js'],
 69 |             message: 'Import TypeScript files with .ts extension, not .js. This ensures compatibility with native TypeScript runtimes like Bun and Deno. Change .js to .ts in your import path.'
 70 |           }
 71 |         ]
 72 |       }],
 73 |     },
 74 |   },
 75 |   {
 76 |     // JavaScript and TypeScript files outside the main project (scripts/, etc.)
 77 |     files: ['**/*.{js,ts}'],
 78 |     ignores: ['src/**/*', '**/*.test.ts'],
 79 |     languageOptions: {
 80 |       ecmaVersion: 2020,
 81 |       sourceType: 'module',
 82 |       parser: tseslint.parser,
 83 |       // No project reference for scripts - use standalone parsing
 84 |     },
 85 |     plugins: {
 86 |       '@typescript-eslint': tseslint.plugin,
 87 |       'prettier': prettierPlugin,
 88 |     },
 89 |     rules: {
 90 |       'prettier/prettier': 'error',
 91 |       // Relaxed TypeScript rules for scripts since they're not in the main project
 92 |       '@typescript-eslint/explicit-function-return-type': 'off',
 93 |       '@typescript-eslint/no-explicit-any': 'warn',
 94 |       '@typescript-eslint/no-unused-vars': ['warn', { 
 95 |         argsIgnorePattern: 'never',
 96 |         varsIgnorePattern: 'never' 
 97 |       }],
 98 |       'no-console': 'off', // Scripts are allowed to use console
 99 |       
100 |       // Disable project-dependent rules for scripts
101 |       '@typescript-eslint/no-unsafe-argument': 'off',
102 |       '@typescript-eslint/no-unsafe-assignment': 'off',
103 |       '@typescript-eslint/no-unsafe-call': 'off',
104 |       '@typescript-eslint/no-unsafe-member-access': 'off',
105 |       '@typescript-eslint/no-unsafe-return': 'off',
106 |       '@typescript-eslint/prefer-nullish-coalescing': 'off',
107 |       '@typescript-eslint/prefer-optional-chain': 'off',
108 |     },
109 |   },
110 |   {
111 |     files: ['**/*.test.ts'],
112 |     languageOptions: {
113 |       parser: tseslint.parser,
114 |       parserOptions: {
115 |         project: './tsconfig.test.json',
116 |       },
117 |     },
118 |     rules: {
119 |       '@typescript-eslint/no-explicit-any': 'off',
120 |       '@typescript-eslint/no-unused-vars': 'off',
121 |       '@typescript-eslint/explicit-function-return-type': 'off',
122 |       'prefer-const': 'off',
123 |       
124 |       // Relax unsafe rules for tests - tests often need more flexibility
125 |       '@typescript-eslint/no-unsafe-argument': 'off',
126 |       '@typescript-eslint/no-unsafe-assignment': 'off',
127 |       '@typescript-eslint/no-unsafe-call': 'off',
128 |       '@typescript-eslint/no-unsafe-member-access': 'off',
129 |       '@typescript-eslint/no-unsafe-return': 'off',
130 |     },
131 |   },
132 | ];
133 | 
```

--------------------------------------------------------------------------------
/src/utils/axe-helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * AXe Helper Functions
  3 |  *
  4 |  * This utility module provides functions to resolve and execute AXe.
  5 |  * Prefers bundled AXe when present, but allows env and PATH fallback.
  6 |  */
  7 | 
  8 | import { accessSync, constants, existsSync } from 'fs';
  9 | import { dirname, join, resolve, delimiter } from 'path';
 10 | import { createTextResponse } from './validation.ts';
 11 | import { ToolResponse } from '../types/common.ts';
 12 | import type { CommandExecutor } from './execution/index.ts';
 13 | import { getDefaultCommandExecutor } from './execution/index.ts';
 14 | 
 15 | const AXE_PATH_ENV_VARS = ['XCODEBUILDMCP_AXE_PATH', 'AXE_PATH'] as const;
 16 | 
 17 | export type AxeBinarySource = 'env' | 'bundled' | 'path';
 18 | 
 19 | export type AxeBinary = {
 20 |   path: string;
 21 |   source: AxeBinarySource;
 22 | };
 23 | 
 24 | function getPackageRoot(): string {
 25 |   const entry = process.argv[1];
 26 |   if (entry) {
 27 |     const entryDir = dirname(entry);
 28 |     return dirname(entryDir);
 29 |   }
 30 |   return process.cwd();
 31 | }
 32 | 
 33 | // In the npm package, build/index.js is at the same level as bundled/
 34 | // So we go up one level from build/ to get to the package root
 35 | const bundledAxePath = join(getPackageRoot(), 'bundled', 'axe');
 36 | 
 37 | function isExecutable(path: string): boolean {
 38 |   try {
 39 |     accessSync(path, constants.X_OK);
 40 |     return true;
 41 |   } catch {
 42 |     return false;
 43 |   }
 44 | }
 45 | 
 46 | function resolveAxePathFromEnv(): string | null {
 47 |   for (const envVar of AXE_PATH_ENV_VARS) {
 48 |     const value = process.env[envVar];
 49 |     if (!value) continue;
 50 |     const resolved = resolve(value);
 51 |     if (isExecutable(resolved)) {
 52 |       return resolved;
 53 |     }
 54 |   }
 55 |   return null;
 56 | }
 57 | 
 58 | function resolveBundledAxePath(): string | null {
 59 |   const entry = process.argv[1];
 60 |   const candidates = new Set<string>();
 61 |   if (entry) {
 62 |     const entryDir = dirname(entry);
 63 |     candidates.add(join(dirname(entryDir), 'bundled', 'axe'));
 64 |     candidates.add(join(entryDir, 'bundled', 'axe'));
 65 |   }
 66 |   candidates.add(bundledAxePath);
 67 |   candidates.add(join(process.cwd(), 'bundled', 'axe'));
 68 | 
 69 |   for (const candidate of candidates) {
 70 |     if (existsSync(candidate)) {
 71 |       return candidate;
 72 |     }
 73 |   }
 74 |   return null;
 75 | }
 76 | 
 77 | function resolveAxePathFromPath(): string | null {
 78 |   const pathValue = process.env.PATH ?? '';
 79 |   const entries = pathValue.split(delimiter).filter(Boolean);
 80 |   for (const entry of entries) {
 81 |     const candidate = join(entry, 'axe');
 82 |     if (isExecutable(candidate)) {
 83 |       return candidate;
 84 |     }
 85 |   }
 86 |   return null;
 87 | }
 88 | 
 89 | export function resolveAxeBinary(): AxeBinary | null {
 90 |   const envPath = resolveAxePathFromEnv();
 91 |   if (envPath) {
 92 |     return { path: envPath, source: 'env' };
 93 |   }
 94 | 
 95 |   const bundledPath = resolveBundledAxePath();
 96 |   if (bundledPath) {
 97 |     return { path: bundledPath, source: 'bundled' };
 98 |   }
 99 | 
100 |   const pathBinary = resolveAxePathFromPath();
101 |   if (pathBinary) {
102 |     return { path: pathBinary, source: 'path' };
103 |   }
104 | 
105 |   return null;
106 | }
107 | 
108 | /**
109 |  * Get the path to the available axe binary
110 |  */
111 | export function getAxePath(): string | null {
112 |   return resolveAxeBinary()?.path ?? null;
113 | }
114 | 
115 | /**
116 |  * Get environment variables needed for bundled AXe to run
117 |  */
118 | export function getBundledAxeEnvironment(): Record<string, string> {
119 |   // No special environment variables needed - bundled AXe binary
120 |   // has proper @rpath configuration to find frameworks
121 |   return {};
122 | }
123 | 
124 | /**
125 |  * Check if axe tool is available (bundled, env override, or PATH)
126 |  */
127 | export function areAxeToolsAvailable(): boolean {
128 |   return getAxePath() !== null;
129 | }
130 | 
131 | export function createAxeNotAvailableResponse(): ToolResponse {
132 |   return createTextResponse(
133 |     'AXe tool not found. UI automation features are not available.\n\n' +
134 |       'Install AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\n' +
135 |       'If you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
136 |     true,
137 |   );
138 | }
139 | 
140 | /**
141 |  * Compare two semver strings a and b.
142 |  * Returns 1 if a > b, -1 if a < b, 0 if equal.
143 |  */
144 | function compareSemver(a: string, b: string): number {
145 |   const pa = a.split('.').map((n) => parseInt(n, 10));
146 |   const pb = b.split('.').map((n) => parseInt(n, 10));
147 |   const len = Math.max(pa.length, pb.length);
148 |   for (let i = 0; i < len; i++) {
149 |     const da = Number.isFinite(pa[i]) ? pa[i] : 0;
150 |     const db = Number.isFinite(pb[i]) ? pb[i] : 0;
151 |     if (da > db) return 1;
152 |     if (da < db) return -1;
153 |   }
154 |   return 0;
155 | }
156 | 
157 | /**
158 |  * Determine whether the bundled AXe meets a minimum version requirement.
159 |  * Runs `axe --version` and parses a semantic version (e.g., "1.1.0").
160 |  * If AXe is missing or the version cannot be parsed, returns false.
161 |  */
162 | export async function isAxeAtLeastVersion(
163 |   required: string,
164 |   executor?: CommandExecutor,
165 | ): Promise<boolean> {
166 |   const axePath = getAxePath();
167 |   if (!axePath) return false;
168 | 
169 |   const exec = executor ?? getDefaultCommandExecutor();
170 |   try {
171 |     const res = await exec([axePath, '--version'], 'AXe Version', true);
172 |     if (!res.success) return false;
173 | 
174 |     const output = res.output ?? '';
175 |     const versionMatch = output.match(/(\d+\.\d+\.\d+)/);
176 |     if (!versionMatch) return false;
177 | 
178 |     const current = versionMatch[1];
179 |     return compareSemver(current, required) >= 0;
180 |   } catch {
181 |     return false;
182 |   }
183 | }
184 | 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Logger Utility - Simple logging implementation for the application
  3 |  *
  4 |  * This utility module provides a lightweight logging system that directs log
  5 |  * messages to stderr rather than stdout, ensuring they don't interfere with
  6 |  * the MCP protocol communication which uses stdout.
  7 |  *
  8 |  * Responsibilities:
  9 |  * - Formatting log messages with timestamps and level indicators
 10 |  * - Directing all logs to stderr to avoid MCP protocol interference
 11 |  * - Supporting different log levels (info, warning, error, debug)
 12 |  * - Providing a simple, consistent logging interface throughout the application
 13 |  * - Sending error-level logs to Sentry for monitoring and alerting
 14 |  *
 15 |  * While intentionally minimal, this logger provides the essential functionality
 16 |  * needed for operational monitoring and debugging throughout the application.
 17 |  * It's used by virtually all other modules for status reporting and error logging.
 18 |  */
 19 | 
 20 | import { createRequire } from 'node:module';
 21 | import { resolve } from 'node:path';
 22 | // Note: Removed "import * as Sentry from '@sentry/node'" to prevent native module loading at import time
 23 | 
 24 | const SENTRY_ENABLED =
 25 |   process.env.SENTRY_DISABLED !== 'true' && process.env.XCODEBUILDMCP_SENTRY_DISABLED !== 'true';
 26 | 
 27 | // Log levels in order of severity (lower number = more severe)
 28 | const LOG_LEVELS = {
 29 |   emergency: 0,
 30 |   alert: 1,
 31 |   critical: 2,
 32 |   error: 3,
 33 |   warning: 4,
 34 |   notice: 5,
 35 |   info: 6,
 36 |   debug: 7,
 37 | } as const;
 38 | 
 39 | export type LogLevel = keyof typeof LOG_LEVELS;
 40 | 
 41 | /**
 42 |  * Optional context for logging to control Sentry capture
 43 |  */
 44 | export interface LogContext {
 45 |   sentry?: boolean;
 46 | }
 47 | 
 48 | // Client-requested log level (null means no filtering)
 49 | let clientLogLevel: LogLevel | null = null;
 50 | 
 51 | function isTestEnv(): boolean {
 52 |   return (
 53 |     process.env.VITEST === 'true' ||
 54 |     process.env.NODE_ENV === 'test' ||
 55 |     process.env.XCODEBUILDMCP_SILENCE_LOGS === 'true'
 56 |   );
 57 | }
 58 | 
 59 | type SentryModule = typeof import('@sentry/node');
 60 | 
 61 | const require = createRequire(
 62 |   typeof __filename === 'string' ? __filename : resolve(process.cwd(), 'package.json'),
 63 | );
 64 | let cachedSentry: SentryModule | null = null;
 65 | 
 66 | function loadSentrySync(): SentryModule | null {
 67 |   if (!SENTRY_ENABLED || isTestEnv()) return null;
 68 |   if (cachedSentry) return cachedSentry;
 69 |   try {
 70 |     cachedSentry = require('@sentry/node') as SentryModule;
 71 |     return cachedSentry;
 72 |   } catch {
 73 |     // If @sentry/node is not installed in some environments, fail silently.
 74 |     return null;
 75 |   }
 76 | }
 77 | 
 78 | function withSentry(cb: (s: SentryModule) => void): void {
 79 |   const s = loadSentrySync();
 80 |   if (!s) return;
 81 |   try {
 82 |     cb(s);
 83 |   } catch {
 84 |     // no-op: avoid throwing inside logger
 85 |   }
 86 | }
 87 | 
 88 | if (!SENTRY_ENABLED) {
 89 |   if (process.env.SENTRY_DISABLED === 'true') {
 90 |     log('info', 'Sentry disabled due to SENTRY_DISABLED environment variable');
 91 |   } else if (process.env.XCODEBUILDMCP_SENTRY_DISABLED === 'true') {
 92 |     log('info', 'Sentry disabled due to XCODEBUILDMCP_SENTRY_DISABLED environment variable');
 93 |   }
 94 | }
 95 | 
 96 | /**
 97 |  * Set the minimum log level for client-requested filtering
 98 |  * @param level The minimum log level to output
 99 |  */
100 | export function setLogLevel(level: LogLevel): void {
101 |   clientLogLevel = level;
102 |   log('debug', `Log level set to: ${level}`);
103 | }
104 | 
105 | /**
106 |  * Get the current client-requested log level
107 |  * @returns The current log level or null if no filtering is active
108 |  */
109 | export function getLogLevel(): LogLevel | null {
110 |   return clientLogLevel;
111 | }
112 | 
113 | /**
114 |  * Check if a log level should be output based on client settings
115 |  * @param level The log level to check
116 |  * @returns true if the message should be logged
117 |  */
118 | function shouldLog(level: string): boolean {
119 |   // Suppress logging during tests to keep test output clean
120 |   if (isTestEnv()) {
121 |     return false;
122 |   }
123 | 
124 |   // If no client level set, log everything
125 |   if (clientLogLevel === null) {
126 |     return true;
127 |   }
128 | 
129 |   // Check if the level is valid
130 |   const levelKey = level.toLowerCase() as LogLevel;
131 |   if (!(levelKey in LOG_LEVELS)) {
132 |     return true; // Log unknown levels
133 |   }
134 | 
135 |   // Only log if the message level is at or above the client's requested level
136 |   return LOG_LEVELS[levelKey] <= LOG_LEVELS[clientLogLevel];
137 | }
138 | 
139 | /**
140 |  * Log a message with the specified level
141 |  * @param level The log level (emergency, alert, critical, error, warning, notice, info, debug)
142 |  * @param message The message to log
143 |  * @param context Optional context to control Sentry capture and other behavior
144 |  */
145 | export function log(level: string, message: string, context?: LogContext): void {
146 |   // Check if we should log this level
147 |   if (!shouldLog(level)) {
148 |     return;
149 |   }
150 | 
151 |   const timestamp = new Date().toISOString();
152 |   const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
153 | 
154 |   // Default: error level goes to Sentry
155 |   // But respect explicit override from context
156 |   const captureToSentry = SENTRY_ENABLED && (context?.sentry ?? level === 'error');
157 | 
158 |   if (captureToSentry) {
159 |     withSentry((s) => s.captureMessage(logMessage));
160 |   }
161 | 
162 |   // It's important to use console.error here to ensure logs don't interfere with MCP protocol communication
163 |   // see https://modelcontextprotocol.io/docs/tools/debugging#server-side-logging
164 |   console.error(logMessage);
165 | }
166 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/list_schemes.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Project Discovery Plugin: List Schemes (Unified)
  3 |  *
  4 |  * Lists available schemes for either a project or workspace using xcodebuild.
  5 |  * Accepts mutually exclusive `projectPath` or `workspacePath`.
  6 |  */
  7 | 
  8 | import * as z from 'zod';
  9 | import { log } from '../../../utils/logging/index.ts';
 10 | import type { CommandExecutor } from '../../../utils/execution/index.ts';
 11 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
 12 | import { createTextResponse } from '../../../utils/responses/index.ts';
 13 | import { ToolResponse } from '../../../types/common.ts';
 14 | import {
 15 |   createSessionAwareTool,
 16 |   getSessionAwareToolSchemaShape,
 17 | } from '../../../utils/typed-tool-factory.ts';
 18 | import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
 19 | 
 20 | // Unified schema: XOR between projectPath and workspacePath
 21 | const baseSchemaObject = z.object({
 22 |   projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
 23 |   workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
 24 | });
 25 | 
 26 | const listSchemesSchema = z.preprocess(
 27 |   nullifyEmptyStrings,
 28 |   baseSchemaObject
 29 |     .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
 30 |       message: 'Either projectPath or workspacePath is required.',
 31 |     })
 32 |     .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
 33 |       message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
 34 |     }),
 35 | );
 36 | 
 37 | export type ListSchemesParams = z.infer<typeof listSchemesSchema>;
 38 | 
 39 | const createTextBlock = (text: string) => ({ type: 'text', text }) as const;
 40 | 
 41 | /**
 42 |  * Business logic for listing schemes in a project or workspace.
 43 |  * Exported for direct testing and reuse.
 44 |  */
 45 | export async function listSchemesLogic(
 46 |   params: ListSchemesParams,
 47 |   executor: CommandExecutor,
 48 | ): Promise<ToolResponse> {
 49 |   log('info', 'Listing schemes');
 50 | 
 51 |   try {
 52 |     // For listing schemes, we can't use executeXcodeBuild directly since it's not a standard action
 53 |     // We need to create a custom command with -list flag
 54 |     const command = ['xcodebuild', '-list'];
 55 | 
 56 |     const hasProjectPath = typeof params.projectPath === 'string';
 57 |     const projectOrWorkspace = hasProjectPath ? 'project' : 'workspace';
 58 |     const path = hasProjectPath ? params.projectPath : params.workspacePath;
 59 | 
 60 |     if (hasProjectPath) {
 61 |       command.push('-project', params.projectPath!);
 62 |     } else {
 63 |       command.push('-workspace', params.workspacePath!);
 64 |     }
 65 | 
 66 |     const result = await executor(command, 'List Schemes', true);
 67 | 
 68 |     if (!result.success) {
 69 |       return createTextResponse(`Failed to list schemes: ${result.error}`, true);
 70 |     }
 71 | 
 72 |     // Extract schemes from the output
 73 |     const schemesMatch = result.output.match(/Schemes:([\s\S]*?)(?=\n\n|$)/);
 74 | 
 75 |     if (!schemesMatch) {
 76 |       return createTextResponse('No schemes found in the output', true);
 77 |     }
 78 | 
 79 |     const schemeLines = schemesMatch[1].trim().split('\n');
 80 |     const schemes = schemeLines.map((line) => line.trim()).filter((line) => line);
 81 | 
 82 |     // Prepare next steps with the first scheme if available
 83 |     let nextStepsText = '';
 84 |     let hintText = '';
 85 |     if (schemes.length > 0) {
 86 |       const firstScheme = schemes[0];
 87 | 
 88 |       // Note: After Phase 2, these will be unified tool names too
 89 |       nextStepsText = `Next Steps:
 90 | 1. Build the app: build_macos({ ${projectOrWorkspace}Path: "${path}", scheme: "${firstScheme}" })
 91 |    or for iOS: build_sim({ ${projectOrWorkspace}Path: "${path}", scheme: "${firstScheme}", simulatorName: "iPhone 16" })
 92 | 2. Show build settings: show_build_settings({ ${projectOrWorkspace}Path: "${path}", scheme: "${firstScheme}" })`;
 93 | 
 94 |       hintText =
 95 |         `Hint: Consider saving a default scheme with session-set-defaults ` +
 96 |         `{ scheme: "${firstScheme}" } to avoid repeating it.`;
 97 |     }
 98 | 
 99 |     const content = [
100 |       createTextBlock('✅ Available schemes:'),
101 |       createTextBlock(schemes.join('\n')),
102 |       createTextBlock(nextStepsText),
103 |     ];
104 |     if (hintText.length > 0) {
105 |       content.push(createTextBlock(hintText));
106 |     }
107 | 
108 |     return {
109 |       content,
110 |       isError: false,
111 |     };
112 |   } catch (error) {
113 |     const errorMessage = error instanceof Error ? error.message : String(error);
114 |     log('error', `Error listing schemes: ${errorMessage}`);
115 |     return createTextResponse(`Error listing schemes: ${errorMessage}`, true);
116 |   }
117 | }
118 | 
119 | const publicSchemaObject = baseSchemaObject.omit({
120 |   projectPath: true,
121 |   workspacePath: true,
122 | } as const);
123 | 
124 | export default {
125 |   name: 'list_schemes',
126 |   description: 'Lists schemes for a project or workspace.',
127 |   schema: getSessionAwareToolSchemaShape({
128 |     sessionAware: publicSchemaObject,
129 |     legacy: baseSchemaObject,
130 |   }),
131 |   annotations: {
132 |     title: 'List Schemes',
133 |     readOnlyHint: true,
134 |   },
135 |   handler: createSessionAwareTool<ListSchemesParams>({
136 |     internalSchema: listSchemesSchema as unknown as z.ZodType<ListSchemesParams, unknown>,
137 |     logicFunction: listSchemesLogic,
138 |     getExecutor: getDefaultCommandExecutor,
139 |     requirements: [
140 |       { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' },
141 |     ],
142 |     exclusivePairs: [['projectPath', 'workspacePath']],
143 |   }),
144 | };
145 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator/stop_app_sim.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from 'zod';
  2 | import { ToolResponse } from '../../../types/common.ts';
  3 | import { log } from '../../../utils/logging/index.ts';
  4 | import type { CommandExecutor } from '../../../utils/execution/index.ts';
  5 | import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
  6 | import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
  7 | import {
  8 |   createSessionAwareTool,
  9 |   getSessionAwareToolSchemaShape,
 10 | } from '../../../utils/typed-tool-factory.ts';
 11 | 
 12 | const baseSchemaObject = z.object({
 13 |   simulatorId: z
 14 |     .string()
 15 |     .optional()
 16 |     .describe(
 17 |       'UUID of the simulator to use (obtained from list_sims). Provide EITHER this OR simulatorName, not both',
 18 |     ),
 19 |   simulatorName: z
 20 |     .string()
 21 |     .optional()
 22 |     .describe(
 23 |       "Name of the simulator (e.g., 'iPhone 16'). Provide EITHER this OR simulatorId, not both",
 24 |     ),
 25 |   bundleId: z.string().describe("Bundle identifier of the app to stop (e.g., 'com.example.MyApp')"),
 26 | });
 27 | 
 28 | const stopAppSimSchema = z.preprocess(
 29 |   nullifyEmptyStrings,
 30 |   baseSchemaObject
 31 |     .refine((val) => val.simulatorId !== undefined || val.simulatorName !== undefined, {
 32 |       message: 'Either simulatorId or simulatorName is required.',
 33 |     })
 34 |     .refine((val) => !(val.simulatorId !== undefined && val.simulatorName !== undefined), {
 35 |       message: 'simulatorId and simulatorName are mutually exclusive. Provide only one.',
 36 |     }),
 37 | );
 38 | 
 39 | export type StopAppSimParams = z.infer<typeof stopAppSimSchema>;
 40 | 
 41 | export async function stop_app_simLogic(
 42 |   params: StopAppSimParams,
 43 |   executor: CommandExecutor,
 44 | ): Promise<ToolResponse> {
 45 |   let simulatorId = params.simulatorId;
 46 |   let simulatorDisplayName = simulatorId ?? '';
 47 | 
 48 |   if (params.simulatorName && !simulatorId) {
 49 |     log('info', `Looking up simulator by name: ${params.simulatorName}`);
 50 | 
 51 |     const simulatorListResult = await executor(
 52 |       ['xcrun', 'simctl', 'list', 'devices', 'available', '--json'],
 53 |       'List Simulators',
 54 |       true,
 55 |     );
 56 |     if (!simulatorListResult.success) {
 57 |       return {
 58 |         content: [
 59 |           {
 60 |             type: 'text',
 61 |             text: `Failed to list simulators: ${simulatorListResult.error}`,
 62 |           },
 63 |         ],
 64 |         isError: true,
 65 |       };
 66 |     }
 67 | 
 68 |     const simulatorsData = JSON.parse(simulatorListResult.output) as {
 69 |       devices: Record<string, Array<{ udid: string; name: string }>>;
 70 |     };
 71 | 
 72 |     let foundSimulator: { udid: string; name: string } | null = null;
 73 |     for (const runtime in simulatorsData.devices) {
 74 |       const devices = simulatorsData.devices[runtime];
 75 |       const simulator = devices.find((device) => device.name === params.simulatorName);
 76 |       if (simulator) {
 77 |         foundSimulator = simulator;
 78 |         break;
 79 |       }
 80 |     }
 81 | 
 82 |     if (!foundSimulator) {
 83 |       return {
 84 |         content: [
 85 |           {
 86 |             type: 'text',
 87 |             text: `Simulator named "${params.simulatorName}" not found. Use list_sims to see available simulators.`,
 88 |           },
 89 |         ],
 90 |         isError: true,
 91 |       };
 92 |     }
 93 | 
 94 |     simulatorId = foundSimulator.udid;
 95 |     simulatorDisplayName = `"${params.simulatorName}" (${foundSimulator.udid})`;
 96 |   }
 97 | 
 98 |   if (!simulatorId) {
 99 |     return {
100 |       content: [
101 |         {
102 |           type: 'text',
103 |           text: 'No simulator identifier provided',
104 |         },
105 |       ],
106 |       isError: true,
107 |     };
108 |   }
109 | 
110 |   log('info', `Stopping app ${params.bundleId} in simulator ${simulatorId}`);
111 | 
112 |   try {
113 |     const command = ['xcrun', 'simctl', 'terminate', simulatorId, params.bundleId];
114 |     const result = await executor(command, 'Stop App in Simulator', true, undefined);
115 | 
116 |     if (!result.success) {
117 |       return {
118 |         content: [
119 |           {
120 |             type: 'text',
121 |             text: `Stop app in simulator operation failed: ${result.error}`,
122 |           },
123 |         ],
124 |         isError: true,
125 |       };
126 |     }
127 | 
128 |     return {
129 |       content: [
130 |         {
131 |           type: 'text',
132 |           text: `✅ App ${params.bundleId} stopped successfully in simulator ${simulatorDisplayName || simulatorId}`,
133 |         },
134 |       ],
135 |     };
136 |   } catch (error) {
137 |     const errorMessage = error instanceof Error ? error.message : String(error);
138 |     log('error', `Error stopping app in simulator: ${errorMessage}`);
139 |     return {
140 |       content: [
141 |         {
142 |           type: 'text',
143 |           text: `Stop app in simulator operation failed: ${errorMessage}`,
144 |         },
145 |       ],
146 |       isError: true,
147 |     };
148 |   }
149 | }
150 | 
151 | const publicSchemaObject = z.strictObject(
152 |   baseSchemaObject.omit({
153 |     simulatorId: true,
154 |     simulatorName: true,
155 |   } as const).shape,
156 | );
157 | 
158 | export default {
159 |   name: 'stop_app_sim',
160 |   description: 'Stops an app running in an iOS simulator.',
161 |   schema: getSessionAwareToolSchemaShape({
162 |     sessionAware: publicSchemaObject,
163 |     legacy: baseSchemaObject,
164 |   }),
165 |   annotations: {
166 |     title: 'Stop App Simulator',
167 |     destructiveHint: true,
168 |   },
169 |   handler: createSessionAwareTool<StopAppSimParams>({
170 |     internalSchema: stopAppSimSchema as unknown as z.ZodType<StopAppSimParams, unknown>,
171 |     logicFunction: stop_app_simLogic,
172 |     getExecutor: getDefaultCommandExecutor,
173 |     requirements: [
174 |       { oneOf: ['simulatorId', 'simulatorName'], message: 'Provide simulatorId or simulatorName' },
175 |     ],
176 |     exclusivePairs: [['simulatorId', 'simulatorName']],
177 |   }),
178 | };
179 | 
```
Page 3/16FirstPrevNextLast