AI Engineering

MCP 2026-07-28: What Changed and How to Migrate

MCP's 2026-07-28 revision drops sessions and the initialize handshake. What breaks, what replaces it, and the exact migration order for your server.

Short answer: on 28 July 2026 the Model Context Protocol shipped its largest revision since launch, and it made MCP a stateless protocol. The initialize handshake is gone. The Mcp-Session-Id header is gone. Every request now carries its own identity, and Streamable HTTP calls must include Mcp-Method and Mcp-Name headers. If you run a remote MCP server, you have real migration work. This guide covers what broke, what replaces it, and the order to fix it in.

As of the 2026-07-28 revision, MCP servers no longer hold sessions. Each request is self-describing: protocol version, client info, and capabilities travel in _meta, and Streamable HTTP calls must include Mcp-Method and Mcp-Name headers. Any request can land on any instance behind a plain round-robin load balancer.

Why the protocol went stateless

The old design held a bidirectional stream open per client. That worked for a local stdio server. It fell apart the moment you put a server behind a load balancer.

You needed sticky sessions. You needed shared storage for session state. A pod restart dropped every connected client. Teams worked around it with routing hacks and Redis, and the protocol maintainers called this out directly: statelessness was the most requested change from people actually running MCP in production.

The new core removes the problem at the protocol level instead of asking you to solve it in infrastructure.

What changed, in one table

Area Before 2026-07-28 From 2026-07-28
Handshake initialize / initialized Removed; optional server/discover RPC
Session Mcp-Session-Id header Removed entirely
Routing Gateway parses the JSON body Mcp-Method + Mcp-Name headers
Server→client calls Held-open stream Multi Round-Trip Requests (MRTR)
List responses Re-fetch every time ttlMs + cacheScope hints
Client registration DCR common CIMD; DCR deprecated, still functional
Long-running work Experimental core io.modelcontextprotocol/tasks extension
Deprecated — Roots, Sampling, Logging, legacy HTTP+SSE

1. No handshake, no session

This is the change that breaks most code. A request no longer belongs to a conversation. It stands alone.

Before, a client opened a session and every later call referenced it:

POST /mcp
Mcp-Session-Id: 3f2a9c1e-...

{"jsonrpc":"2.0","id":1,"method":"tools/call", ...}

Now the same call describes itself completely:

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"},
 "_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}

Note what is not in there: no session header, no prior handshake, no server-side state required to interpret the call. That single property is what lets you put eight replicas behind round-robin and stop worrying about sticky routing.

What if my server needs state?

You still can. But the maintainers are explicit about the pattern: mint an explicit handle from a tool and have the model pass it back as an argument.

That is better than hidden transport state for two reasons. The handle is visible in the transcript, so the model can reason about it. And it survives independently of any connection, so a restart does not strand the user’s workflow.

2. MRTR replaces server-initiated requests

Sampling, elicitation, and roots all needed the server to call back into the client mid-request. Without a persistent stream, that has to be inverted.

Multi Round-Trip Requests do it in plain request/response. When a tool needs something from the user — a confirmation, a missing parameter — the server returns resultType: "input_required" together with the requests it needs answered. The client prompts, collects the answers, then retries the original call with those answers attached in inputResponses.

Two practical consequences:

  • Your tool handler must become resumable. It cannot assume it runs to completion in one call.
  • Your client must handle input_required as a normal response, not as an error.

This is the part most migrations underestimate. A confirmation flow that used to be three lines of stream plumbing becomes explicit state you own.

3. List responses are now cacheable

tools/list, prompts/list, resources/list, and resources/read return ttlMs and cacheScope.

Why it matters beyond latency: stable tool catalogues keep upstream prompt caches warm. If your tool list reshuffles on every reconnect, you pay for cache misses on every turn. Deterministic ordering plus a TTL hint fixes that without any client-side cleverness.

If you serve a tool catalogue that rarely changes, emit a generous ttlMs. If your tools are user-scoped, scope the cache accordingly.

4. Authorization hardening

Authorization is where implementers spend most of their integration time, and this release tightens several known holes.

  • RFC 9207 issuer validation. Authorization servers should return iss, and clients must validate it before redeeming a code. This closes an authorization-server mix-up attack.
  • CIMD replaces DCR. Dynamic Client Registration is formally deprecated in favour of Client ID Metadata Documents. DCR still works today and will be removed in a future version.
  • Issuer-bound credentials. Client credentials are bound to the issuer that minted them and cannot be reused across authorization servers.
  • Desktop and CLI redirects. Clients now set application_type during registration, which stops authorization servers rejecting localhost redirects.

If you have been putting off the auth cleanup, this revision is the deadline.

5. Tasks moved out of core

Long-running work is now the io.modelcontextprotocol/tasks extension rather than an experimental part of the core. You poll with tasks/get, update with tasks/update, and receive change notifications through a single subscriptions/listen stream that clients opt into per notification type.

The old HTTP GET notification endpoint is gone. If you built against it, that is a hard break, not a deprecation.

6. What got deprecated

Roots, Sampling, and Logging are deprecated, with a twelve-month minimum window. They still work. New implementations should not adopt them. The legacy HTTP+SSE transport is also officially deprecated with a year-long off-ramp.

That twelve-month floor is itself a change worth noting: MCP now has a formal deprecation policy, so you can plan upgrades instead of reacting to them.

Migration checklist, in order

  1. Bump the version string. Send MCP-Protocol-Version: 2026-07-28. Decide now whether you will dual-serve old clients.
  2. Delete the handshake. Remove initialize / initialized handling. Add server/discover only if your clients genuinely need capability discovery up front.
  3. Rip out session storage. Delete the session table, the Redis keys, and the sticky-session rule in your load balancer.
  4. Add the routing headers. Mcp-Method and Mcp-Name on every Streamable HTTP request. Wire your gateway and rate limiter to them.
  5. Rewrite server→client flows as MRTR. Start with elicitation, because it is the one users notice when it breaks.
  6. Emit cache hints. Add ttlMs and cacheScope to list and read responses, and make ordering deterministic.
  7. Audit auth. Validate iss, set application_type, and start planning the move from DCR to CIMD.
  8. Load test behind a round-robin balancer. The whole point is that any request can hit any instance. Prove it, including mid-workflow restarts.

Then write regression tests before you ship. A stateless protocol fails differently from a stateful one: the bug is usually a handler that silently depended on something the session used to carry. We wrote a separate guide on building an eval harness that runs in CI for exactly this class of regression.

Should you migrate now?

If your server is remote and runs more than one instance, yes. The session removal is the fix for a problem you are currently paying for in infrastructure.

If you only ship a local stdio server, the urgency is lower. Stdio has no transport session to speak of, so most of this revision costs you nothing. Still schedule it: SDKs have moved, and staying two revisions behind compounds.

Either way, pin the version you speak and write it down. The most common production incident here is not a protocol bug. It is a client and server silently disagreeing about which revision they are on.

Frequently asked questions

Do I still need server/discover?

Only if a client wants capabilities before making its first real call. It is optional by design. Most clients can simply call a tool and read the error if it is unsupported.

Can I support both the old and new revisions at once?

Yes, by routing on MCP-Protocol-Version. In practice the cost is real: two code paths, two test suites. Most teams set a cutover date and run the old path read-only until it retires.

Is Mcp-Session-Id still accepted?

No. It was retired with the handshake. A server that requires it will not work with 2026-07-28 clients.

What replaces Sampling?

MRTR covers the case where a server needs the client or user to produce something mid-call. Sampling is deprecated, not deleted, so nothing breaks today.

How long do I have before deprecations bite?

Twelve months minimum, by policy. Roots, Sampling, Logging, and HTTP+SSE all fall under it.

Does going stateless mean my server cannot remember anything?

No. It means the transport carries no state. Application state is fine — store it explicitly and hand the model a handle.

Sources

Protocol specification and release notes, modelcontextprotocol.io, revision 2026-07-28. Release announcement published 28 July 2026, checked 22 October 2026. SDK availability and migration notes for TypeScript, Python, Go, and C# live in the official repositories; the Rust SDK supports this revision in beta.

Last updated 22 October 2026. Protocol revisions move fast — verify against the specification before you ship.