The MCP TypeScript SDK reached v2.0.0 the same week the 2026-07-28 stateless spec locked, and the change that trips people isn't in the protocol — it's in npm install. The single @modelcontextprotocol/sdk package you've been importing from for a year is gone, split into nine subpackages. If you've already hit Cannot find module '@modelcontextprotocol/sdk/server/mcp.js', this is why.

The good news: it's a mechanical migration, there's a codemod, and once you understand why it split, choosing your packages takes about thirty seconds.

Why one package became nine#

In the stateful era you shipped one fat SDK because your server was long-lived — it opened a session, held it in memory, and stayed running. Loading a client, every transport adapter, and legacy shims you never called was fine; it happened once at boot.

The 2026-07-28 spec is stateless. Your server is now often a per-request function — a serverless handler that cold-starts, answers one call, and dies. In that world, importing a full-fat SDK means paying, on every cold start, to load code you'll never touch. The split fixes exactly that: you import @modelcontextprotocol/server plus one transport adapter, and nothing else. v2 also does lazy schema construction — it doesn't build its schema tree until something needs it — so a process-per-invocation runtime doesn't parse schemas it won't use. The packaging is the stateless bet, expressed in package.json.

The nine packages:

Step 1 — Run the codemod#

Let the tool do the import surgery first. @modelcontextprotocol/codemod rewrites your source for the new layout — most visibly moving Protocol and mergeCapabilities off the old shared/protocol.js path onto the client or server package root:

npx @modelcontextprotocol/codemod .

Commit before you run it so the diff is reviewable. The codemod handles the mechanical moves; it does not decide which transport you meant — that's the one judgment call it leaves you.

Step 2 — Install only what you use#

Now install the two or three packages the codemod's rewritten imports actually reference. For the common stateless-server case on a plain Node server:

npm install @modelcontextprotocol/server @modelcontextprotocol/node

Match the second package to your stack instead — the decision is small:

BuildingInstall
Stateless server, plain Nodeserver + node
Server inside Expressserver + express
Server on Hono (edge/workers)server + hono
Server on Fastifyserver + fastify
A client / gatewayclient + a transport

Then remove the old dependency so nobody re-imports it:

npm uninstall @modelcontextprotocol/sdk

If you have v1 server code you genuinely can't rewrite this sprint, @modelcontextprotocol/server-legacy gives you compat shims — treat it as a bridge, not a destination.

Step 3 — Fix the transport, then verify#

The transport wiring is the part you touch by hand, because it's the part the codemod can't infer. Your server construction now imports the handler from @modelcontextprotocol/server and the transport from your chosen adapter package rather than from one monolith path. Wire the adapter to your existing HTTP router, then confirm two things: the server advertises the 2026-07-28 revision, and it holds no session — no Mcp-Session-Id, no initialize handshake. If either is still there, you've migrated the packages but not the statefulness, and a modern client will treat you as legacy.

One relief for CommonJS projects: v2 ships both ESM (.mjs/.d.mts) and CJS (.cjs/.d.cts) with a require condition, so you don't have to convert to import syntax to upgrade. The package split is real work, but it's npm work — not a rewrite of your tools.

The payoff lands where it counts: a stateless MCP server that installs two packages, cold-starts lean, and speaks the current spec. If you're also choosing how to validate tool inputs while you're in here, that's its own v2 decision — the SDK stopped hard-wiring Zod.