Logo Accura
Accura
All posts

How to migrate an MCP server to the 2026-07-28 spec

The MCP 2026-07-28 spec removes sessions, the initialize handshake and three primitives. Here are the seven breaking changes, in the order you should handle them.


On 28 July 2026, the Model Context Protocol shipped its largest revision since launch. This is not a few new fields. The transport core was rewritten, three primitives are deprecated, authorization got stricter, and two extensions were promoted to first class.

If you run an MCP server in production on 2025-11-25, nothing is broken today. That is the first thing to get straight, because half the writing on this topic is selling a panic that does not exist. The switch is opt-in on both sides: as long as your clients do not speak the new revision, your server keeps answering. And a client that speaks 2026-07-28 knows how to fall back to the old handshake when it reaches an older server.

What you lose by waiting is not availability, it is the upside. Servers that migrate run behind a plain round-robin load balancer, with no sticky sessions, no shared session store, and no gateway opening packets to route. They also become eligible for what comes next.

Here are the seven changes, in the order you should handle them.

1. The protocol goes stateless

This is the structural change, and everything else follows from it.

Protocol-level sessions are gone. The Mcp-Session-Id header is removed from the Streamable HTTP transport. List endpoints (tools/list, resources/list, prompts/list) no longer vary per connection: two clients querying your server get the same list.

A server that needs state across calls now handles it explicitly, with identifiers it mints itself and that travel as ordinary tool arguments. The pattern is called a handle. Your server issues an opaque value, the client hands it back on the next call, your server resolves it against storage.

The real work is not writing that pattern. It is finding every place your code still reads a session identifier without anyone realising. A pagination cursor kept in memory, a configuration draft built across two calls, an auth context cached per connection. As long as you ran a single instance, it worked, and that hid the problem.

The classic symptom after migration: it works locally, it works in staging on one pod, and it fails one time in three in production as soon as the load balancer spreads traffic.

2. The initialize handshake is gone

initialize and notifications/initialized are no longer required. There is no longer a moment where client and server introduce themselves and then remember each other.

Instead, every request carries what it needs to be understood on its own. Protocol version and client capabilities travel in the _meta field, under io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities.

For discovery, the spec introduces server/discover. That is where a client learns which versions and capabilities your server advertises.

In practice: anything initialised once at handshake and reused afterwards has to be recomputed or refetched per request. If your server resolves a user context during the handshake, that context now has to be rebuilt from the token and metadata of the current request.

3. Two HTTP headers become mandatory

Mcp-Method and Mcp-Name must accompany requests on Streamable HTTP. They mirror the JSON-RPC method and the name being called in the request body.

This is the highest-return change in the whole spec for anyone running the infrastructure. Until now, applying per-tool policy meant opening the body at the gateway. Now a rate limit on your export tool, a WAF rule on write tools, routing to a dedicated pool for expensive calls, per-tool metrics: all of it happens at the HTTP layer with tools you already have.

One important caveat. The header is declarative. A request can advertise a harmless read-only tool in Mcp-Name and call a sensitive tool in the JSON-RPC body. If your security policy trusts the header without checking it against the body, you have just built a bypass. The server must reject the mismatch, and that rejection must show up in your telemetry.

4. Error code -32002 becomes -32602

MCP had introduced -32002 for a missing resource. JSON-RPC 2.0 already has -32602 for invalid params. The spec drops the duplicate.

Two lines of code, and the nastiest trap in the set, because it breaks silently. A client matching on the literal -32002 does not crash, it simply stops recognising the error. You will not see it in error logs or alerts, only in degraded behaviour nobody connects back to the migration.

Grep for the string everywhere, including tests and in-house SDKs.

5. Tool lists become cacheable

The new revision introduces caching semantics. Your server can advertise a validity window through ttlMs, and clients may cache tools/list for that long.

On a server with many tools and many clients, that is less traffic and less first-call latency. It also demands discipline: if your tools vary by plan or feature flag, a long TTL surfaces tools the client cannot call, or hides tools that exist. Pick a TTL that matches how often your tool set actually changes, not the maximum you can get away with.

6. Three primitives are deprecated

roots, sampling and logging move to deprecated.

Sampling is the painful one, because it is the only deprecation that changes an architecture. A server that used to ask the client to run a model on its behalf now calls a model provider directly, or goes through its own unified inference layer. That moves both a cost and an API key from the client to you.

The good news: the spec now has a formal feature lifecycle. A deprecated feature stays functional for at least twelve months before removal, and deprecations are tracked publicly with timelines. So this does not belong in the same sprint as the transport work.

The bad news: code that compiles but throws at runtime is more expensive to debug later than to delete now. If you have dead sampling paths, remove them while the context is fresh.

7. Authorization gets stricter

Three OAuth changes, motivated by the real deployment shape of MCP, where one client talks to many servers.

The authorization server should include the iss parameter in authorization responses, and the client must validate that iss against the recorded issuer before redeeming the authorization code. This protects against authorization server substitution.

Clients must specify an appropriate application_type at registration, to avoid OpenID Connect redirect URI conflicts.

Client credentials are bound to the authorization server that issued them, so they must be stored keyed by issuer, and the client must re-register when the authorization server changes.

Finally, OAuth 2.0 Dynamic Client Registration is formally deprecated as a registration mechanism in favour of Client ID Metadata Documents. It still works for backwards compatibility, but it has a clock on it.

What does not break

Worth saying as clearly as the rest, because it changes the urgency:

  • Your tool definitions, input schemas and responses do not change
  • A 2026-07-28 client falls back to the initialize handshake against an older server
  • Depending on the SDK, one server can answer both revisions from a single endpoint
  • Nothing switches until both client and server have moved

This is not an overnight migration. It is a migration to do properly, with a rollback window.

The six-step plan

1. Static audit. Search the whole repo for session reads, Mcp-Session-Id, -32002, sampling calls, sticky session configuration. Do it on the server, but also on in-house clients, gateways and deployment config. A static audit does not prove compatibility, it lists where to look.

2. Map the state. For every session read you found, write down which business state it was actually hiding. This is the step teams skip, and the one that sinks migrations.

3. Externalise. Replace each piece of state with an explicit handle, shared storage, or a durable task. Pick a scope and TTL per state type, and plan for revocation.

4. Protocol conformance. server/discover, version metadata in _meta, Mcp-Method and Mcp-Name with body consistency checks, corrected error code, cache TTL.

5. Failover test. Drop sticky sessions from the load balancer and run a real multi-step scenario while killing instances. If a workflow survives a failover mid-flight, externalisation is done. If not, hidden state remains.

6. Rollback window. Keep the ability to return to the previous revision for at least two weeks of real traffic, and watch per-tool error rates, which you can finally count per tool.

The mistakes we see most

Migrating the transport without migrating the state. The code is conformant, the session is gone, and state still lives in an instance variable. It passes every unit test and fails in production under load.

Forgetting background subscriptions and resources. They held state, they are not on the main request path, nobody looks at them.

Trusting the new headers. See point 3. A declarative header is not an authorization decision.

Handling deprecations in the same sprint. Twelve months minimum is not the same urgency as the transport. Split the two.

Not measuring first. Without a baseline of latency and per-tool error rate before the switch, you cannot tell whether the migration degraded anything.

FAQ

Will my server stop working?

No. The switch is opt-in on both sides. A server on 2025-11-25 keeps answering clients that speak that revision.

How long does a migration take?

For a simple read-only server with no cross-call state, a few days. For a multi-tenant server with OAuth, multi-step workflows and a gateway in front, two to four weeks, most of it spent mapping state rather than writing code.

Should I migrate before submitting to the Claude connectors directory?

Not strictly, but you do not want to submit a server you will rewrite three weeks later. The sensible order is migrate, harden, then submit.

What if I have no protocol team?

That is the normal case. Most SaaS companies have an MCP server a developer wrote in two weeks a year ago, and nobody has tracked the SEPs since. That is exactly what the audit is for.

Find out where you stand

Send me access to your MCP server, or just the repository. Within 24 business hours you get a written diagnostic: what breaks, what does not, and a quote for the work. Free, no strings.

Ready to check? Get my MCP server audited →

Related reading

Official sources

Got an MCP server to audit?

Five minutes to describe it. I reply within one business day with a first written diagnosis, free of charge.

Get my MCP server audited
How to migrate an MCP server to the 2026-07-28 spec