feat(mcp): support protocol revision 2026-07-28 on SDK v2 - #508
Conversation
Adopt the official TypeScript SDK v2 packages (@modelcontextprotocol/server, @modelcontextprotocol/client) and serve MCP protocol revision 2026-07-28 alongside the 2025 revisions on the same stdio connection. Modern era: - No initialize handshake and no Mcp-Session-Id. The protocol revision and the client's declared capabilities arrive in the _meta envelope of every request. - server/discover advertises the supported modern revisions, capabilities and instructions. - Results carry resultType, and cacheable operations carry ttlMs/cacheScope via server-level cache hints (private scope, since every result depends on local workspace and Xcode state). - Direct requests are served without prior discovery. - Per-request io.modelcontextprotocol/logLevel replaces logging/setLevel. - Notifications are delivered over subscriptions/listen. Legacy era is unchanged: an initialize opening pins the connection to the 2025 handshake, keeps logging/setLevel, and never sees modern-only result members. Serving contexts now build a fresh server instance per connection (and per HTTP request for the fetch-shaped handler, which the SDK entry uses to enforce the MCP-Protocol-Version / Mcp-Method / Mcp-Name header contract). The expensive manifest, tool-module and resource-module resolution stays process level and is replayed cheaply onto each instance; application session state (session defaults, debugger sessions, log captures) is process scoped and survives instance replacement, including the discarded server/discover probe instance. Sentry MCP instrumentation still wraps every instance - the SDK v2 McpServer passes its structural check. Because the released integration derives protocol and client identity from the initialize handshake, modern-era identity is now published from the validated _meta envelope so no observability is lost. Adds lifecycle, modern discovery/direct, legacy, notification, protocol-helper and instrumentation test coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`npm run test:schema-fixtures` failed after the SDK v2 migration. Regenerated with `npm run test:schema-fixtures:update`; the whole 164-file diff is exactly two uniform, intentional changes and nothing else: - inputSchema `$schema` moves from draft-07 to `https://json-schema.org/draft/2020-12/schema`, which is the draft the v2 Zod-to-JSON-Schema conversion targets. - the always-constant `execution: {"taskSupport":"forbidden"}` member is no longer emitted, because v2 only publishes `execution` when a tool explicitly configures it. Both are client-visible, so they are called out in the changelog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two serving-layer leaks found in review of the 2026-07-28 work. Idle shutdown never fired once a modern client opened a subscription. The serving entry answers `subscriptions/listen` out of band - an acknowledgement notification now, the JSON-RPC result only at teardown - and a cancelled request receives no response at all, because the SDK drops the reply for an aborted handler. The transport observer counted both as ordinary in-flight work, so the count never returned to zero and the process stayed alive forever. Long-lived requests are now tracked separately and never counted as in-flight, and `notifications/cancelled` settles whichever kind of request it names. Ordinary request start/complete metrics are unchanged. HTTP serving contexts leaked a server instance and its tool registrations on every request. Teardown was hooked on `McpServer.close`, but `createMcpHandler` closes the low-level `server.server` per request and never touches the high-level wrapper, so `activeServers` and `registrationsByServer` grew without bound. Teardown is now wired to the low-level close lifecycle as well, and is idempotent so an unconnected instance closed through the high-level API is still released. Two consequences of the per-request model are fixed with it: - the process-level Xcode tools bridge is bound only by connection-scoped contexts, so an HTTP request can no longer move the proxied tools off the live stdio connection; - server instances carry their serving scope, so `getServer()` prefers the connection-scoped instance and a per-request instance can never shadow it. Adds a modern stdio e2e regression driving listen plus cancellation under XCODEBUILDMCP_MCP_IDLE_TIMEOUT_MS (verified failing before the fix), observer unit coverage for every settle path, HTTP sequential/concurrent/handler.close teardown assertions, and bridge-binding coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Migrates XcodeBuildMCP to the split MCP TypeScript SDK v2 packages and adds dual-era support for the MCP 2026-07-28 protocol revision (modern stateless _meta envelopes + headers) while keeping 2025-era legacy initialize clients working on the same serving entrypoints.
Changes:
- Introduces modern-era protocol helpers + request-lifecycle accounting for cancellations and long-lived
subscriptions/listenso idle shutdown doesn’t hang. - Refactors serving state to support multiple active SDK v2 server instances (connection-scoped stdio + request-scoped HTTP) and updates shutdown/lifecycle plumbing accordingly.
- Refreshes MCP contract fixtures and harness/test imports for SDK v2 + JSON Schema 2020-12 tool schema shape.
Reviewed changes
Copilot reviewed 218 out of 219 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/sentry.ts | Add protocol-era/client identity tags + serving-context metric helpers. |
| src/utils/tests/tool-registry.test.ts | Update server-state mock to new getActiveServers() API. |
| src/snapshot-tests/resource-harness.ts | Switch snapshot harness to SDK v2 client imports. |
| src/snapshot-tests/mcp-tool-contract-fixtures.ts | Update Tool type import for SDK v2. |
| src/snapshot-tests/mcp-harness.ts | Update v2 client imports and callTool signature usage. |
| src/snapshot-tests/json-schema-validation.ts | Update Tool type import for SDK v2. |
| src/snapshot-tests/tests/json-fixture-schema.test.ts | Update Tool type import for SDK v2. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/xcode_tools_bridge_sync.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/xcode_tools_bridge_status.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/xcode_tools_bridge_disconnect.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/xcode_ide_list_tools.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/xcode_ide_call_tool.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/wait_for_ui.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/type_text.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/touch.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/toggle_software_keyboard.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/toggle_connect_hardware_keyboard.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/test_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/test_macos.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/test_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/tap.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/sync_xcode_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swipe.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swift_package_test.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swift_package_stop.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swift_package_run.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swift_package_list.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swift_package_clean.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/swift_package_build.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/stop_mac_app.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/stop_app_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/stop_app_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/snapshot_ui.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/sim_statusbar.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/show_build_settings.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/set_sim_location.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/set_sim_appearance.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/session_use_defaults_profile.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/session_show_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/session_set_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/session_clear_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/screenshot.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/scaffold_macos_project.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/scaffold_ios_project.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/reset_sim_location.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/record_sim_video.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/open_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/manage-workflows.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/long_press.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/list_sims.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/list_schemes.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/list_devices.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/launch_mac_app.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/launch_app_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/launch_app_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/key_sequence.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/key_press.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/install_app_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/install_app_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_sim_app_path.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_mac_bundle_id.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_mac_app_path.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_file_coverage.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_device_app_path.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_coverage_report.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/get_app_bundle_id.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/gesture.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/erase_sims.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/drag.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/doctor.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/discover_projs.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_variables.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_stack.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_lldb_command.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_detach.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_continue.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_breakpoint_remove.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_breakpoint_add.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/debug_attach_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/clean.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/button.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/build_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/build_run_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/build_run_macos.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/build_run_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/build_macos.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/build_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/boot_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-enabled/batch.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/xcode_tools_bridge_sync.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/xcode_tools_bridge_status.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/xcode_tools_bridge_disconnect.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/xcode_ide_list_tools.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/xcode_ide_call_tool.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/wait_for_ui.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/type_text.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/touch.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/toggle_software_keyboard.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/toggle_connect_hardware_keyboard.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/test_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/test_macos.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/test_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/tap.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/sync_xcode_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swipe.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swift_package_test.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swift_package_stop.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swift_package_run.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swift_package_list.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swift_package_clean.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/swift_package_build.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/stop_mac_app.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/stop_app_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/stop_app_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/snapshot_ui.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/sim_statusbar.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/show_build_settings.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/set_sim_location.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/set_sim_appearance.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/session_use_defaults_profile.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/session_show_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/session_set_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/session_clear_defaults.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/screenshot.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/scaffold_macos_project.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/scaffold_ios_project.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/reset_sim_location.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/record_sim_video.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/open_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/manage-workflows.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/long_press.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/list_sims.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/list_schemes.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/list_devices.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/launch_mac_app.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/launch_app_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/launch_app_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/key_sequence.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/key_press.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/install_app_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/install_app_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_sim_app_path.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_mac_bundle_id.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_mac_app_path.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_file_coverage.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_device_app_path.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_coverage_report.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/get_app_bundle_id.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/gesture.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/erase_sims.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/drag.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/doctor.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/discover_projs.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_variables.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_stack.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_lldb_command.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_detach.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_continue.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_breakpoint_remove.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_breakpoint_add.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/debug_attach_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/clean.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/button.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/build_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/build_run_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/build_run_macos.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/build_run_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/build_macos.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/build_device.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/boot_sim.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/snapshot-tests/fixtures/mcp-contracts/session-defaults-disabled/batch.json | Fixture: JSON Schema 2020-12 + drop execution. |
| src/smoke-tests/mcp-test-harness.ts | Switch smoke harness to SDK v2 client imports. |
| src/smoke-tests/tests/e2e-mcp-idle-timeout.test.ts | Switch smoke test to SDK v2 client imports. |
| src/server/start-mcp-server.ts | Use stdio serving handle + runtime bootstrap split. |
| src/server/server-state.ts | Track multiple active servers + registration/unregistration API. |
| src/server/request-lifecycle.ts | Track cancellation + long-lived listen requests; observe modern envelopes. |
| src/server/mcp-shutdown.ts | Close via serving-handle abstraction (not raw server instance). |
| src/server/mcp-protocol.ts | New: modern protocol constants, header helpers, envelope reader, cache hints. |
| src/server/mcp-lifecycle.ts | Store serving handle in lifecycle coordinator (instead of server). |
| src/server/mcp-instrumentation.ts | New: publish modern _meta identity to Sentry + serving-context metrics. |
| src/server/bootstrap.ts | Split process bootstrap from per-serving-context registration replay. |
| src/server/tests/serving-test-fixtures.ts | New: minimal tool/resource plan for serving-layer tests. |
| src/server/tests/server.test.ts | Update transport/types imports; add lifecycle accounting tests. |
| src/server/tests/raw-mcp-peer.ts | New: raw JSON-RPC peer to test modern wire behavior. |
| src/server/tests/mcp-xcode-bridge-binding.test.ts | New: ensure bridge binding stays connection-scoped. |
| src/server/tests/mcp-shutdown.test.ts | Update shutdown tests to serving-handle close. |
| src/server/tests/mcp-protocol.test.ts | New: unit tests for header/envelope/cache-hint helpers. |
| src/server/tests/mcp-notifications.test.ts | New: validate list-changed notifications across eras. |
| src/server/tests/mcp-lifecycle.test.ts | Update assertions for serving handle lifecycle. |
| src/server/tests/mcp-legacy-protocol.test.ts | New: legacy-era handshake and compatibility assertions. |
| src/server/tests/mcp-instrumentation.test.ts | New: Sentry integration compatibility + modern identity publication. |
| src/runtime/types.ts | Update MCP ToolAnnotations type import for SDK v2. |
| src/integrations/xcode-tools-bridge/tool-service.ts | Update MCP types import for SDK v2. |
| src/integrations/xcode-tools-bridge/registry.ts | Support rebinding proxied tools onto new server instances. |
| src/integrations/xcode-tools-bridge/manager.ts | Add bindServer + rebind handling for per-serving server instances. |
| src/integrations/xcode-tools-bridge/index.ts | Update MCP server type import for SDK v2. |
| src/integrations/xcode-tools-bridge/core.ts | Update Tool type import for SDK v2. |
| src/integrations/xcode-tools-bridge/client.ts | Switch to SDK v2 client + schema helpers. |
| src/integrations/xcode-tools-bridge/bridge-tool-result.ts | Update CallToolResult type import for SDK v2. |
| src/integrations/xcode-tools-bridge/bridge-response-artifact.ts | Update CallToolResult type import for SDK v2. |
| src/integrations/xcode-tools-bridge/tests/registry.integration.test.ts | Update server/client imports for SDK v2. |
| src/integrations/xcode-tools-bridge/tests/manager.test.ts | Update MCP types import for SDK v2. |
| src/integrations/xcode-tools-bridge/tests/fixtures/fake-xcode-tools-server.mjs | Update stdio transport/server imports for SDK v2. |
| src/daemon/protocol.ts | Update ToolAnnotations type import for SDK v2. |
| src/daemon/daemon-server.ts | Update CallToolResult type import for SDK v2. |
| src/core/resources.ts | Split load vs register; attach cache hints for resources. |
| src/core/tests/resources.test.ts | Update mocked server API to registerResource + cacheHint. |
| src/cli/cli-tool-catalog.ts | Update ToolAnnotations type import for SDK v2. |
| scripts/probe-xcode-mcpbridge.ts | Update script to SDK v2 client + schema helper. |
| package.json | Replace @modelcontextprotocol/sdk with v2 client/server packages. |
| CHANGELOG.md | Document protocol support + SDK v2 migration + contract/fixture changes. |
| AGENTS.md | Update architecture description to reflect SDK v2 dual-era serving. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…dle on listen Both findings from the PR getsentry#508 review reproduce. `ServerRegistrations.toolPlan` captured `getToolRegistrationPlan()` once during bootstrap, and every serving context replayed that snapshot. `manage_workflows` rewrites the process-level plan at runtime and applies it to the servers that are already active, so the live context was correct, but any context built afterwards re-registered the boot-time workflows and silently undid the client's selection. It is now a resolver (`resolveToolPlan`) read at apply time, so each context registers the plan that is current when it is built. The Xcode tools bridge binding is untouched: per-request contexts still never take it, so this adds no rebind churn. Opening a `subscriptions/listen` stream was excluded from the lifecycle callbacks entirely. That kept it out of the in-flight count, which is required, but it also meant the interaction never refreshed the idle window, so a subscription opened near the deadline could be killed by the very next idle check. Long-lived requests now report activity exactly once on open through a new `onRequestActivity` observer hook and `McpIdleShutdownController.markActivity`, which moves the idle deadline without touching the in-flight count. Idle shutdown still runs once the client goes quiet. Regressions, all verified failing before the fix: - new HTTP context and stdio probe-to-pinned replacement after a plan change; - an idle-timeout e2e where the client only ever opens listen streams - it previously died mid-loop with "opening a subscription did not refresh the idle window"; - controller timing tests pinning the restarted window, the unchanged original deadline, and that activity cannot mask in-flight work; - observer tests for once-per-stream activity and no activity for ordinary requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit made tool registration follow the live workflow selection, but the Xcode tools bridge was still gated on `xcodeIdeEnabled`, a boolean captured during bootstrap. The two sources disagreed as soon as `manage_workflows` ran, in both directions: - xcode-ide enabled after startup: a later context registered the xcode-ide manifest tools from the current plan but never constructed, bound or enabled the bridge manager, so the proxied `xcode_tools_*` tools were missing; - xcode-ide disabled after startup: a later context still bound and re-enabled the bridge, undoing the client's disable. The boot-time flag is removed rather than refreshed, so there is no stale value left to read: `ServerRegistrations` now exposes only `resolveToolPlan`, and both the tool registrations and the bridge decision are derived from that one plan via `planEnablesXcodeIde`. Disable is now a real teardown. `setWorkflowEnabled(false)` drops the proxied registrations on the transition, so a context built after a disable cannot keep serving tools the client turned off, and a later re-enable syncs fresh instead of replaying a stale catalogue through `rebind`. A disabled context only ever peeks at the manager, so it never constructs one just to switch it off. HTTP ownership is unchanged and now explicit in both directions: a per-request context neither takes the binding nor disables a bridge the live stdio connection still owns. Regressions (enable-after-start and disable-after-start both verified failing before the fix): serving-context gating for stdio and HTTP, no-manager-creation on the disabled path, and manager-level coverage that disabling clears the proxied registrations, is idempotent, and stops listChanged-driven resyncs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 92b8abd. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 222 out of 223 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/utils/sentry.ts:680
clientCapabilitiesis optional but an empty array is truthy, so this currently recordsclientCapabilities: ''(empty string) into the Sentry context. That makes "no capabilities" indistinguishable from "capabilities missing" and adds noise to observability payloads. Consider only emitting the field when the array is non-empty.
…entry capabilities Three follow-ups from the review of 92b8abd. Re-enabling the workflow left `suppressListChangedSync` set by the preceding disable, so `onToolCatalogInvalidated` returned early forever and the proxied catalogue never refreshed again unless something happened to run a manual sync. Enabling now clears the suppression, and `setWorkflowEnabled` short-circuits when the state is unchanged so repeated calls cannot double-clear the registry. A disable racing an in-flight sync could resurrect the tools it had just torn down: `syncTools` awaits bridge availability and then the remote tool list, and nothing revalidated the workflow after either await, so a sync started before the disable still reached `registry.sync`. Every transition - enable, disable and manual disconnect - now advances a workflow epoch, the sync captures it and revalidates after each await, and an in-flight sync is only shared with callers from the same epoch. A stale completion returns a zeroed result and touches neither the registry nor the outbound notification. `clientCapabilities` is an array, and an empty array is truthy, so the Sentry `mcp.protocol` context published `clientCapabilities: ''` for every client that declares none - which is the common case on the modern era. The payload is now built by a pure `buildMcpProtocolContextPayload` that omits the member unless it has entries, which also makes the emission unit-testable given the surrounding function is a no-op under test. All seven new regressions verified failing before the fix: re-enable restores invalidation-driven syncing, repeated transitions keep working, disable during a delayed sync leaves zero tools registered (both awaits covered), a manual disconnect invalidates the same way, a later re-enable syncs the fresh catalogue, and empty capabilities are omitted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Also addressed Copilot’s suppressed Sentry observation in 63c7bed: empty client capability arrays are now omitted rather than recorded as an empty string. Added payload-level coverage. Validation: 2,683 tests, typecheck, lint, format, build, 333 schema fixtures, idle e2e, and targeted bridge/Sentry tests. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 223 out of 224 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/server/tests/serving-test-fixtures.ts:30
ctx.structuredOutput.schema/schemaVersionin this fixture don’t follow the structured output contract used elsewhere in the repo (e.g. snapshot fixtures useschema: "xcodebuildmcp.output.bundle-id"with integer-string versions like "1"/"2"). Keepingschema: 'bundle-id'andschemaVersion: '1.0.0'makes these serving-layer tests less representative and can mask regressions around schema naming/versioning.
…xtures
The serving-layer fixture emitted `schema: 'bundle-id'` with
`schemaVersion: '1.0.0'`. Neither matches the repo contract: real tools emit the
fully-qualified identifier and an integer-string version - for this kind,
`setBundleIdStructuredOutput` emits `xcodebuildmcp.output.bundle-id` at version
`2` - and every snapshot fixture agrees.
That mattered because these tests assert the MCP `structuredContent` envelope
end to end through a fresh SDK v2 server, so they were attesting to an envelope
the published contract rejects. The `2.schema.json` pins both members with
`const`, and validating the two envelopes against it confirms it:
before -> INVALID: /schema must be equal to constant,
/schemaVersion must be equal to constant
after -> VALID
The fixture now exports `PROBE_SCHEMA` / `PROBE_SCHEMA_VERSION` sourced from the
same `BUNDLE_ID_STRUCTURED_OUTPUT_SCHEMA` constant production uses, so the two
cannot drift again, and the one assertion that hard-coded the identifier now
checks the version too.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| function identityChanged(next: ObservedProtocolIdentity): boolean { | ||
| const previous = lastObservedIdentity; | ||
| return ( | ||
| previous?.protocolVersion !== next.protocolVersion || | ||
| previous.clientName !== next.clientName || | ||
| previous.clientVersion !== next.clientVersion || | ||
| previous.capabilityKeys !== next.capabilityKeys | ||
| ); | ||
| } |
There was a problem hiding this comment.
Confirmed as a false positive. When previous is null, previous?.protocolVersion !== next.protocolVersion is true because next.protocolVersion is a required string, so || short-circuits before any direct property access. readModernRequestEnvelope only produces this identity after validating the version is a string, and making it optional causes TypeScript to reject the function. Existing instrumentation tests reset the identity to null, drive the first modern request through the Sentry hooks, and pass without throwing.

Summary
This migrates XcodeBuildMCP to the split MCP TypeScript SDK v2 packages and adds dual-era support for the July 28, 2026 MCP specification.
Modern stdio connections now use stateless per-request metadata,
server/discover, required result/cache fields, andsubscriptions/listen. Legacy initialization-based clients continue to work through the sameserveStdioentry. Application session state remains process-scoped rather than protocol-session-scoped.The change also:
Validation
npm run typechecknpm run lintnpm run format:checknpm run buildnpx vitest run(2,656 tests)npm run test:schema-fixtures(333 tests)Device-dependent snapshot suites were not run locally.