How to Migrate Your MCP Server to 2.0: Codemod Walkthrough and Manual Fixes (2026)
Introduction
Your MCP server still runs on the v1 SDK, and the ecosystem has moved. The TypeScript SDK shipped v2 as split packages, the Python SDK released v2.3.0 on 2026-10-02, and the 2026-07-28 protocol revision made MCP stateless. This tutorial walks you through the full migration: running the official TypeScript codemod, fixing what it cannot rewrite, porting the Python breaking changes by hand, and optionally adopting the new protocol revision. It is written for developers who already run an MCP server in production or in active development.
Quick answer
Upgrade the SDK packages, then adopt the new wire protocol. For TypeScript, run the official codemod at your package root; for Python, apply the migration guide’s renames. Serving protocol revision 2026-07-28 is a separate opt-in step.
Evidence
Three numbers frame this migration. First, the Python SDK reached v2.3.0 on 2026-10-02, and pip install -U mcp now installs the 2.x line (GitHub releases for modelcontextprotocol/python-sdk, as of October 2026). Second, the TypeScript v2 packages require Node.js 20 or newer (official TypeScript SDK migration guide, as of October 2026). Third, the official codemod @modelcontextprotocol/codemod stands at version 2.3.1 on the npm registry (as of October 2026), which tells you the migration tooling itself is past its early teething phase.
The official migration guide gives one piece of advice that catches nearly everyone: “Run it at the package root (.), not ./src — real projects import the SDK from test/, scripts/, and fixtures too, and those rewrites are missed when you point it at ./src.” That single sentence explains most failed first attempts, so run the codemod at the root.
Outbound references: this tutorial’s procedures come from the TypeScript SDK v2 migration guide, the Python SDK v1-to-v2 migration guide, the guide to adopting protocol revision 2026-07-28, and the 2026-07-28 specification itself. Every URL was verified to return HTTP 200 on 2026-10-24.
Prerequisites
- An existing MCP server built on the v1 TypeScript SDK (
@modelcontextprotocol/sdk) or the v1 Python SDK (mcp1.x). - For TypeScript: Node.js 20 or newer installed. Check with
node --version. - For Python: Python 3.10 or newer (the v2 line raised its dependency floors; check your project’s Python range).
- Git working tree committed, so the codemod’s rewrites are easy to review and roll back.
- The Python MCP server tutorial or the TypeScript MCP server tutorial is useful background if you want to compare v1 and v2 idioms side by side.
Step 1: Decide your migration scope
MCP “2.0” is two upgrades stacked on each other, and confusing them causes most of the pain. The first upgrade is the SDK surface: the monolithic @modelcontextprotocol/sdk package splits into @modelcontextprotocol/client, @modelcontextprotocol/server, and @modelcontextprotocol/core, with framework adapters like @modelcontextprotocol/express for HTTP hosting. The second upgrade is the wire protocol: revision 2026-07-28 removes the initialize handshake and per-request sessions, adds a server/discover method, and wraps every request in a _meta envelope.
Do the SDK upgrade first. A hand-constructed Client, Server, or McpServer keeps speaking the 2025-era protocol it was written for, byte for byte, even after you move to v2 packages. That means the SDK upgrade is safe to deploy on its own, and the protocol adoption becomes a deliberate second change.
Step 2: Run the TypeScript codemod
The official codemod rewrites every mechanical rename. Commit your tree first, then run it at the package root:
npx @modelcontextprotocol/codemod@latest v1-to-v2 .
The trailing . is the package root, not ./src. The codemod rewrites package.json too, swapping the monolith dependency for the v2 packages your imports actually use. It also handles import paths, symbol renames such as McpError to ProtocolError and ErrorCode to ProtocolErrorCode, the setRequestHandler(Schema, ...) form becoming setRequestHandler('method/string', ...), and the builder API renames: .tool(), .prompt(), and .resource() become registerTool, registerPrompt, and registerResource, with raw Zod shapes wrapped in z.object().
After the run, three follow-up commands are mandatory:
grep -rn '@mcp-codemod-error' .
tsc --noEmit
prettier --write .
The grep finds every site the codemod recognized but could not safely rewrite, marked in place with an @mcp-codemod-error comment. Typical markers include namespace schema access like t.CallToolResultSchema.parse(...), files that receive the SDK surface through dependency injection with no SDK import of their own, and StreamableHTTPError constructor calls whose argument shape changed in the rename to SdkHttpError. The codemod also renames IsomorphicHeaders to the Web Standard Headers type, but converting bracket access such as headers["x-token"] to .get() is a manual step, since Headers exposes .get() and .set() rather than bracket indexing.
Step 3: Fix what the codemod cannot rewrite
Work through each @mcp-codemod-error marker, and watch for these specific traps that the migration guide calls out.
First, monorepo workspace members. The codemod rewrites only the nearest package.json found walking up from the target directory, one manifest total. Workspace members that still declare @modelcontextprotocol/sdk are listed in the printed manifest summary with the exact dependency changes they need; apply those edits yourself, then install. The framework adapter packages declare their framework as a peer dependency, so install express, hono, or fastify explicitly in the member that uses the adapter.
Second, dependency-injected SDK surfaces. The codemod is import-driven, so a file with no SDK import that receives the server or client through a parameter is never rewritten, and its v1 idioms fail at runtime instead of compile time. The classic symptom is the schema-first setRequestHandler(Schema, ...) form throwing a TypeError at registration. Grep such seams for v1 tokens beyond import statements (setRequestHandler(, ErrorCode., extra.) and apply the handler-registration and error sections of the guide by hand.
Third, the behavioral changes section. The codemod does not adapt you to list auto-aggregation, capability empties, lazy validator compilation, or the new output-schema validation rules. The experimental tasks feature was also removed outright (SEP-2663), so task-handler schema registrations get an action-required marker rather than a rewrite.
Step 4: Migrate the Python server
Python has no codemod, so this path is manual, guided by the official migration table. Update your pins first:
# pyproject.toml
dependencies = [
"mcp>=2,<3",
]
The mechanical renames that almost every project hits: FastMCP became MCPServer, so ModuleNotFoundError: No module named 'mcp.server.fastmcp' means you still import the old path. Fields moved from camelCase to snake_case, so tool.inputSchema is now tool.input_schema, and AttributeError: 'Tool' object has no attribute 'inputSchema' is the tell. McpError became MCPError. Resource URIs are plain str now, not AnyUrl.
The import changes deserve care. Protocol wire types moved into the standalone mcp-types distribution (import package mcp_types). The good news is that import mcp.types keeps working permanently: it is an alias that mirrors mcp_types exactly, every name the same object. Keep importing through mcp, the package you actually depend on, rather than reaching past it into mcp_types directly.
The transport swap catches production servers. httpx and httpx-sse were replaced by httpx2:
# Before (v1)
import httpx
http_client = httpx.AsyncClient(timeout=httpx.Timeout(30, read=300))
# After (v2)
import httpx2
http_client = httpx2.AsyncClient(timeout=httpx2.Timeout(30, read=300))
Audit except httpx.ConnectError: clauses as well: if httpx is still installed through another package, the old exception classes keep importing fine and simply never match again, a silent failure. Also note that httpx2 validates TLS certificates against the operating system trust store via truststore rather than the bundled certifi list, which matters in minimal containers without a usable system CA store.
Finally, two client-side changes: the Client now defaults to mode='auto', probing servers with server/discover, and the old streamablehttp_client alias was removed. If your package depends on mcp and you are not ready to migrate, keep a <2 upper bound; the v1.x line continues to receive critical bug fixes and security patches.
Step 5: Adopt protocol revision 2026-07-28 (opt-in)
Upgrading the SDK changes nothing on the wire. To serve 2026-07-28 over HTTP, wrap your server factory with createMcpHandler from @modelcontextprotocol/server:
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
const handler = createMcpHandler(() => {
const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
// register tools, resources, prompts once — the same factory backs both eras
return server;
});
By default (legacy: 'stateless') this serves both 2026-07-28 and 2025-era traffic from one endpoint. For stdio servers, replace await server.connect(new StdioServerTransport()) with serveStdio(() => buildServer()) from @modelcontextprotocol/server/stdio; the opening exchange pins each connection to an era.
The architectural consequence is real: 2026-07-28 is per request. The handler builds a fresh server per request and there is no Mcp-Session-Id. If your v1 server kept state keyed on the session id (ctx.sessionId / extra.sessionId), the 2026 answer is requestState: an opaque string your server returns with inputRequired(...) and the client echoes byte-for-byte on the retry, read back through the typed accessor ctx.mcpReq.requestState<T>(). This pairs with the stateless transport migration, which covered the same session-removal change from the transport side.
On the client side, opt into negotiation explicitly:
const client = new Client({ name: 'my-client', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')));
console.log(client.getProtocolEra()); // 'modern' | 'legacy'
Mode 'auto' probes with server/discover and falls back to the 2025 handshake against a 2025-only server; pinning { pin: '2026-07-28' } rejects against a 2025-only server instead of falling back. A client that negotiated the modern era drops inbound server-to-client JSON-RPC requests, because the 2026 era has no such channel. Also note the deprecations: roots, sampling, and logging are now deprecated client features.
Step 6: Verify with Claude Code and the inspector
Register the migrated server with Claude Code and check the era it negotiates. Run claude mcp list, confirm the server is connected, and ask the agent to call one of your tools. Since Claude Code 2.1.292 (2026-10-07) moved stdio MCP servers to protocol revision 2026-07-28, a server that only speaks the 2025 handshake will still work through the compatibility rules, but you will want to see modern once you have opted into the new entries. The Claude Code MCP setup walkthrough shows the registration commands in detail.
For in-process tests without sockets, drive createMcpHandler directly through its fetch function instead of spinning up a server; there is no in-memory serving entry, because InMemoryTransport.createLinkedPair() connects 2025-era instances only. Run your full suite, then run your conformance tests against both eras before the next deploy.
Troubleshooting
1. The codemod finished, but tsc --noEmit reports hundreds of errors.
This is expected when the codemod only got partway. First check the markers: grep -rn '@mcp-codemod-error' . and resolve each one. Then grep for leftover v1 tokens that the codemod cannot see, especially in test fixtures and scripts: setRequestHandler(, ErrorCode., extra., and @modelcontextprotocol/sdk outside src/. Remove v1-era double casts like as unknown as Transport too, since v2’s types satisfy those contracts directly and a surviving cast hides real errors.
2. After migration, clients get Error: Cannot find module '@modelcontextprotocol/sdk'.
Some file still imports the monolith. The codemod rewrites the nearest manifest walking up from the target directory, so in a monorepo some members were never rewritten; the manifest summary it printed lists each one with the exact dependency changes. Also check CI scripts, dependency-pin lints, and version allowlists that encode the literal v1 package name; they fail after the manifest swap and are invisible to the codemod.
3. Python server raises ModuleNotFoundError: No module named 'mcp.server.fastmcp'.
You are importing the v1 path. The class is now MCPServer:
# Before (v1)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
# After (v2)
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("my-server")
4. Python server starts but SSE events stop arriving, with no error.
This is the silent httpx versus httpx2 identity split. You passed an httpx.AsyncClient as http_client, and the SDK’s transport degrades subtly instead of raising. Import from httpx2, rebuild any httpx.Auth subclasses as httpx2.Auth, and audit except httpx.ConnectError: blocks, which now never match. The same split affects logging: target logging.getLogger("httpx2") and logging.getLogger("httpcore2") instead of the v1 logger names.
FAQ
Do I have to migrate my MCP server to 2.0 right away? No. The TypeScript v1 line and the Python v1.x line both keep receiving critical bug fixes and security patches, and the Python v1 docs stay available. But new features, the 2026-07-28 protocol revision, and new clients like Claude Code 2.1’s stdio runtime target v2, so a planned migration beats an emergency one.
Does upgrading the SDK change what my server puts on the wire? No. Upgrading the SDK packages is a separate step from adopting protocol revision 2026-07-28. A hand-constructed Client, Server, or McpServer keeps speaking the 2025-era protocol until you explicitly opt in with createMcpHandler, serveStdio, or versionNegotiation on the client.
Can the codemod migrate my server to protocol revision 2026-07-28? No. The official codemod only rewrites the v1 to v2 SDK surface: renames, import paths, and registration calls. Adopting 2026-07-28 (stateless requests, server/discover, per-request metadata) is an architectural change you do by hand.
What breaks most often in the Python migration? The FastMCP to MCPServer rename, camelCase to snake_case field renames, the httpx to httpx2 swap, and import paths that moved into the standalone mcp-types package. Each produces a specific ImportError or AttributeError that the migration guide’s find-your-changes table maps to the fix.
Will my migrated server still work with 2025-era clients? Yes. The v2 SDKs speak both protocol eras from the same entry points. createMcpHandler serves 2026-07-28 and 2025-era traffic from one factory by default, and serveStdio pins the era per connection after the opening exchange.
Conclusion
Migrate in two deliberate passes: first the SDK surface with the codemod (TypeScript) or the rename table (Python), deploy it, and only then adopt protocol revision 2026-07-28 with createMcpHandler or serveStdio. The urgency is real but not panicked: Claude Code already negotiates the new era, v1 lines keep security fixes for now, and the v2 SDKs speak both eras from the same entry points. Next, make the migrated server discoverable and operational by working through the n8n automation pillar guide, which shows how to wire MCP servers into production agent workflows.
Frequently asked questions
Do I have to migrate my MCP server to 2.0 right away?
No. The TypeScript v1 line and the Python v1.x line both keep receiving critical bug fixes and security patches, and the Python v1 docs stay available. But new features, the 2026-07-28 protocol revision, and new clients like Claude Code 2.1's stdio runtime target v2, so a planned migration beats an emergency one.
Does upgrading the SDK change what my server puts on the wire?
No. Upgrading the SDK packages is a separate step from adopting protocol revision 2026-07-28. A hand-constructed Client, Server, or McpServer keeps speaking the 2025-era protocol until you explicitly opt in with createMcpHandler, serveStdio, or versionNegotiation on the client.
Can the codemod migrate my server to protocol revision 2026-07-28?
No. The official codemod only rewrites the v1 to v2 SDK surface: renames, import paths, and registration calls. Adopting 2026-07-28 (stateless requests, server/discover, per-request metadata) is an architectural change you do by hand.
What breaks most often in the Python migration?
The FastMCP to MCPServer rename, camelCase to snake_case field renames, the httpx to httpx2 swap, and import paths that moved into the standalone mcp-types package. Each produces a specific ImportError or AttributeError that the migration guide's find-your-changes table maps to the fix.
Will my migrated server still work with 2025-era clients?
Yes. The v2 SDKs speak both protocol eras from the same entry points. createMcpHandler serves 2026-07-28 and 2025-era traffic from one factory by default, and serveStdio pins the era per connection after the opening exchange.