|
2 | 2 |
|
3 | 3 | This guide covers the breaking changes introduced in v2 of the MCP TypeScript SDK and how to update your code. |
4 | 4 |
|
| 5 | +> **Note:** This guide describes the v2.0.0 release as a whole. The compatibility shims it references land across the [v2-bc PR series](https://github.com/modelcontextprotocol/typescript-sdk/pulls?q=is%3Apr+label%3Av2-bc); on any individual PR's branch some referenced symbols may not yet exist. |
| 6 | +
|
| 7 | +## TL;DR — most servers just bump the version |
| 8 | + |
| 9 | +For **most MCP servers**, upgrading to v2 is a one-line change: |
| 10 | + |
| 11 | +```diff |
| 12 | +- "@modelcontextprotocol/sdk": "^1.0.0" |
| 13 | ++ "@modelcontextprotocol/sdk": "^2.0.0" |
| 14 | +``` |
| 15 | + |
| 16 | +In v2, `@modelcontextprotocol/sdk` is a meta-package that re-exports the new split packages at the v1 import paths. v1 APIs (deep-import paths like `@modelcontextprotocol/sdk/server/mcp.js`, variadic `server.tool()`, `McpError`, `RequestHandlerExtra`, etc.) continue to work as |
| 17 | +**`@deprecated` aliases** — your IDE will show strikethrough and a hover note pointing at the new API, but there are no runtime warnings. You can ship on v2 immediately and migrate the deprecated usages at your own pace. |
| 18 | + |
| 19 | +Read on if you want to: |
| 20 | + |
| 21 | +- Migrate to the new split packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/client`) for smaller bundles |
| 22 | +- Adopt the new API surface (no `@deprecated` strikethrough) |
| 23 | +- Understand a specific breaking change |
| 24 | + |
| 25 | +> **Clients and frameworks** (host applications, custom transports, proxies) typically need a few more changes than servers — see the [Custom methods](#setrequesthandler-and-setnotificationhandler-use-method-strings), [Error hierarchy](#error-hierarchy-refactoring), and [Server auth](#server-auth-split) sections. |
| 26 | +
|
| 27 | +## Prerequisites |
| 28 | + |
| 29 | +Before upgrading, check these three environment requirements. They are the most common source of confusing errors during migration. |
| 30 | + |
| 31 | +### Zod must be `^4.2.0` |
| 32 | + |
| 33 | +If you use Zod for tool/prompt schemas, you must be on **`zod@^4.2.0` or later**, and import from the `zod` (or `zod/v4`) entry point. |
| 34 | + |
| 35 | +Older Zod versions (3.x, or 4.0.0–4.1.x) do **not** implement the `~standard.jsonSchema` property the SDK uses to render `inputSchema` for `tools/list`. This passes type-checking but **crashes at runtime** when a client calls `tools/list`. |
| 36 | + |
| 37 | +```jsonc |
| 38 | +// package.json |
| 39 | +"dependencies": { |
| 40 | + "zod": "^4.2.0" |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +```typescript |
| 45 | +// Either of these works on zod@^4.2.0: |
| 46 | +import * as z from 'zod'; |
| 47 | +import * as z from 'zod/v4'; |
| 48 | + |
| 49 | +// This does NOT work (zod 3.x has no Standard Schema support): |
| 50 | +import * as z from 'zod/v3'; |
| 51 | +``` |
| 52 | + |
| 53 | +### TypeScript `moduleResolution` must be `bundler`, `nodenext`, or `node16` |
| 54 | + |
| 55 | +v2 packages ship ESM-only with an `exports` map and no top-level `main`/`types` fields. TypeScript's legacy `"moduleResolution": "node"` (or `"node10"`) cannot resolve them and fails with `TS2307: Cannot find module '@modelcontextprotocol/server'`. |
| 56 | + |
| 57 | +Update your `tsconfig.json`: |
| 58 | + |
| 59 | +```jsonc |
| 60 | +{ |
| 61 | + "compilerOptions": { |
| 62 | + "moduleResolution": "bundler" // or "nodenext" / "node16" |
| 63 | + } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +> We deliberately do **not** ship `main`/`types` fallback fields — testing showed it suppresses TypeScript's helpful "consider updating moduleResolution" diagnostic and replaces it with dozens of misleading transitive errors. |
| 68 | +
|
| 69 | +### Testing locally with bun: clear the cache |
| 70 | + |
| 71 | +If you are testing a local v2 build via `file:` tarballs, note that **bun caches `file:` dependencies by filename** and ignores content changes. Re-packing the SDK without bumping the version installs a stale tarball. |
| 72 | + |
| 73 | +```bash |
| 74 | +bun pm cache rm && bun install --force |
| 75 | +``` |
| 76 | + |
5 | 77 | ## Overview |
6 | 78 |
|
7 | 79 | Version 2 of the MCP TypeScript SDK introduces several breaking changes to improve modularity, reduce dependency bloat, and provide a cleaner API surface. The biggest change is the split from a single `@modelcontextprotocol/sdk` package into separate `@modelcontextprotocol/core`, |
@@ -396,6 +468,9 @@ server.setRequestHandler('acme/search', { params: SearchParams, result: SearchRe |
396 | 468 |
|
397 | 469 | The handler receives the parsed `params` directly (not the full request envelope). `_meta` is stripped before validation and is available as `ctx.mcpReq._meta`. Supplying `result` types the handler's return value; omit it to return any `Result`. |
398 | 470 |
|
| 471 | +> **Warning:** Do **not** add custom fields to a _spec_ method's params (e.g., adding `{ tabId, image }` to `'notifications/message'`). v2 validates incoming spec messages against the spec schema, so unknown fields are stripped before your handler sees them. Use a vendor-prefixed |
| 472 | +> method name instead. |
| 473 | +
|
399 | 474 | For `setNotificationHandler`, the 3-arg handler is `(params, notification) => void`. The raw notification is the second argument, so `_meta` is recoverable via `notification.params?._meta`. |
400 | 475 |
|
401 | 476 | #### Sending custom-method requests |
@@ -512,6 +587,16 @@ const result = await specTypeSchemas.CallToolResult['~standard'].validate(value) |
512 | 587 |
|
513 | 588 | `isSpecType` and `specTypeSchemas` are keyed by `SpecTypeName` — a literal union of every named type in the MCP spec — so you get autocomplete and a compile error on typos. `specTypeSchemas.X` is a `StandardSchemaV1<In, Out>`, which composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works. |
514 | 589 |
|
| 590 | +### Transitive dependencies still on v1 |
| 591 | + |
| 592 | +If a package you depend on (e.g., a shared MCP utilities library or framework adapter) still vends types from `@modelcontextprotocol/sdk@^1`, you will see structural type errors where v1 and v2 types meet (`Server` is not assignable to `Server`, `Transport` not assignable to |
| 593 | +`Transport`). |
| 594 | + |
| 595 | +There is no SDK-side fix for this. Either: |
| 596 | + |
| 597 | +- Upgrade the transitive dependency to v2 first, or |
| 598 | +- Add a type assertion (`as unknown as Transport`) at the boundary until it is upgraded. |
| 599 | + |
515 | 600 | ### Client list methods return empty results for missing capabilities |
516 | 601 |
|
517 | 602 | `Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, and `listTools()` now return empty results when the server didn't advertise the corresponding capability, instead of sending the request. This respects the MCP spec's capability negotiation. |
|
0 commit comments