Migrating an MCP Server to the 2026-07-28 Spec: Twenty Minutes, Step by Step
Migrating an MCP server to the 2026-07-28 spec in about twenty minutes: sessions are gone, five changes to know, Python SDK 2.0 steps and the MRTR rewrite.
This article is published in English.
Sources: Your MCP Server Is Running on a Deprecated Spec. Here’s the 20-Minute Migration.
Nothing broke, and that's the unsettling part. On July 28, 2026, the Model Context Protocol published its 2026-07-28 specification, the biggest change since remote MCP first appeared. A header that servers relied on was retired and the transport model changed shape, yet compatibility was kept. Servers carried on without a stack trace, an alert or a red build.
That is the quiet sort of technical debt. It stays hidden until someone needs to scale out, or until the twelve-month deprecation window closes and a dependency stops being maintained. A server still speaking the 2025-11-25 revision is a revision behind. One on the legacy HTTP+SSE transport is using something now deprecated, along with possibly three features on a countdown.
What follows covers what changed, why the maintainers did it and how to catch up. For most servers it's about twenty minutes of work. One change, though, is a genuine rewrite for some servers, and it's better to say so up front.
Why sessions went
The engineering motive explains everything else. MCP launched in late 2024 as a session-oriented protocol: the client opens with an initialize handshake, the server issues an Mcp-Session-Id, and every later request carries it. That suited the original case of one client and one server on one machine over stdio.
Then servers moved onto real infrastructure and the design hit a wall. Google's engineers described this in their write-up on the stateless update: a design that was ideal for a single local client broke down once MCP servers were spread across cloud-native infrastructure.
Consider what a session ID demands. Either sticky routing, so one client always reaches the same instance, or a shared session store that any instance can query. A plain HTTP service suddenly needs affinity-aware load balancing and something like Redis, merely to satisfy the transport, before it does anything useful.
The release post from David Soria Parra and Den Delimarsky puts it bluntly: MCP goes from a bidirectional, stateful protocol to a stateless request/response one, which was among the most requested changes from developers wanting reliability and scale. The urgency reflects size. The Tier 1 SDKs see close to half a billion downloads a month, and the TypeScript and Python SDKs have each passed a billion in total. A protocol at that scale can't keep making every deployment solve session affinity.
So the session is gone. Each request carries its own protocol version, client identity and capabilities, which means any request can reach any instance behind a plain round-robin balancer with no shared storage. Everything below follows from that.
Five changes to understand
1. No handshake, no session header
The initialize and initialized exchange and the Mcp-Session-Id header are officially retired (SEP-2575 and SEP-2567). Each request stands alone, with version, client identity and capabilities in _meta. A request under the new spec looks like this:
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search","arguments":{"q":"otters"},
"_meta":{"io.modelcontextprotocol/clientInfo":
{"name":"my-app",
"version":"1.0"}
}}}
A client that wants to know a server's abilities beforehand can use the new server/discover RPC, which is optional and no longer a mandatory handshake.
A common fear is that applications can no longer keep state. They still can. Removing the session from the protocol doesn't oblige an application to be stateless. When a server needs continuity, a tool should mint an explicit handle that the model hands back as an argument later. That works better than state tucked into the transport, since the model can see the handle and pass it from tool to tool. Visible state beats invisible state.
2. Two mandatory headers
From now on, Streamable HTTP requests have to carry Mcp-Method and Mcp-Name (SEP-2243), and the server is expected to confirm they match the request. Infrastructure benefits: a gateway, rate limiter or WAF can route and meter using headers and skip JSON parsing. Anyone who abandoned per-tool rate limiting at the edge, because the tool name was buried inside a payload, now has a way out.
3. Cacheable list responses
Results from tools/list, prompts/list, resources/list and resources/read now include ttlMs and cacheScope (SEP-2549) and come back in deterministic order. Clients can cache your tool catalog and keep upstream prompt caches stable across reconnects. Since so much of an agent's token bill is tool definitions sent repeatedly, this doubles as a cost feature.
4. Tighter authorization
Four changes landed, and one may explain a bug you've already fought:
- Authorization servers should return the
issparameter defined in RFC 9207, and clients must verify it before redeeming a code (SEP-2468), closing a mix-up attack hole. - Clients set
application_typeduring Dynamic Client Registration (SEP-837), so authorization servers stop refusinglocalhostredirects for desktop and CLI apps. If a CLI's OAuth flow ever failed with aredirect_urierror, this is probably why. - Client credentials are tied to the issuer that created them and can't be reused across authorization servers (SEP-2352).
- Dynamic Client Registration itself is formally deprecated in favor of Client ID Metadata Documents. It still works and will go in a later version.
5. The deprecation list
The word deprecated carries weight here. Roots, Sampling and Logging (SEP-2577) are deprecated but functional, with at least twelve months of runway, and new implementations shouldn't adopt them. The legacy HTTP+SSE transport is deprecated with a year-long off-ramp too.
Tasks was relocated, not deprecated. It graduated from the experimental core into the io.modelcontextprotocol/tasks extension, polled with tasks/get and updated through the new tasks/update (SEP-2663). Change notifications now flow through a single subscriptions/listen stream that clients opt into.
The best news here is a formal deprecation policy with a minimum twelve-month window. Real Python's coverage adds that a 90-day floor applies only to active security risks, which is more notice than the ecosystem has ever had. That policy may matter more than anything else in the release, because it separates a protocol you tinker with from one you can build a business on.
The migration, step by step
The walkthrough uses the Python SDK, whose rename is the most disruptive of the four, though the shape is the same in TypeScript, Go and C#.
Minutes 0 to 2: decide. If the server uses sampling, elicitation or roots, it needs a real rewrite, covered below. If it only exposes tools and resources, carry on.
Minutes 2 to 5: upgrade the SDK. The Python SDK reached 2.0.0 on the day the spec landed, and pip install mcp now pulls the 2.x line.
pip install --upgrade "mcp>=2"
One transitive change to know about: the SDK moved from httpx to httpx2. If httpx is pinned elsewhere in your dependency tree, sort that out now.
Minutes 5 to 10: rename the class. Every Python server is affected. FastMCP is now MCPServer, with no alias and no deprecation shim:
# Before, on mcp 1.x
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Demo")
# After, on mcp 2.x
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Demo")
Three more renames arrive in the same release:
# Wire types are snake_case now
result.isError # → result.is_error
tool.inputSchema # → tool.input_schema
# The error class matches the new capitalisation
except McpError: # → except MCPError:
mcp-types is now its own package, but mcp.types stays the supported import path, so those imports keep working. A caution: someone using the standalone fastmcp package instead of the official SDK isn't affected. It's a different project with its own 3.x versions, and part of the reason for the rename is to keep the two apart.
Minutes 10 to 15: remove session machinery. Session dictionaries, cleanup timers and a Redis instance built only for session state can all be deleted. If continuity across calls is truly needed, use the handle pattern:
# Instead of hiding state in a session the model can't see:
@mcp.tool()
def open_dataset(path: str) -> str:
"""Open a dataset and return a handle for subsequent calls."""
handle = register_dataset(path) # your own store, your own TTL
return handle # the model now HOLDS this
@mcp.tool()
def query_dataset(handle: str, query: str) -> list[dict]:
"""Query a previously opened dataset."""
return lookup(handle).query(query)
Minutes 15 to 18: validate the new headers. The server must check that incoming Mcp-Method and Mcp-Name agree with the request. The SDK covers the usual path, but behind a custom gateway or a hand-written transport, verify it end to end.
Minutes 18 to 20: fix the error code. It's easy to miss and produces confusing client behavior. Missing-resource errors should use the standard JSON-RPC invalid-params code -32602 and not MCP's own -32002.
Safe to ship
A single MCPServer serves both protocol eras, so a 2025-era client keeps working against a migrated server with nothing to configure on either side. That's why this can ship on an ordinary Tuesday, with no flag day coordinated across every client.
When it takes longer
Servers using sampling, elicitation or roots face a genuine rewrite, which Real Python calls the largest item on the migration list. Previously the server could initiate a request and call back to the client across a held-open two-way stream. A stateless design has no such stream, so the pattern flips through Multi Round-Trip Requests (SEP-2322).
The new flow works like this. The server responds with resultType: "input_required" and the questions it needs answered, and the client then repeats the original call with its answers in inputResponses. This changes how tools are written. A handler can't pause halfway and wait for an answer. It may be invoked multiple times for one logical operation and must pick up correctly when the input arrives, so anything kept in local variables across the pause has to be stored where a retry can reach it.
The payoff is real. Supabase's head of product said elicitation had long been on the roadmap but wasn't feasible because their MCP server runs statelessly, and MRTR makes it possible, letting tools confirm with a user first, for instance the cost of a new project or a query that would delete data. Writing the tool gets slightly harder, and running it stateless at scale becomes possible.
What the new protocol buys
Manufact hosts thousands of MCP servers and maintains the open-source mcp-use framework. It measured SDK v2 at roughly 83% smaller and 25% faster, crediting the new client-server split. The Agentic AI Foundation's ecosystem round-up says a server now acts like any other HTTP service, answering from whichever instance a round-robin balancer picks, with gateways routing and rate-limiting by header. The twelve-month policy means the protocol also stops moving under you.
Uptake was fast for an open standard. Within days, infrastructure providers, SDK maintainers, cloud platforms and tool vendors had announced support, put out migration notes and released production implementations. Cloudflare's Agents SDK handled the spec from day zero, Amazon Bedrock AgentCore ships the stateless core, and Microsoft Foundry, Netlify, Figma, Supabase and Honeycomb joined in. Honeycomb says nearly one in five of its monthly interactive queries now comes from agents, which explains why this mattered. Agents are past the demo stage.
Staying on 1.x
That's a legitimate choice. Pin and wait:
# Buys you time, keeps security patches coming
pip install "mcp>=1.28,<2"
Know two things while there. The 1.x line is in maintenance mode, with security fixes only. And the Tasks extension isn't yet in the Python SDK 2.0 release, so Tasks has left the core spec without arriving in the SDK. For Python users who depend on Tasks, waiting is correct. Put a reminder on the calendar for the twelve-month mark on Roots, Sampling and Logging, because deprecation windows feel endless until they end.
The danger of silent deprecation
A breaking change is loud: it fails the build, pages someone and gets fixed. A deprecation is quiet by design. The server runs, tests pass and nothing turns red, while the cost builds up: a transport with an expiry date, no easy horizontal scaling, and a dependency in maintenance mode as the ecosystem moves on. The version in MCP-Protocol-Version is a date, and it's worth checking how old it now is.
For most servers, catching up is an afternoon at most, and under half an hour of real work. Servers using sampling or elicitation should budget a day and read the MRTR specification first. Either way, do it before the twelve months quietly become two.