MCP TypeScript SDK v2 Migration Guide
The official MCP TypeScript SDK split into separate client and server packages for v2. Here is what actually breaks, what the codemod handles automatically, and what you still have to fix by hand.
Version 2 of the official MCP TypeScript SDK shipped alongside the 2026-07-28 protocol revision, and it is a genuine breaking change, not a minor version bump. The single @modelcontextprotocol/sdk package is gone, replaced by a set of smaller packages, and several APIs were renamed or restructured along the way. This guide walks through what changed and how to migrate an existing server.
The Package Split
v1 shipped everything in one package: @modelcontextprotocol/sdk. v2 splits it into focused packages:
@modelcontextprotocol/server: server implementation@modelcontextprotocol/client: client implementation@modelcontextprotocol/core: shared Zod schema constants used by both- Framework adapters:
@modelcontextprotocol/node,@modelcontextprotocol/express,@modelcontextprotocol/hono,@modelcontextprotocol/fastify
You only install what you actually use. A server that only needs Node's built-in http module no longer pulls in Express-adjacent code it never calls.
Run the Codemod First
Before doing anything by hand, run the official codemod:
npx @modelcontextprotocol/codemod@latest v1-to-v2 .
It rewrites imports, renames .tool() calls to registerTool(), and updates most of the mechanical renames automatically. After running it:
grep -rn '@mcp-codemod-error' . tsc --noEmit
The codemod leaves a @mcp-codemod-error marker anywhere it could not confidently rewrite the code, so search for those first, then let the TypeScript compiler surface anything else.
Handler Registration Changed
The .tool(), .prompt(), and .resource() shorthand methods are gone. v1:
server.tool("greet", "description", {name: z.string()}, async ({name}) => { return {content: [{type: "text", text: `Hello, ${name}!`}]}; });
v2:
server.registerTool( "greet", { description: "description", inputSchema: z.object({name: z.string()}), }, async ({name}) => { return {content: [{type: "text", text: `Hello, ${name}!`}]}; } );
The pattern is consistent across tools, prompts, and resources: an explicit register* call with a config object, instead of positional arguments.
The Handler Context Parameter Was Restructured
The second argument to a request handler, previously called extra, is renamed ctx and its properties are reorganized:
| v1 | v2 |
|---|---|
extra.sessionId | ctx.sessionId |
extra.signal | ctx.mcpReq.signal |
extra.requestId | ctx.mcpReq.id |
extra.sendRequest() | ctx.mcpReq.send() |
extra.sendNotification() | ctx.mcpReq.notify() |
extra.requestInfo?.headers | ctx.http?.req?.headers |
That last one is worth calling out on its own: headers are now a standard Web Headers object, so you read them with .get("mcp-session-id") instead of bracket access on a plain object.
Request Handlers Take Method Strings, Not Schemas
v1 required importing a Zod schema constant just to register a handler:
import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; server.setRequestHandler(CallToolRequestSchema, handler);
v2 takes the method name directly:
server.setRequestHandler("tools/call", handler);
For spec-defined methods, the SDK resolves the schema from the method name internally, so you no longer pass a schema when calling client.request() for something like sampling/createMessage either.
Error Classes Were Consolidated
If your server has error handling that checks specific SDK error types, this is one of the changes most likely to cause a silent bug rather than a compile error:
| Scenario | v1 | v2 |
|---|---|---|
| Request timeout | McpError + ErrorCode.RequestTimeout | SdkError + SdkErrorCode.RequestTimeout |
| Protocol errors | McpError + ErrorCode.* | ProtocolError + ProtocolErrorCode.* |
| HTTP transport errors | StreamableHTTPError | SdkHttpError + SdkErrorCode.ClientHttp* |
| OAuth errors | InvalidGrantError and similar | OAuthError + OAuthErrorCode.* |
// v1 if (error instanceof McpError && error.code === ErrorCode.RequestTimeout) { /* ... */ } // v2 if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) { /* ... */ }
Search your codebase for instanceof McpError, instanceof StreamableHTTPError, and any specific OAuth error class imports. Those are the checks most likely to silently stop matching after the upgrade.
Zod Version Requirement
v1 accepted zod ^3.25 || ^4.0. v2 requires zod ^4.2.0. If your server still uses Zod v3 schemas, they will not fail at install time or compile time. They fail at runtime, and only surface the first time a client calls tools/list. Upgrade Zod explicitly rather than relying on the type checker to catch it.
What Was Removed Outright
A few things do not have a direct v2 replacement and need to be redesigned:
SSEServerTransportandWebSocketClientTransport- The variadic
.tool(),.prompt(),.resource()shorthand methods - Task-related APIs tied to the old experimental Tasks implementation (
taskStore,taskId,taskRequestedTtl), superseded by theio.modelcontextprotocol/tasksextension in the 2026-07-28 spec
If you were using the legacy HTTP+SSE transport, frozen copies exist at @modelcontextprotocol/server-legacy/sse for a temporary bridge, but plan to move to Streamable HTTP rather than depending on the legacy package long-term.
Migrating a Large Codebase Incrementally
For anything beyond a small server, migrating everything in one commit is risky. The SDK authors recommend a staged approach:
- Add the v2 packages alongside the existing
@modelcontextprotocol/sdkdependency. - Rewrite call sites incrementally, module by module.
- Remove the v1 dependency only once nothing references it.
The one hard constraint: v1 and v2 objects cannot be passed across the same boundary. If you stage the migration, do it along transport or process boundaries rather than mixing v1 and v2 objects inside the same request handler.
Minimum Requirements
v2 requires Node.js 20 or later. It is built ESM-first but ships a CommonJS build as well, so both import and require() continue to work.
Skipping the SDK Entirely
If maintaining SDK version compatibility across every server you run sounds like more overhead than you want, that is exactly the kind of maintenance MCPCore is designed to remove. You write tools in plain JavaScript in a browser-based builder, and SDK and protocol version upgrades happen on the platform side instead of being something you have to schedule and test yourself.
For the protocol-level changes that came with this SDK release, see MCP 2026-07-28: What Changed in the New Specification.
Package names, APIs, and migration steps are current as of the v2 release. Always check the official TypeScript SDK migration guide for the latest details before upgrading a production server.