Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/migration-doc-overhaul.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---

Reframe the v1→v2 migration guide around the "most servers: just bump `@modelcontextprotocol/sdk` to ^2" path. Adds a TL;DR section, a Prerequisites section (zod ^4.2.0, `moduleResolution`, bun cache), transitive-v1-dependency guidance, and a warning against extending spec-method params with custom fields.
21 changes: 19 additions & 2 deletions docs/migration-SKILL.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
---
name: migrate-v1-to-v2
description: Migrate MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to v2 (@modelcontextprotocol/core, /client, /server). Use when a user asks to migrate, upgrade, or port their MCP TypeScript code from v1 to v2.
description: Migrate MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to v2 (@modelcontextprotocol/sdk@^2, or the split /client and /server packages). Use when a user asks to migrate, upgrade, or port their MCP TypeScript code from v1 to v2.
---

# MCP TypeScript SDK: v1 → v2 Migration

Apply these changes in order: dependencies → imports → API calls → type aliases.
## Path A (default) — Stay on the `/sdk` package

`@modelcontextprotocol/sdk@^2` is a meta-package: v1 import paths and APIs continue to work as `@deprecated` aliases (IDE strikethrough, no runtime warnings). **Do not rewrite imports.**

1. Bump `"@modelcontextprotocol/sdk"` to `"^2.0.0"` in package.json.
2. Ensure §1 environment prerequisites (zod `^4.2.0`, tsconfig `moduleResolution: bundler|nodenext|node16`).
3. Run the build / typecheck.
4. **If it passes — STOP. Migration complete.** Report `@deprecated`-flagged usages as optional follow-ups, do not rewrite them.
5. If it fails, fix only the specific errors using the rename table in §5 and the not-shimmed list below. Do not apply §2 (uninstall sdk) or §3 (import rewriting).

**Not shimmed (need a one-line edit even on Path A):** `IsomorphicHeaders` → `Headers`; `StreamableHTTPServerTransport` → `NodeStreamableHTTPServerTransport` (from `@modelcontextprotocol/node`); the 5-arg `server.tool(name, desc, schema, annotations, cb)` form → `registerTool`.

## Path B — Move to the split packages

Apply §2 onwards **only** if the user explicitly wants to drop `@modelcontextprotocol/sdk` and import from `@modelcontextprotocol/server` / `@modelcontextprotocol/client` directly (smaller bundle, no `@deprecated` strikethrough, or building a host/framework/transport). Order: environment → dependencies → imports → API calls → type aliases.

## 1. Environment

- Node.js 20+ required (v18 dropped)
- ESM only (CJS dropped). If the project uses `require()`, convert to `import`/`export` or use dynamic `import()`.
- TypeScript `moduleResolution` must be `bundler`, `nodenext`, or `node16` (legacy `node`/`node10` cannot resolve the v2 `exports` map; fails with TS2307).
- If using Zod, must be `zod@^4.2.0` (older versions lack `~standard.jsonSchema`; passes typecheck but **crashes at runtime** on `tools/list`).

## 2. Dependencies

Expand Down Expand Up @@ -89,6 +105,7 @@ Notes:
| `isJSONRPCResponse` (deprecated in v1) | `isJSONRPCResultResponse` (**not** v2's new `isJSONRPCResponse`, which correctly matches both result and error) |
| `ResourceReference` | `ResourceTemplateReference` |
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
| `ResourceTemplate` (type) | `ResourceTemplateType` |
| `IsomorphicHeaders` | REMOVED (use Web Standard `Headers`) |
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |
| `McpError` | `ProtocolError` |
Expand Down
116 changes: 116 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,104 @@

This guide covers the breaking changes introduced in v2 of the MCP TypeScript SDK and how to update your code.

> **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.

## Upgrade paths

There are two ways to move to v2. **Pick one before reading further.**

### Path A — Stay on the `/sdk` package (recommended for most servers)

`@modelcontextprotocol/sdk@^2` is a meta-package that re-exports the new split packages at the v1 import paths. **Your existing imports work unchanged.**

```diff
// package.json
- "@modelcontextprotocol/sdk": "^1.0.0"
+ "@modelcontextprotocol/sdk": "^2.0.0"
```

After bumping, run your typecheck. **If it passes, you're done — stop reading.** The v1 APIs (`@modelcontextprotocol/sdk/server/mcp.js`, `server.tool()` variadic, `McpError`, `RequestHandlerExtra`, `SSEServerTransport`, etc.) are `@deprecated` aliases that compile and run identically. Your IDE will show strikethrough and a hover note pointing at the new API, but there are no runtime warnings.

If typecheck fails, fix only the errors you see — the table in [What the meta-package doesn't shim](#what-the-meta-package-doesnt-shim) lists the handful of changes that still need a one-line edit.

**Do not rewrite imports to the split packages unless you're choosing Path B.**

### Path B — Move to the split packages

Choose this if you want to drop the `/sdk` dependency and import directly from `@modelcontextprotocol/server` / `@modelcontextprotocol/client`. Reasons:

- Smaller bundle (you only ship what you use)
- No `@deprecated` strikethrough in your editor
- You're a host application, framework adapter, or custom transport author (these touch surface the meta-package doesn't fully cover)

The rest of this guide is **Path B**. If you chose Path A, skip to [What the meta-package doesn't shim](#what-the-meta-package-doesnt-shim).

> **Clients and frameworks** 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.

## What the meta-package doesn't shim

These are the changes you need regardless of which path you chose. Everything else in this guide is Path B only.

| What | Change |
|---|---|
| `zod` version | Must be `^4.2.0` — see [Prerequisites](#zod-must-be-420) |
| tsconfig `moduleResolution` | Must be `bundler`, `nodenext`, or `node16` — see [Prerequisites](#typescript-moduleresolution-must-be-bundler-nodenext-or-node16) |
| `IsomorphicHeaders` type | → standard `Headers` — see [§](#headers-object-instead-of-plain-objects) |
| `StreamableHTTPServerTransport` (Node) | → `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node` — see [§](#streamablehttpservertransport-renamed) |
| `server.tool(name, desc, schema, annotations, cb)` 5-arg form | → `registerTool(name, {description, inputSchema, annotations}, cb)` — see [§](#mcpservertool-prompt-resource-removed) |

These five were intentionally not shimmed because the codemod handles them mechanically (`npx @modelcontextprotocol/codemod-v2`).

Check warning on line 51 in docs/migration.md

View check run for this annotation

Claude / Claude Code Review

npx @modelcontextprotocol/codemod-v2 — uncited forward-reference reintroduced at :51

🟡 nit: Same uncited-codemod issue previously flagged for `mcp-codemod v1-to-v2` at :251 (resolved by dropping it) has reappeared here under a different name — `npx @modelcontextprotocol/codemod-v2` has no implementation in the monorepo and no cited v2-bc companion PR (unlike #1887/#1903/#1906/#1907/#1908/#1909/#1913/#1916/#1917). The table above already links each item to its manual fix, so the sentence stands without the parenthetical; either cite the PR that ships `@modelcontextprotocol/codemo
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 🟡 nit: Same uncited-codemod issue previously flagged for mcp-codemod v1-to-v2 at :251 (resolved by dropping it) has reappeared here under a different name — npx @modelcontextprotocol/codemod-v2 has no implementation in the monorepo and no cited v2-bc companion PR (unlike #1887/#1903/#1906/#1907/#1908/#1909/#1913/#1916/#1917). The table above already links each item to its manual fix, so the sentence stands without the parenthetical; either cite the PR that ships @modelcontextprotocol/codemod-v2 or drop (npx @modelcontextprotocol/codemod-v2).

Extended reasoning...

What changed

Commit d556ce5 (the Path A/B restructure) added a new "What the meta-package doesn't shim" table at docs/migration.md:43-49 and closed it with line 51:

These five were intentionally not shimmed because the codemod handles them mechanically (npx @modelcontextprotocol/codemod-v2).

What ships

rg -i 'codemod' across the entire repo returns exactly one hit: docs/migration.md:51 itself. packages/ contains only client, core, middleware, server — there is no codemod-v2 package, no bin entry in any package.json, and no other reference. A user running npx @modelcontextprotocol/codemod-v2 will get npm ERR! 404 Not Found.

Why this isn't a duplicate of resolved comment 3148499514

That comment flagged mcp-codemod v1-to-v2 at :251. The author resolved it in d556ce5 by dropping the reference (rather than citing a PR — strong evidence no codemod PR exists in the v2-bc series). But the same commit simultaneously introduced this new reference at :51 with a different package name. The target line of 3148499514 no longer contains a codemod reference; :51 is new content added after that thread was resolved, so it never went through the cite-or-drop vetting.

Why this isn't covered by the top-of-doc disclaimer

The note at :5 ("compatibility shims … land across the v2-bc PR series") licenses forward-references to shims, and the established standard in this PR's thread (comments 3096147312 / 3096386106 / 3096432260) is that each forward-reference maps to a specific cited PR. Every other not-yet-on-this-branch reference in the doc has one: isSpecType/specTypeSchema#1887, /zod-schemas#1906, OAuthError#errorCode#1903, 3-arg setNotificationHandler#1916, Protocol/ProtocolSpec#1917, /sdk meta-package → #1913, /express RS-auth → #1907, /node/sse#1909, server-auth-legacy#1908. The codemod has none.

Step-by-step proof

  1. A Path A user reads the new "What the meta-package doesn't shim" section — the only section the doc tells them to read.
  2. Line 51 says the five not-shimmed items are handled by npx @modelcontextprotocol/codemod-v2.
  3. They run npx @modelcontextprotocol/codemod-v2.
  4. npm responds npm ERR! 404 Not Found - GET https://registry.npmjs.org/@modelcontextprotocol%2fcodemod-v2 — no such package exists in the monorepo or anywhere in the cited v2-bc series.
  5. They fall back to the table at :43-49, which links each of the five items to a §-section with the manual one-line fix. Migration completes; the codemod parenthetical added a 30-second dead end without adding a working path.

Impact

Low — hence nit, matching the precedent set for the same issue at :251. The rationale ("intentionally not shimmed") and the table's per-row §-links stand on their own; only the convenience-tool parenthetical is unbacked. A failed npx is a brief detour, not a blocked migration.

Fix

Same two options applied at :251:


## Prerequisites

Before upgrading, check these three environment requirements. They are the most common source of confusing errors during migration.

### Zod must be `^4.2.0`

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.

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`.

```jsonc
// package.json
"dependencies": {
"zod": "^4.2.0"
}
```

```typescript
// Either of these works on zod@^4.2.0:
import * as z from 'zod';
import * as z from 'zod/v4';

// This does NOT work (zod 3.x has no Standard Schema support):
import * as z from 'zod/v3';
```

### TypeScript `moduleResolution` must be `bundler`, `nodenext`, or `node16`

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'`.

Update your `tsconfig.json`:

```jsonc
{
"compilerOptions": {
"moduleResolution": "bundler" // or "nodenext" / "node16"
}
}
```

> 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.

### Testing locally with bun: clear the cache

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.

```bash
bun pm cache rm && bun install --force
```

## Overview

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`,
Expand Down Expand Up @@ -111,6 +209,10 @@

### Server-side SSE transport removed

> **Path A users:** the meta-package re-exports `SSEServerTransport` and `SSEClientTransport` at their v1 paths. You don't need to change anything.
>
> **Path B users:** the v2 server package does not include SSE. Install `@modelcontextprotocol/server-auth-legacy` (which also bundles `LegacySSEServerTransport`) or migrate to Streamable HTTP per the example below.

Check failure on line 214 in docs/migration.md

View check run for this annotation

Claude / Claude Code Review

SSE Path-B callout: contradicts SKILL §3/§8, points at server-auth-legacy, and references a nonexistent 'example below'

The new Path B SSE callout has three problems in one sentence: (1) it contradicts SKILL.md — d556ce5 reverted SKILL §3 to `server/sse.js` → "REMOVED (migrate to Streamable HTTP)" and §8 to "`SSEServerTransport` removed entirely", so migration.md offers Path B users an SSE escape hatch while SKILL.md (the LLM-applied Path B procedure) says none exists; (2) it routes SSE users to `@modelcontextprotocol/server-auth-legacy` (which your own forward-ref map assigns to #1908, the **auth** package), not
Comment on lines +212 to +214
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The new Path B SSE callout has three problems in one sentence: (1) it contradicts SKILL.md — d556ce5 reverted SKILL §3 to server/sse.js → "REMOVED (migrate to Streamable HTTP)" and §8 to "SSEServerTransport removed entirely", so migration.md offers Path B users an SSE escape hatch while SKILL.md (the LLM-applied Path B procedure) says none exists; (2) it routes SSE users to @modelcontextprotocol/server-auth-legacy (which your own forward-ref map assigns to #1908, the auth package), not the SSE package (#1909) — and rg 'LegacySSEServerTransport|server-auth-legacy' returns only this line, so please confirm which v2-bc PR actually ships LegacySSEServerTransport; (3) "per the example below" is dangling — the SSE section ends one sentence later, and the NodeStreamableHTTPServerTransport example is in the section above (link to #streamablehttpservertransport-renamed instead).

Extended reasoning...

What changed

Commit d556ce5 (the Path A/B restructure) added a new callout to the SSE section at docs/migration.md:212-214:

Path B users: the v2 server package does not include SSE. Install @modelcontextprotocol/server-auth-legacy (which also bundles LegacySSEServerTransport) or migrate to Streamable HTTP per the example below.

The same commit touched docs/migration-SKILL.md only to add the new Path A/B preamble (+15/-3); it did not update §3 or §8.

(1) Cross-doc contradiction with SKILL §3 / §8

docs/migration-SKILL.md now reads:

  • §3 server-imports table, row @modelcontextprotocol/sdk/server/sse.js"REMOVED (migrate to Streamable HTTP)" — no fallback package.
  • §8, titled "## 8. Removed Server Features", body: "SSEServerTransport removed entirely. Migrate to NodeStreamableHTTPServerTransport."

SKILL's own preamble says "Apply §2 onwards only if the user explicitly wants to drop @modelcontextprotocol/sdk" — i.e., §3 and §8 are the Path B procedure. So for the same path, migration.md says "install server-auth-legacy and keep SSE", while SKILL.md says "SSE is gone, rewrite to Streamable HTTP". An LLM following SKILL.md will force a Streamable-HTTP rewrite even when migration.md says a one-line install suffices.

This is the same cross-doc-disagreement category accepted and fixed in 10164d3 and 2126a9d. It is not a duplicate of resolved comment 3148499526 — that comment described the previous state (SKILL §8 said "Relocated… @modelcontextprotocol/node/sse", migration.md heading said "removed"). d556ce5 was the response: it inverted the polarity (SKILL now says removed, migration.md now offers an escape hatch) and changed the referenced package, so this is a fresh instance.

(2) server-auth-legacy vs the SSE package

Your forward-reference map in comment 3096386106 assigns server-auth-legacy#1908 (auth) and /node/sse#1909 (SSE) as separate v2-bc PRs. Earlier revisions of this PR pointed SSE users at @modelcontextprotocol/node/sse (#1909); d556ce5 silently switched to @modelcontextprotocol/server-auth-legacy with no citation. rg 'LegacySSEServerTransport|server-auth-legacy' returns only docs/migration.md:214 — nothing under packages/, nothing in SKILL.md.

It's possible #1908 deliberately consolidated the legacy SSE transport into the auth-legacy package, but per the cite-or-drop standard established in this thread, please confirm which v2-bc PR ships LegacySSEServerTransport (or correct the package name if it's a copy-paste from the auth row).

(3) Dangling "per the example below"

The callout ends "…or migrate to Streamable HTTP per the example below." But the SSE section contains exactly one more prose sentence (:216) before ### WebSocketClientTransport removed begins at :218 — there is no example below. The NodeStreamableHTTPServerTransport before/after example is in the preceding section ("StreamableHTTPServerTransport renamed", :194-208), i.e. above. The diff hunk @@ -111,6 +209,10 @@ added 4 lines and removed 0, so the section never had an example.

Step-by-step proof

  1. A user on Path B with an SSE-based server reads docs/migration.md:214 and sees they can keep SSE by installing @modelcontextprotocol/server-auth-legacy.
  2. They (or an LLM) cross-check against migration-SKILL.md — the doc this PR's own footer says is "designed for tools like Claude Code to mechanically apply all the changes".
  3. SKILL §3 says server/sse.js → "REMOVED (migrate to Streamable HTTP)"; §8 says "SSEServerTransport removed entirely." Neither mentions server-auth-legacy or LegacySSEServerTransport.
  4. The agent forces a Streamable-HTTP rewrite. The user, reading migration.md, expected to keep SSE. The two docs gave opposite instructions.
  5. Separately, a reader who chooses "migrate to Streamable HTTP per the example below" scrolls down, finds only one prose sentence and then the WebSocket section, and has to scroll back up to find the example at :194-208.

Fix


The SSE transport has been removed from the server. Servers should migrate to Streamable HTTP. The client-side SSE transport remains available for connecting to legacy SSE servers.

### `WebSocketClientTransport` removed
Expand Down Expand Up @@ -396,6 +498,9 @@

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`.

> **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
> method name instead.

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`.

#### Sending custom-method requests
Expand Down Expand Up @@ -512,6 +617,16 @@

`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.

### Transitive dependencies still on v1

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
`Transport`).

There is no SDK-side fix for this. Either:

- Upgrade the transitive dependency to v2 first, or
- Add a type assertion (`as unknown as Transport`) at the boundary until it is upgraded.

### Client list methods return empty results for missing capabilities

`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.
Expand Down Expand Up @@ -553,6 +668,7 @@
| `isJSONRPCResponse` | `isJSONRPCResultResponse` (see note below) |
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
| `ResourceReference` | `ResourceTemplateReference` |
| `ResourceTemplate` (type) | `ResourceTemplateType` |
| `IsomorphicHeaders` | Use Web Standard `Headers` |
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |

Expand Down
Loading