The Model Context Protocol TypeScript SDK went from one npm package to nine on July 27, 2026. @modelcontextprotocol/sdk — the package every MCP server tutorial, every StackOverflow answer, and every AI coding agent’s training data has referenced since MCP launched — was retired in favor of @modelcontextprotocol/server, @modelcontextprotocol/client, @modelcontextprotocol/core, and five framework adapters. Six weeks later, according to npm’s own download stats, @modelcontextprotocol/sdk still accounts for 86% of weekly installs across the three packages combined. If you’re writing a CLAUDE.md for a project that builds or maintains an MCP server, that number is the whole story: your agent is statistically far more likely to reach for the deprecated package than the current one, and nothing will stop it from compiling.
Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.
The Number That Explains Why Your Agent Still Writes v1 Code
Pulled directly from npm’s public download API for the week of September 3–9, 2026:
| Package | Weekly downloads |
|---|---|
@modelcontextprotocol/sdk (v1, deprecated) | 30,023,263 |
@modelcontextprotocol/server (v2) | 2,870,967 |
@modelcontextprotocol/client (v2) | 1,939,833 |
That’s 30M installs of a package that stopped being the recommended path six weeks earlier, against 4.8M combined installs of its two direct replacements — an 86/14 split in favor of the deprecated package. Some of that is legitimately pinned v1 codebases that haven’t migrated yet, which is fine and expected. But a meaningful share is new code: new tutorials still being published against v1, new Stack Overflow answers copy-pasted from before July, and AI coding agents scaffolding MCP servers using whatever pattern shows up most often in their training data — which, for the foreseeable future, is going to be @modelcontextprotocol/sdk.
The old package isn’t broken. 1.30.0, published the same day as the v2 launch, still installs and still works. That’s exactly the problem: there’s no error forcing anyone off it.
What Actually Split
The single package became a family of scoped ones, per the official migration guide:
| v1 | v2 |
|---|---|
@modelcontextprotocol/sdk | @modelcontextprotocol/client — client implementation |
@modelcontextprotocol/server — server implementation | |
@modelcontextprotocol/core — public Zod *Schema constants | |
@modelcontextprotocol/core-internal — private, never import directly | |
| Built-in HTTP framework support | @modelcontextprotocol/node, -express, -hono, -fastify |
@modelcontextprotocol/express, -hono, and -fastify declare their framework as a peer dependency now, not a direct one — npm install @modelcontextprotocol/express no longer pulls in express for you. That’s an easy one for an agent to miss silently: the import resolves, the types check, and the failure only shows up at runtime when the peer is actually missing.
The Renames CLAUDE.md Needs to Call Out
The v2 packages ship a codemod that mechanically rewrites most of this (more below), but an agent writing new code from a stale mental model will reach for the v1 shapes by default. The three that come up constantly:
.tool() is gone. Use registerTool with a config object.
// v1 — variadic, raw shape
server.tool('greet', 'Greet a user', { name: z.string() }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// v2 — config object, Standard Schema
server.registerTool(
'greet',
{ description: 'Greet a user', inputSchema: z.object({ name: z.string() }) },
async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
}
);
Watch the arity trap: a tool registered without an inputSchema passes the context object as its single callback argument in v2, where v1 passed nothing meaningful there. async ctx => ({ content: [] }) type-checks whether ctx is treated as a context object or ignored — an agent that doesn’t know the signature changed won’t get a compile error telling it so.
extra is now ctx, and it’s structured, not flat.
// v1
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
const signal = extra.signal;
});
// v2 — method string instead of Zod schema, structured ctx
server.setRequestHandler('tools/call', async (request, ctx) => {
const signal = ctx.mcpReq.signal;
});
extra.sendRequest() became ctx.mcpReq.send(), extra.authInfo became ctx.http?.authInfo (optional — undefined on stdio transports), and extra.sessionId became ctx.sessionId. An agent pattern-matching on old Stack Overflow snippets for extra. will produce code that fails type-checking immediately, which is the good case — the renames that silently keep compiling are the ones worth writing into CLAUDE.md.
Error classes split into protocol errors and local errors. McpError → ProtocolError, ErrorCode → ProtocolErrorCode, JSONRPCError → JSONRPCErrorResponse, StreamableHTTPError → SdkHttpError. The split reflects a real distinction v1 blurred — “the request was malformed” (protocol-level, the other side should see it) versus “something failed locally, like the connection dropped” (SDK-level) — so this isn’t cosmetic renaming an agent should just alias away.
Transports That Got Removed, Not Renamed
Two transports don’t have a rename — they’re gone:
SSEServerTransportis removed. Migrate to Streamable HTTP. A frozen v1 copy ships at@modelcontextprotocol/server-legacy/sseas a temporary bridge, but it’s explicitly a legacy shim, not a maintained path forward.WebSocketClientTransportis removed outright — it was never a spec transport. UseStreamableHTTPClientTransportfor remote servers orStdioClientTransportfor local ones.
The stdio transports also moved to a /stdio subpath rather than the package root:
// v1
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
// v2
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
And if your server does OAuth: Resource Server helpers (requireBearerAuth, mcpAuthMetadataRouter) moved to @modelcontextprotocol/express or the runtime-neutral core in @modelcontextprotocol/server, but Authorization Server helpers (mcpAuthRouter, OAuthServerProvider) landed in the frozen @modelcontextprotocol/server-legacy/auth — the guide’s own recommendation is to migrate your AS to a dedicated identity provider rather than lean on the legacy copy long-term. If your MCP server handles OAuth tokens or API keys during that transition, keep them out of source and out of .mcp.json entirely — 1Password CLI’s op run injects secrets at runtime instead, so a committed config file or a copy-pasted .env never becomes the thing that leaks a token during a rushed migration.
Why v1 Won’t Throw an Error — It’ll Just Be Wrong
This is the part that matters most for a CLAUDE.md rule, because it explains why the 86% download share isn’t going to self-correct on its own timeline:
@modelcontextprotocol/sdk(v1.x) keeps receiving bug fixes and security updates for at least six months past the v2 launch — there’s no forcing function.- v1 and v2 can be installed side by side in the same
package.json, under different names, which is the officially recommended path for staged migrations — but it also means an agent can add a v2 import next to existing v1 imports without anything flagging the inconsistency. - v1-constructed objects and v2-constructed objects don’t cross safely —
instanceofchecks and nominal types don’t match across the boundary, so an agent that mixes a v1Clientwith a v2 error class handler will get a bug that looks unrelated to the SDK split entirely. - The deprecated
sampling,roots, andloggingsubsystems are annotation-only deprecations in v2 (SEP-2577) — they still work, with the same call signatures, for at least twelve months. An agent copying aserver.sendLoggingMessage()call from existing code isn’t wrong today, but it’s writing against an API with a published removal date.
None of this produces a build failure. It produces code that works today and accumulates migration debt an agent has no way to see unless the project states it.
The Codemod, and What It Can’t Fix
The SDK ships an official codemod that handles the mechanical rewrites — import paths, .tool() → registerTool, extra → ctx property remapping, the error-class renames:
# Run at the package root, not a subdirectory —
# it also rewrites package.json and test/script imports
npx @modelcontextprotocol/codemod@latest v1-to-v2 .
# Anything it recognized but couldn't safely rewrite is marked in place
grep -rn '@mcp-codemod-error' .
# Then type-check and reformat
tsc --noEmit
prettier --write .
What it deliberately leaves for a human (or an agent working carefully, not on autopilot): deciding whether StreamableHTTPServerTransport should become the Node-specific or web-standard version depending on your runtime, re-pointing Resource Server auth helpers if the conservative default routed them to the legacy package, and any code that receives the SDK as an injected dependency rather than an import — the codemod is import-driven, so factory/DI seams pass through untouched and fail at runtime instead of compile time.
A CLAUDE.md Template for MCP Server Code
# CLAUDE.md — MCP Server (TypeScript)
## SDK Version
- This project uses MCP TypeScript SDK v2 (`@modelcontextprotocol/server` /
`@modelcontextprotocol/client` / `@modelcontextprotocol/core`), migrated
from v1 (`@modelcontextprotocol/sdk`) on {date}.
- Do not add new imports from `@modelcontextprotocol/sdk`. If you see it in
existing code, that file has not been migrated yet — do not mix v1-imported
objects with v2-imported code in the same call path (instanceof checks and
nominal types do not cross the v1/v2 boundary).
## Tool Registration
- Use `registerTool` / `registerPrompt` / `registerResource` with an explicit
config object. The variadic `.tool()` / `.prompt()` / `.resource()` forms
are removed in v2, not just deprecated.
- A tool registered without an `inputSchema` receives the context object as
its single callback argument — do not assume a single-parameter callback
is receiving tool arguments without checking the registration config.
## Handler Context
- The second handler argument is `ctx`, not `extra`. Common remaps:
`extra.signal` → `ctx.mcpReq.signal`, `extra.sendRequest()` →
`ctx.mcpReq.send()`, `extra.authInfo` → `ctx.http?.authInfo` (optional —
undefined on stdio transports).
## Transports
- `SSEServerTransport` and `WebSocketClientTransport` are removed in v2. Use
Streamable HTTP transports and `StreamableHTTPClientTransport` /
`StdioClientTransport` respectively.
- Never suggest reaching for `@modelcontextprotocol/server-legacy` as a
long-term fix — it exists as a frozen migration bridge, not a maintained
path.
## Errors
- `McpError` → `ProtocolError` (wire-protocol errors), `ErrorCode` →
`ProtocolErrorCode`. Local/transport failures use `SdkError` /
`SdkErrorCode` instead — these are not interchangeable with protocol
errors even though v1 didn't distinguish them.
{
"permissions": {
"allow": [
"Bash(npx @modelcontextprotocol/codemod*)",
"Bash(tsc --noEmit)"
],
"deny": [
"Edit(package.json)",
"Bash(npm install @modelcontextprotocol/sdk*)"
]
}
}
The npm install @modelcontextprotocol/sdk deny isn’t about blocking a legitimate dependency — it’s there so an agent troubleshooting a missing-module error can’t “fix” it by reinstalling the exact package the project just migrated away from.
v1 vs v2 at a Glance
v1 (@modelcontextprotocol/sdk) | v2 | |
|---|---|---|
| Package structure | One monolithic package | 9 scoped packages by role |
| Tool registration | .tool() variadic, raw shape | registerTool() with config object |
| Handler second argument | extra (flat) | ctx (structured: mcpReq, http, sessionId) |
| Low-level handler registration | setRequestHandler(ZodSchema, ...) | setRequestHandler('method/string', ...) |
| SSE server transport | SSEServerTransport | Removed — Streamable HTTP; frozen copy in server-legacy/sse |
| WebSocket client transport | WebSocketClientTransport | Removed entirely (not a spec transport) |
sampling / roots / logging | Fully current | Deprecated (SEP-2577), functional for ≥12 months |
| Node.js requirement | No hard floor | Node.js 20+ |
| Support window | — | v1 gets bug fixes/security patches for ≥6 months post-launch |
Common Mistakes to Watch For
Letting an agent scaffold a “new” MCP server against @modelcontextprotocol/sdk because that’s what’s in its training data. It’ll compile, it’ll run, and it’ll be built against a package with no future beyond security patches. Pin the CLAUDE.md rule before the first file gets written, not after.
Assuming the codemod finishes the job. It handles the mechanical renames well, but transport runtime selection, auth helper re-pointing, and dependency-injected SDK usage all need manual review — check for @mcp-codemod-error markers before calling a migration done.
Bumping to v2 without checking the zod peer range in a monorepo pinned to zod@3. v2 packages depend on zod ^4.2.0; in a workspace that can’t dedupe onto that, each v2 package resolves its own nested zod@4 copy. Measured on a production SPA, that cost roughly +83 KB gzipped of total JS before the workspace was upgraded to re-dedupe. Not fatal, but a surprise if a bundle-size budget gate trips right after an “unrelated” SDK bump.
Treating deprecated-in-v2 as removed-in-v2. sampling, roots, and logging still work with unchanged call signatures — an agent that rewrites working code to avoid them because they’re marked @deprecated is doing unnecessary churn on a twelve-month runway, not fixing a bug.
The v2 split itself is a reasonable design — a monolithic SDK that bundled every transport, every framework adapter, and every deprecated subsystem into one package was always going to get split eventually. The problem for anyone using an AI coding agent to write or maintain an MCP server isn’t the split; it’s that nothing forces the old package to stop working, so the default behavior — for a copy-pasted tutorial, a Stack Overflow answer, or an agent scaffolding from its training data — stays pointed at v1 until a CLAUDE.md says otherwise.
Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.
FAQ
Do I need to migrate my MCP server to the v2 SDK immediately?
No. @modelcontextprotocol/sdk (v1.x) continues to work and receives bug fixes and security updates for at least six months after the v2 launch. Migration is recommended but not forced by any deadline in the SDK itself.
Why does @modelcontextprotocol/sdk still get 86% of weekly downloads six weeks after v2 launched?
Partly legitimate — existing v1 codebases that haven’t migrated yet don’t need to rush. But a meaningful share is new code: tutorials and Stack Overflow answers written before the split, plus AI coding agents scaffolding new MCP servers from training data that overwhelmingly references the v1 package name.
Can I install v1 and v2 packages in the same project?
Yes, and it’s the officially recommended path for staged migrations, since they have different package names. The caveat: objects constructed by v1 code and v2 code don’t cross safely — instanceof checks and nominal types don’t match across the boundary, so mixing them inside the same call path produces bugs that don’t look SDK-related.
Does the migration codemod handle everything automatically?
It handles most mechanical renames — import paths, .tool() → registerTool, the extra → ctx property remap, error-class renames. It does not decide which HTTP transport runtime to use, re-point auth helpers that were routed conservatively, or catch SDK usage that comes in through dependency injection rather than a direct import. Run grep -rn '@mcp-codemod-error' . after running it to find what still needs manual review.
Are sampling, roots, and logging removed in v2?
No — they’re deprecated (SEP-2577) but fully functional, with unchanged call signatures, for at least twelve months from the deprecation date. Don’t rewrite working code that uses them just because they’re marked @deprecated.
Related Reading
- The MCP 2026-07-28 Spec: What Changes for Claude Code — the wire-protocol changes behind this SDK release, from the
.mcp.jsonconsumer’s perspective - Claude Code MCP Servers: The Complete Setup Guide for 2026
- MCP JSON Configuration: The Complete Reference
- Testing and Debugging MCP Servers in Claude Code