Repository navigation
Description
Why
mcp-debugger speaks MCP protocol 2025-11-25 at most, because @modelcontextprotocol/sdk (the v1 line, latest 1.32.2) tops out there. The 2026-07-28 revision ships only in the v2 TypeScript SDK, released 2026-10-23 under new package names (@modelcontextprotocol/server, client, core, the node/express/hono/fastify transport packages, server-legacy for the frozen SSE transport), so Dependabot never offers it as a bump and nothing in the repo tracks it. Found while reviewing #1017 and building #1023.
The SDK's own upgrade guide separates the two moves, and so does this ticket: the API migration has a codemod and keeps the 2025 wire protocol; adopting 2026-07-28 is architectural. There is no deadline pressure — clients negotiate down, and the legacy era stays supported for at least twelve months under the spec's feature lifecycle policy — but the longer step 2 waits, the more code accrues on a session concept the spec has deleted.
Not before v0.26.0 ships. Target v0.27 or later; step 1 can be its own release.
Step 1 — SDK v2 on the 2025 wire protocol (mechanical, codemod-assisted)
Surface today: 16 files under src/ import the SDK (17 import sites from types.js, 5 from server/index.js, stdio/streamableHttp/sse server transports, and the client transports the dev proxy and self-debug paths use), 1 under tools/, 108 under tests/. No direct zod dependency; v2 needs zod ^4.2.0 as a peer (Zod 4.0–4.1 falls back with a warning and drops .describe() text).
npx @modelcontextprotocol/codemod@latest v1-to-v2 .at the repo root (it rewritespackage.jsontoo; in the monorepo it edits the nearest manifest only, so checkpackages/mcp-debuggerandtools/dev-proxyby hand). Then grep for@mcp-codemod-error, typecheck, lint, test.- Package mapping:
sdk/server/*→@modelcontextprotocol/server;sdk/client/*→@modelcontextprotocol/client;sdk/types.jsschemas →@modelcontextprotocol/core(shared types re-exported from client/server);StreamableHTTPServerTransport→NodeStreamableHTTPServerTransportfrom@modelcontextprotocol/node; stdio on the/stdiosubpaths. setRequestHandlertakes a method string, not a Zod schema, and the handler's second argument isctxnotextra(src/server/output-resources.ts,prompts.ts, the tool dispatch);registerResourceneeds ametadataargument;resources/readunknown-URI error becomes-32602.SSEServerTransportis removed from v2: the deprecatedssecommand moves to@modelcontextprotocol/server-legacy/sse(frozen v1 copy) or is dropped — decide, and say so in the changelog either way.createMcpHandlerdoes not validateHost/Origin: the existing allowlist middleware stays in front of it (it already runs beforehandleRequest).- Node 20+ (we require 22), ESM-first with a CJS build; the
packages/mcp-debuggerbundle andscripts/bundle.jsneed a re-check of what esbuild pulls in. - The dev proxy (
tools/dev-proxy) uses both a server and a client transport; the 1.x and 2.x SDKs share no classes, so it moves in the same PR.
Done when: tests/integration/cli/http-spec-contract.test.ts (the 2025-11-25 clauses, #1023) and the mcp-conformance CI lane are green unchanged; the stdio, http and container smokes pass; the published tarball still speaks 2025-11-25 to Claude Code, Codex, pi and MCP Inspector.
Step 2 — protocol 2026-07-28 (architectural)
What the revision changes, and what in this server sits on each change:
| 2026-07-28 change | SEP | What it touches here |
|---|---|---|
Protocol-level sessions and Mcp-Session-Id removed; cross-call state via server-minted handles passed as tool arguments |
SEP-2567 | the one-DebugMcpServer-per-client model (createHttpApp's session map), per-client isolation of debug sessions (#939, the foreign-session gate), the stale-session reaper (#337, #658), /health's session listing, the 404 answer (#1017). Debug session ids are already handles, so the model becomes one shared server with explicit ownership tokens, or no isolation — a design decision |
initialize handshake removed; _meta carries version and client capabilities; server/discover is mandatory |
SEP-2575 | DebugMcpServer capabilities/instructions, the mode-gated instructions (#271), list_supported_languages's per-handshake behaviour |
GET SSE stream and resources/subscribe/unsubscribe replaced by subscriptions/listen |
SEP-2575 | src/server/output-resources.ts (the output transcript subscriptions and their coalesced resources/updated), the dev proxy's stream handling, the stream-lost reaper path (#658) |
ping, logging/setLevel, roots list-changed removed; logging/sampling/roots deprecated |
SEP-2575, SEP-2577 | nothing declared today beyond ping (SDK-provided); confirm /health stays the liveness probe |
| HTTP+SSE transport formally Deprecated | SEP-2596 | the sse command (decided in step 1) |
Every result carries resultType; list/read results carry ttlMs/cacheScope; tools/list order must be deterministic |
SEP-2322, SEP-2549 | the tool result envelope (src/server/tool-result.ts), the 24 tool-list snapshots, check-docs counts |
Mcp-Method/Mcp-Name headers required on POST; x-mcp-header |
SEP-2243 | the allowlist/CORS middleware's allowed and exposed headers |
SSE resumability and Last-Event-ID removed |
SEP-2575 | CORS exposed headers; the expose_session mirror is DAP, unaffected |
Error-code repartition (-32020…-32099 reserved for the spec) |
— | the bare -32001 Session not found from #1017 and the -32000 allowlist answers are grandfathered implementation-defined; nothing to renumber unless the SDK's own bodies change |
Serve both eras during the window: v2's createMcpHandler(factory, { legacy: 'stateless' }) answers 2025-era requests per request and serveStdio(factory) pins a stdio connection's era on its opening exchange; legacy: 'reject' turns the old era off later. A modern-era client needs a second contract file (http-spec-contract-2026.test.ts or a table keyed by era) — the 2025 file stays as the legacy pin until the era is retired.
Done when: a 2026-07-28 client (the conformance CLI's 0.2 line with --spec-version 2026-07-28, once it is stable) and a 2025-11-25 client both drive a full debug session over http and stdio; the Docker image, the npx bundle and the pi package pass their smokes; docs name which era each transport speaks.
Sources
- https://modelcontextprotocol.io/specification/2026-07-28/changelog
- https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2.md
- https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/protocol-versions.md
- https://github.com/modelcontextprotocol/conformance (0.1.16 stable = 2025-11-25; 2026-07-28 scenarios in the 0.2 alphas)
- Related: fix(http): answer an unknown Mcp-Session-Id with 404 so the client re-initializes #1017, MCP spec conformance: a seam contract test for the Streamable HTTP clauses this server decides, plus the official conformance suite as a CI lane #1023, http: a debug session owned by another MCP client session answers a plain Session not found; say that sessions are per client #939, [BUG] Abandoned attach session can leave an orphaned lldb-server tracing the target (invisible to list_debug_sessions) #337, http: a client that drops without DELETE leaves its attach session holding the target paused for the 30-minute stale window, invisible to every other client #658
Activity
debugmcpdev commentedon Nov 2, 2026
One sequencing question for the 1.0 criteria, raised in the 2026-11-02 backlog review: step 2 removes protocol-level sessions, and the body already names the fork — "one shared server with explicit ownership tokens, or no isolation". If ownership tokens become tool arguments, that is a tool-schema change. The ROADMAP freezes the schema at 1.0, so either 1.0 waits for step 2, or step 2 is designed so ownership never surfaces as a tool parameter (handles minted per client inside the server, the way debug session ids already are). Worth deciding before the freeze is declared; nothing changes in the plan here.
debugmcpdev commentedon Nov 3, 2026
Decided 2026-11-03, answering the 2026-11-02 question above: the handle is the capability. Debug-session ownership stays server-side and never surfaces as a tool parameter.
Why this and not the alternatives:
expose_sessionalready treats a token as the capability._meta.clientInfopartition is self-asserted and non-unique: two instances of the same client share it.Consequences for step 2: one shared server per process in the modern era. The per-client
DebugMcpServerand the #939 foreign-session gate are legacy-era behaviour, not a contract. Two guardrails, written once in the session-tree design doc (#1005 part B, the ownership & lifetime section) and reused here:list_debug_sessionsis the whole process's list; the sessionnameis the agent's own namespace hint. Several agents against onehttpprocess are mutually visible and closable — the documented answer is one server per agent, as stdio already gives, not a pretence of isolation.wait_for_stopcounts), beside explicit close and debuggee exit.Forward-compatible: if real auth ever arrives, the authenticated principal becomes the owner key with no schema change.
So 1.0 does not wait for step 2. Step 1 (the v2 SDK API on the 2025-11-25 wire protocol) starts now as one mechanical PR; the
ssecommand stays in it on@modelcontextprotocol/server-legacy/sse, with its removal as a separate follow-up PR. Step 2 stays gated on a client we serve negotiating 2026-07-28, or on the conformance CLI's 0.2 line going stable. Step 1 adds one deliberate thing beyond the rename: the negotiated protocol version is logged per client, so that trigger is measurable from server logs.deps: move to the v2 MCP TypeScript SDK on the 2025-11-25 wire protoc…
debugmcpdev commentedon Nov 3, 2026
Step 1 is on
main: #1079 merged as 69ac4d8 (the v2 TypeScript SDK on the 2025-11-25 wire protocol,ssekept on@modelcontextprotocol/server-legacy/ssefor now). Every CI lane passed on the PR, the conformance suite andhttp-spec-contractwere unchanged, and the bundles grew 10.6–11.7 % (hono, the v2 AJV validator, the legacy SSE package — the last goes with #1078, which is in progress).Step 2 (protocol 2026-07-28) stays gated on a client we serve negotiating it. The server now logs
[MCP Server] Client initializedwith the negotiatedprotocolVersionand the client name/version on everyinitialize, so the trigger is measurable from real traffic rather than guessed. The ownership decision above (the handle is the capability) is written into the session-tree design doc's Ownership and lifetime section, which is the input step 2 takes when it starts.debugmcpdev commentedon Nov 3, 2026
The
ssefollow-up (#1078) is up as #1084: the legacy package is gone from the dependency list and the Docker bundle sheds ~99 KB (2,066,956 → 1,968,289), the npx bundle ~209 KB (4,414,619 → 4,205,542). With it merged, the SDK v2 step 1 surface is@modelcontextprotocol/server,node,clientandcoreonly.docs: the session-tree design — a debuggee's children as debug sessio…
debugmcpdev commentedon Nov 7, 2026
Status 2026-11-07: the step-2 gate was mis-instrumented on our side, it has been met for a month, and step 2 starts now.
The 2026-11-03 comment above gated step 2 on "a client we serve negotiating 2026-07-28, measured from the
Client initializedlog line". That measurement could never succeed, for three reasons found today:initializehandshake, which a modern-era client only runs as its fallback.Serverregisters theserver/discoverhandler only when its version list holds a modern revision, and only the SDK's serving entries (createMcpHandler,serveStdio) install one. We connect the transports directly, so no mcp-debugger server can answer the probe: over stdio it is a method-not-found; overhttpit lands in our own 400 "missing Mcp-Session-Id" branch (a discover request has no session id and is not an initialize) before the SDK sees it.versionNegotiationdefault is'legacy'; only a client opting into'auto'or a pin ever probes.What the clients do (checked 2026-11-07):
MCP_SDK_GENERATION,MCP_PROTOCOL_NEGOTIATIONserver/discover; may remember a stdio server's answer for 7 daysprotocolEra: legacyfor every mcp-debugger entry on a dev box since 2026-10-09, and "remembered as a legacy peer from an earlier connect (its server/discover probe was answered method-not-found or not at all); skipping the probe"rmcp-client,protocol_mode.rs)mcp_2026_07_28, UnderDevelopment, default offinitializeonlyMeasured against this server (v2 client 2.4.0,
versionNegotiation.mode):'auto'lands on 2025-11-25 over stdio,httpand the dev proxy, with no error and no respawn;{ pin: '2026-07-28' }fails cleanly withERA_NEGOTIATION_FAILEDon all three; a rawserver/discoverPOST to/mcpanswers HTTP 400 and leaves oneRejecting MCP request: missing session IDwarning in the server log per probing connect. Over stdio the probe leaves no trace at all, which is why the gate looked unmet from our side.Support windows, from the sources: the spec's versioning page lists 2026-07-28 as the current revision and "2025-11-25 and earlier" as legacy; the TypeScript SDK README says v1 "continues to receive bug fixes and security updates for at least six months after the v2 release (2026-07-27)", with no fixed date; no window is published for the legacy wire era (the twelve-month policy in the changelog is the feature lifecycle). Conformance:
latestis still 0.1.16; the 2026-07-28 scenarios exist only in0.2.0-alpha.12.Decision: nothing external gates step 2, and the lead harness asks for it on every connect, so it starts now as a planned multi-PR change rather than on a trigger. The shape is the SDK's own dual-era pattern: the existing sessionful app keeps serving the requests
isLegacyRequestclassifies as 2025-era; a strict modern handler,createMcpHandler(factory, { legacy: 'reject' })behindtoNodeHandler, serves the rest;serveStdio(factory)on stdio; one factory for both legs. The factory runs per request and a connectedServercannot reconnect, so the core of the work is a per-requestServerfacade over one shared SessionManager, which is the "one shared server per process" decision above and the session-tree doc's ownership section. The plan follows on this issue.refactor(server): pin the facade's sink detach through the real SDK c…
fix(http): count a finished wait as activity, close a transport whose…
fix(stdio): bound the serving handle's close and hear the wire closin…
fix(conformance): reconcile the 2026 lane's summary with checks.json,…
fix(dev-proxy): track the backend's resources for a 2026 client, seri…
fix(session): nothing expires unless the operator asks, an expired se…
fix(stdio): name a 2026-07-28 client in the log, and word the exit li…
fix(conformance): let the suite judge its own baseline, fix the diver…
fix(dev-proxy): negotiate with a stdio backend in place, survive a ba…
fix(session): a stray session id no longer hides a session from the l…
fix(conformance): excuse one check per baseline entry, never a whole …
fix(dev-proxy): end a backend start that is still negotiating when th…
docs(dev-proxy): the ignored abort is the SDK's auto negotiation, on …
Merge pull request #1114 from debugmcp/refactor/1025-protocol-facade
Merge pull request #1115 from debugmcp/feat/1025-shared-core
Merge pull request #1116 from debugmcp/feat/1025-stdio-2026
Merge pull request #1117 from debugmcp/feat/1025-http-2026
Merge pull request #1118 from debugmcp/feat/1025-dev-proxy-eras
debugmcpdev commentedon Nov 11, 2026
Status 2026-11-11: both steps are on
main; the issue closes with the 0.27.0 release.Step 2 landed as five PRs — #1114 (the SDK
Serverbecomes a per-connection facade over one core), #1115 (one core perhttpprocess, the owner-key seam, the opt-in idle policy), #1116 (both eras onstdio), #1117 (both eras on/mcp, the 2026-07-28 conformance lane), #1118 (the dev proxy on both eras) — and #1126 closes out what was left.Against this issue's "done when":
stdioandhttpstdio-era-negotiation,http-era-negotiation, the twohttp-spec-contractsuites, the http e2e smoke--requirements 2026-07-28set runs in CI, judged by the suite against a 31-check baseline in which no entry is a divergence of oursprotocolEra: modern,negotiatedProtocolVersion: 2026-07-28for all fifteen proxy entries on this machineSo what is left is the 0.27.0 release itself, and its canary and
/testpublishedpasses in both eras. This issue closes with that evidence.Follow-ups filed from the work: #1120 (authenticated
httpmode — the seam is built, the mode is not), #1121 (move the facade from the deprecated low-levelServertoMcpServer), #1122 (progress notifications), #1123 (isErroron failed calls), #1124 (two example scripts that post a non-MCP request), #1125 (CI launches no Java or .NET session). Upstream: our use cases are on modelcontextprotocol/typescript-sdk#2827 (a server-side hook forsubscriptions/listen) and modelcontextprotocol/conformance#516 (scenarios that fail a server for capabilities it never declared); the ignoredconnect()signal the proxy works around is modelcontextprotocol/typescript-sdk#3133.fix: complete dual-era artifact coverage and idle-policy reporting (#…