Harden XcodeBazelMCP: validation, sim/device tools, and MCP PATH fixes#13
Conversation
… tools A reliability/consistency pass across the MCP tools and CLI, plus new simulator/device tools (tool count 120 -> 125). All changes verified against a real Bazel iOS workspace. Highlights: - Fix combined stdout/stderr JSON parsing (simctl list, swift dump-package, idb accessibility snapshot) - Host-arch-aware iOS simulator CPU (BAZEL_IOS_SIMULATOR_CPU); quote-aware BAZEL_IOS_STARTUP_ARGS; env-overridable discover scope - LLDB: per-command output buffer (fixes >500KB deadlock), newline/quote injection guards, session cleanup on shutdown - Streaming tools return structured content; Bazel exit-code annotations - Scaffold: bundleId/minimumOs/rulesVersion validation, families & bazelVersion options, iOS Info.plist keys, absolute config path - New tools: bazel_ios_uninstall_app, bazel_ios_device_uninstall_app, bazel_ios_device_list_apps, bazel_ios_add_media, bazel_ios_get_app_container - Doctor reports idb/pymobiledevice3/maxOutput; README env-var section - Runtime smoke script + CI smoke step; +45 tests (575 -> 619 passing) Note: also snapshots in-progress xcode-native-mcp, command-log, and diagnostics modules already present in the working tree. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve bare `bazel` via augmented PATH when Cursor spawns MCP with a minimal environment, and walk up from cwd/env when the configured workspace is not a Bazel root. Generated-by: Cursor Co-authored-by: Cursor <cursoragent@cursor.com>
When BITRISE_BUILD_CACHE_AUTH_TOKEN is unset, drop --config=bitrise and --config=remote_linux so local Bazel builds/tests proceed instead of failing credential helper auth. Generated-by: Cursor Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve conflicts keeping Bitrise auth stripping on top of main's hardening (PR #12). Fix duplicate expandTilde/resolvePath in config.ts. Generated-by: Cursor Co-authored-by: Cursor <cursoragent@cursor.com>
Reframes the Bitrise token messaging so agents/users don't treat RBE as the expected path: README, mcp-setup skill, and the stderr notice now lead with "local execution is the default" and demote the token to an opt-in for RBE offload. bazel_ios_health also surfaces the active execution mode so it's visible in diagnostics. Co-authored-by: oh-my-pi <https://omp.sh>
Caught by `npm run lint`; the module uses accessSync via isExecutable, so the existsSync binding was dead. Keeps the PR's validation checklist green. Co-authored-by: oh-my-pi <https://omp.sh>
Follow-up: messaging + validation gaps from the PR descriptionReviewed the unchecked items in the Validation section of the PR description and closed the ones runnable locally. Done in this push
Validation now green locally
PR description checkboxes updated accordingly. Still unchecked (require hardware / manual steps outside this session)
These need a booted simulator / connected device and are out of scope for a code+docs push. Happy to run them next if someone points me at a Bazel iOS target + simulator. |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Simulator validation done ✅Ran the XcodeBazelMCP simulator workflow end-to-end against the BazelApp example project on a booted iPhone 17 (iOS 26.3) simulator.
|
| Step | Result |
|---|---|
bazel_ios_build_and_run //app:app |
✅ built (11.9s), installed, launched (PID 88212) |
bazel_ios_stop_app com.example.SwiftUIApp |
✅ terminated |
| simctl screenshot | ✅ captured (754 KB, 1206×2622) |
Unit tests — bazel_ios_test
| Target | Result |
|---|---|
//modules/Models:ModelsTests |
✅ PASSED via MCP (13.3s) |
//modules/API:APITests |
✅ PASSED via bazel (68.0s; MCP 30s request cap too short for cold builds — see note) |
Bitrise-config stripping verified
Calling bazel_ios_test with extraArgs: ["--config=bitrise"] and no BITRISE_BUILD_CACHE_AUTH_TOKEN produced a command with --config=bitrise stripped — ran locally instead of failing Bitrise auth. Confirms stripBitriseAuthFlags.
Note on the ~/Projects/ios DoorDash repo
Bare bazel test there fails with No repository visible as '@build_bazel_rules_swift' — a pre-existing Bzlmod repo-mapping issue in that repo's .bazelrc/MODULE.bazel (it references --@rules_swift//swift:copt but resolves to @build_bazel_rules_swift). Reproduces with plain bazel, unrelated to this PR. Used BazelApp for the buildable target instead.
Remaining unchecked (need hardware / separate steps)
- Manual CLI validation with
xcodebazelmcp … - Physical device validation (devices are connected — happy to run next)
Summary
spawn bazel ENOENTwhen Cursor launches MCP with a minimal PATH by resolving Bazel via Homebrew/common bin dirs.cwd/env when the configured workspace is invalid.bazel_ios_health.BITRISE_BUILD_CACHE_AUTH_TOKENis unset, automatically strips--config=bitrise/--config=remote_linuxand runs locally instead of failing Bitrise credential auth. Applies to both MCP and CLI build/test paths.Scope
Validation
npm run lintnpm run typechecknpm test(628 passed)npm run buildnpx vitest --run --coverage(59.52% stmts / 74.62% branches)xcodebazelmcp ...Compatibility
Notes For Reviewers
env -i PATH=/usr/bin:/bin):xcodebazelmcp doctorresolves bazel to/opt/homebrew/bin/bazeland passes againstexample_projects/BazelApp(workspace + MODULE.bazel + bazel 7.6.0 ✅).PATHandBAZEL_IOS_WORKSPACE(see updated.cursor/mcp.jsonexample).xcodebazelmcp build //modules/Models:ModelsLib --config bitrisewith no token runsbazel build //modules/Models:ModelsLib(config stripped, build ✅). Negative control withBITRISE_BUILD_CACHE_AUTH_TOKENset keeps--config=bitrisein the argv as expected.doctor(minimal PATH),tools,show-defaults,set-defaults,devicesall succeed.xcodebazelmcp devicesenumerates a connected iPhone 13 Pro (iOS 26.5.2, paired).xcodebazelmcp build //app:app --devicecorrectly emits--ios_multi_cpus+--platformsfor arm64 and reaches Bazel analysis; it fails only on the BazelApp example's missingprovisioning_profile(sample-app limitation, not an MCP defect).src/core/bazel.tsand is shared by both MCP and CLI build/test paths, so CLI behavior transitively benefits. No CLI flags, output format, or exit codes changed.BITRISE_BUILD_CACHE_AUTH_TOKENonly when RBE offload is intentionally desired.Made with Cursor