MCP TypeScript SDK v2 split packages¶
Status: accepted for #4678 (step 1 of 3: dependency swap and handler rewrite).
Dependency: @modelcontextprotocol/server
Dependency: @modelcontextprotocol/client
Dependency: @modelcontextprotocol/node
Decision¶
The monolithic @modelcontextprotocol/sdk (v1, protocol 2025-11-25) is replaced by the SDK
v2 split packages, which are the official successors published from the same repository
(github.com/modelcontextprotocol/typescript-sdk, same npm maintainers):
@modelcontextprotocol/server2.3.1:McpServer,Server, the stdio server transport,ProtocolError, the spec types.@modelcontextprotocol/client2.3.1:Client, the Streamable HTTP and stdio client transports used by the daemon’s loopback forwarder and the harness scripts.@modelcontextprotocol/node2.1.1:NodeStreamableHTTPServerTransport, thenode:httpadapter for the daemon’s internal loopback MCP endpoint. v2 moved the Node Streamable HTTP server transport out of the server package; this is the official replacement for the v1server/streamableHttp.jsimport.
All three are build-time (devDependencies) and inlined into dist/ by the build, as the v1
SDK was, so the published runtime graph (scripts/release/runtime-graph.json) is unchanged.
zod-to-json-schema moves from a transitive dependency of the v1 SDK to a direct
devDependency at the same locked version (3.25.1). The v1 SDK’s toJsonSchemaCompat used it
to advertise the Zod 3 prototype spec schema; v2 removed that helper, and calling the
converter directly with the same options keeps schemas/tool-definitions.json unchanged.
Alternatives checked¶
| Alternative | Why not |
|---|---|
Stay on @modelcontextprotocol/sdk 1.x |
It only speaks protocol 2025-11-25; the Tasks extension (#4668) needs the 2026-07-28 surface that only v2 ships. The SDK team describes the monolith as retired in favour of the split packages. |
WebStandardStreamableHTTPServerTransport from the server package plus a hand-written node:http to Request/Response bridge |
Re-implements what the official Node adapter does; the adapter’s own deps (hono, @hono/node-server) were already in the lock through the v1 SDK. |
| An existing repository helper | None: src/ and scripts/lib/ have no MCP protocol implementation. |
Zod 4’s built-in toJSONSchema for the prototype spec |
The spec schema is Zod 3; converting it with a different converter changes the advertised tool definition. |
Consequences¶
- Wire output is unchanged. The v2 behaviour differences are bridged in code, each in one
place:
McpError(src/daemon/mcpError.ts) keeps the v1MCP error <code>:message prefix on errors this server throws;createStdioServerTransport(src/server/stdioServerTransport.ts) keeps the stdio transport open across stdin EOF so requests received before EOF are still answered (#11951, #12049), and reads stdin only while started, as v1 did;createSdkMcpServer(src/server/sdkMcpServer.ts) bridges the rest: - it keeps v2 from advertising
prompts.listChanged.tools.listChangedandresources.listChangedare advertised under both SDKs (v1’sregisterToolmerged the tools bit in too), so theinitializecapabilities are identical; - it maps the
tools/callvalidation errors the SDK raises itself back to v1: a malformed request answers-32603with the bare validation message (v2:-32602 "Invalid tools/call request: …"), and a malformed result keeps itsMCP error -32602:prefix; - it gives every handler a signal whose transport-close abort reason is an
AbortErrorDOMException, as v1’s was (v2 aborts withSdkError(ConnectionClosed)), so theerror.name === "AbortError"checks insrc/still match.
test/server/sdkMcpServerV1Wire.test.ts pins these frames server side. Two differences
remain and are intentional: JSON-RPC notification frames serialize their keys in a
different order (jsonrpc first), which no JSON consumer observes; and a transport close
aborts in-flight handlers after the server’s onclose hooks run, where v1 aborted first.
- The daemon’s loopback client (
LoopbackMcpClient) maps SDK v2 client errors back to the v1 shape (prefixed message, numeric timeout/connection-closed codes, the abort signal’s own reason), so daemon socket frames are unchanged. The harness clients (scripts/mcp-drive.ts,scripts/manual-test/proxy-session.ts) do the same throughwithV1ClientErrors, so a request timeout is still reported as-32001. - v2 requires Zod
^4.2.0for schemas it converts itself. This repository still pins Zod 3.25.76; no v2 code path converts our schemas, becausetools/list,tools/calland the resource handlers remain our ownsetRequestHandlerhandlers. The v2 packages carry their own nested Zod 4 copy.