TypeScript SDK v1 to v2
- Spec
2025-11-25 → 2026-07-28- Evidence
- E2Reproducedarm64
- TypeScript SDK v1
@modelcontextprotocol/sdk@1.32.2- TypeScript SDK (server)
@modelcontextprotocol/server@2.4.0- TypeScript SDK (client)
@modelcontextprotocol/client@2.4.0- Verified
Version 2 of the TypeScript SDK splits the single @modelcontextprotocol/sdk package into separate packages, requires zod 4, and adds the entry points that serve the 2026-07-28 protocol. Version 1 (@modelcontextprotocol/sdk@1.32.2) is still published, and v1 clients and servers keep working with v2 ones over the 2025-11-25 protocol, so you can migrate one side at a time.
What changed
Section titled “What changed”| v1 | v2 | |
|---|---|---|
| Package | @modelcontextprotocol/sdk |
@modelcontextprotocol/server, @modelcontextprotocol/client, plus adapters such as @modelcontextprotocol/node and @modelcontextprotocol/express |
| Imports | …/sdk/server/mcp.js, …/sdk/server/stdio.js |
@modelcontextprotocol/server, @modelcontextprotocol/server/stdio |
| Schemas | zod 3 or 4; raw shapes like { a: z.number() } |
zod 4.2 or later (any Standard Schema library); wrap with z.object() |
| Start a stdio server | await server.connect(new StdioServerTransport()) |
serveStdio(() => createServer()) |
| Start an HTTP server | StreamableHTTPServerTransport |
createMcpHandler(() => createServer()) |
| Protocol | Up to 2025-11-25 | 2025-11-25 and 2026-07-28 |
| Node.js | 20 or later |
A tested example
Section titled “A tested example”We wrote a small server against @modelcontextprotocol/sdk 1.32.2 and ported it.
Before (v1):
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';import { z } from 'zod';
const server = new McpServer({ name: 'notes', version: '1.0.0' });
server.registerTool( 'add', { description: 'Add two numbers.', inputSchema: { a: z.number(), b: z.number() }, outputSchema: { sum: z.number() }, }, async ({ a, b }) => { const output = { sum: a + b }; return { content: [{ type: 'text', text: JSON.stringify(output) }], structuredContent: output }; },);
const transport = new StdioServerTransport();await server.connect(transport);console.error('notes: ready on stdio');After (v2):
import { McpServer } from '@modelcontextprotocol/server';import { serveStdio } from '@modelcontextprotocol/server/stdio';import * as z from 'zod';
serveStdio(() => { const server = new McpServer({ name: 'notes', version: '2.0.0' });
server.registerTool( 'add', { description: 'Add two numbers.', inputSchema: z.object({ a: z.number(), b: z.number() }), outputSchema: z.object({ sum: z.number() }), }, async ({ a, b }) => { const output = { sum: a + b }; return { content: [{ type: 'text', text: JSON.stringify(output) }], structuredContent: output }; }, );
return server;});console.error('notes: ready on stdio');Three changes: the imports, z.object() around the schemas, and building the server inside a factory passed to serveStdio(). The tool code itself did not change.
Use the codemod
Section titled “Use the codemod”The SDK team publishes a codemod. Run it at the package root, not in src/, because it also edits package.json:
npx -y @modelcontextprotocol/codemod@2.4.0 v1-to-v2 .grep -rn '@mcp-codemod-error' .On our v1 example it rewrote the imports, wrapped both raw schemas in z.object(), replaced @modelcontextprotocol/sdk with @modelcontextprotocol/server in package.json, and printed this warning:
Warning: zod range '3.25.76' cannot satisfy v2's floor: zod >=4.2.0 is required.It did not change server.connect(transport). That is deliberate, and it matters for the next two sections.
Trap 1: zod 3 fails late
Section titled “Trap 1: zod 3 fails late”If you ignore the zod warning, the server starts and even answers tools/call. It fails on tools/list, which is the first thing a client does:
Schema appears to be from zod 3, which the SDK cannot convert to JSON Schema. Upgrade to zod >=4.2.0, or wrap your JSON Schema with fromJsonSchema().Fix: npm install zod@^4.2.0. The v2 packages depend on zod 4 themselves, so a project that pins zod 3 ends up with two copies.
Trap 2: connect() stays on the old protocol
Section titled “Trap 2: connect() stays on the old protocol”StdioServerTransport still exists in v2, and server.connect(new StdioServerTransport()) still runs. But it only speaks 2025-11-25. We connected the TypeScript client 2.4.0 with versionNegotiation: { mode: 'auto' } to each version of the server:
| Server | Negotiated |
|---|---|
v1, connect() |
2025-11-25 |
v2 after the codemod, connect() |
2025-11-25 |
v2, serveStdio() |
2026-07-28 |
So after the codemod, move the entry point to serveStdio() yourself. For HTTP servers the equivalent is createMcpHandler(); see Migrate from 2025-11-25 to 2026-07-28.
Clients
Section titled “Clients”On the client side the package is @modelcontextprotocol/client. Two things to know:
- The v2 client still uses the 2025-11-25 handshake by default. Set
versionNegotiation: { mode: 'auto' }to use 2026-07-28 when the server supports it. client.callTool()no longer takes a result schema argument for spec methods; the codemod removes it.
Related
Section titled “Related”Sources
MCPDev is community documentation. It is not affiliated with the Model Context Protocol project or with the vendors of the clients it covers.