MCP 2026-07-28 shipped: the stateless core is the headline, header routing is the story

The final MCP spec deletes sessions, mirrors requests into HTTP headers, and turns elicitation into multi-round-trip calls. What breaks, and what to audit.


Diagram: one tools/call under MCP 2026-07-28 — a client POSTs a self-contained request with mirrored MCP-Protocol-Version, Mcp-Method, Mcp-Name and Mcp-Param headers; a gateway routes on headers without parsing the body; any pod in a stateless server pool serves it and rejects header/body mismatches with -32020; below, the Multi Round-Trip Request loop returns an InputRequiredResult with an opaque requestState the client echoes back

In June I wrote that the MCP release candidate was about to delete the session handshake, and that you had about ten weeks. That clock ran out yesterday. The 2026-07-28 specification is final — the fifth revision of MCP, and the largest since it launched.

Two of the three things I flagged in June landed roughly as expected: the stateless core, and the OAuth hardening. Fine. But re-reading the finished spec against the RC, the change I underweighted is the one I’d now put first for anyone operating this stuff — and it isn’t statelessness.

It’s that MCP became a protocol your existing L7 infrastructure can actually see.


The part everyone will lead with

Sessions are gone. No initialize handshake, no Mcp-Session-Id. Every request is self-contained, carrying its protocol version, client identity, and capabilities in _meta.io.modelcontextprotocol/* fields. Any instance behind a round-robin load balancer can serve any request.

This is genuinely good and genuinely boring — which is the point. It converts MCP servers from stateful connection-holders into ordinary horizontally-scalable HTTP services: no sticky sessions, no session affinity, no in-memory state to lose on a pod restart, and serverless and edge deployment become viable. Anthropic’s David Soria Parra called it the most important release since remote MCP launched, and on scaling economics he’s right.

The adoption numbers explain the urgency behind it: MCP passed 400M monthly SDK downloads, 4x growth this year, with over 950 servers in Claude’s connectors directory. A protocol at that volume can’t keep asking operators for session affinity.

But “my servers scale sideways now” is a capacity story. Here’s the operability story.


Headers changed what a gateway can be

Under Streamable HTTP, clients must now mirror selected body fields into HTTP headers, explicitly so that “intermediaries (load balancers, gateways, observability tooling) can route and inspect requests without parsing the body.”

HeaderMirrorsRequired for
MCP-Protocol-Version_meta protocol versionevery POST
Mcp-Methodmethodall requests
Mcp-Nameparams.name or params.uritools/call, resources/read, prompts/get
Mcp-Param-{Name}any tool param annotated x-mcp-headerwhen the server annotates it

That last row is the sleeper. A server can annotate individual tool parameters in its inputSchema with x-mcp-header, and conforming clients must mirror those values into headers. Using the spec’s own example, a Spanner query tool can surface its region argument as a routable header:

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1

Read that as an operator. Tool identity and selected tool arguments are now visible to Envoy, nginx, your WAF, and your rate limiter as plain header values. Per-tool rate limits, per-tenant quotas, region-pinned routing, tool-level metrics, blocking one misbehaving tool at the edge — all of it becomes ordinary L7 configuration instead of a body-parsing proxy you wrote yourself.

I’ve argued for a while that agent platforms need an MCP gateway doing identity exchange, catalog brokering, schema firewalling, and audit. That argument doesn’t go away — policy, redaction, and human-approval queues still need to understand payloads. But the cheap half of it just became infrastructure config, and the expensive half gets a much better substrate to sit on. For token cost attribution in particular, Mcp-Name in an access log is a materially better dataset than anything I’ve had to work with.

There are constraints worth knowing before you design around it: x-mcp-header applies only to primitive parameters (integer, string, boolean — number is excluded), only to properties statically reachable through a chain of properties keys, and values that aren’t header-safe ASCII get a Base64 sentinel wrapper (=?base64?…?=). Clients must reject tools that violate these rules — by excluding just that tool from tools/list, not the whole server.


And it opens a confused-deputy seam the spec tells you to close

Mirroring creates two copies of the truth, and the spec is unusually direct about the risk: servers must reject requests where headers disagree with the body, “when different components in the network rely on different sources of truth (e.g., a load balancer routing on the header value while the MCP server executes based on the body value).”

The body wins. Mismatch gets 400 Bad Request and JSON-RPC error -32020 (HeaderMismatch), and servers must Base64-decode encoded header values before comparing. If you write an intermediary that rewrites a mirrored header, you have built a request the backend is required to reject.

The sharper trap is version-dependent. Header–body validation is only guaranteed on revisions that mandate it, so an intermediary enforcing policy on mirrored headers should verify MCP-Protocol-Version indicates such a revision — and reject rather than trust the headers if it doesn’t. Skip that check and you’ve built exactly the vulnerability the validation rule exists to prevent: an older client can hand your gateway header values nobody will ever reconcile against the body, and your routing, metering, and tenant isolation all key off attacker-chosen strings.

If you’re standing up header-based policy this quarter, that one check is the highest-value line of code in the migration. This is the same class of problem as trusting an agent’s self-asserted identity: the mechanism is fine, and the failure is believing an unverified claim about it.


Your one tool call is now several HTTP requests

Statelessness had to be paid for somewhere, and Multi Round-Trip Requests (SEP-2322) is where. Servers may no longer send JSON-RPC requests at all — not on SSE streams, not anywhere. When a server needs a user confirmation or an LLM completion, it returns an InputRequiredResult carrying inputRequests plus an opaque, server-defined requestState. The client gathers the input and re-POSTs the original call with inputResponses and that same requestState echoed back unchanged. Any instance can pick up the retry, because the continuation travels in the payload.

Clean design. Four consequences if you operate it:

  • Latency budgets multiply. A tool call needing confirmation is now at minimum two full request cycles including human think-time between them. Anything you sized as one timeout is now a sequence, and the p99 you measured last month describes a different protocol.
  • Idempotency stops being optional. A retried tool call arrives as a new request with a new id. If your handler has side effects and your only guard was “one call, one connection,” that guard is gone.
  • Traces need stitching. Two POSTs are one logical operation, and nothing in HTTP tells your backend that. Whatever correlation you carry — and if you’re following the OTel GenAI conventions, you have somewhere to put it — has to span the round trips, or every elicitation flow reads as two unrelated calls forever.
  • requestState is opaque to you too. It’s the server’s continuation, so its size, sensitivity, and lifetime are the server’s business. If you run the gateway, it’s an unbounded blob transiting your policy layer in both directions. Bound it and log its size before someone puts a megabyte of context in there.

Change notifications moved too: the standalone GET stream endpoint is gone, replaced by a long-lived subscriptions/listen request whose response stream carries only the notification types the client opted into.


What you quietly lost

Some removals are easy to miss because they’re framed as simplification:

  • Stream resumability. Last-Event-ID is not supported. Combined with “closing the SSE response stream MUST be treated as cancellation,” a dropped connection mid-call is now unambiguously a cancelled request. For long-running tool calls over imperfect networks — the wobbly VPN, the laptop that slept — this is a real regression, and the mitigation is application-level: make long work resumable yourself, most likely via the Tasks extension, which moved out of the experimental core into a versioned io.modelcontextprotocol/tasks extension with polling and durable handles.
  • Roots, sampling, and logging are deprecated. They still work; new implementations shouldn’t adopt them.
  • The legacy HTTP+SSE transport (2024-11-05) is formally deprecated under the new feature-lifecycle policy (SEP-2596) and eligible for removal in a future revision.
  • Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents.

The genuinely new thing here is that all of those come with a clock instead of a vibe: the revised spec guarantees at least 12 months between deprecation and removal. Boring governance is the strongest signal in this release. It’s the difference between a protocol you experiment with and one you can put in a compliance document — and if you’re navigating the EU AI Act timeline, a written deprecation floor is worth more than any feature in the changelog.


Honest caveats

I’m reading a spec, not a fleet. Nobody has operated this at scale yet, and the interesting failures in stateless protocols are the ones that only appear under real traffic — thundering herds against tools/list when a cache TTL expires fleet-wide, requestState blobs growing until they hit a proxy’s header or body limit, MRTR retry storms when a client mishandles input_required. My chaos experiments for MCP need rewriting for this revision, and I’d rather do that than predict which one bites first.

Coverage so far has also been notably one-sided. The Register’s piece quotes Anthropic and Microsoft on the benefits and concedes only that “some migration cost is expected” — no skeptical practitioner voices, because there hasn’t been time for any. Treat the enthusiasm accordingly, mine included.

And the SDK story is uneven: TypeScript, Python, Go, and C# ship full support, Rust is beta. If Rust is load-bearing for you, that’s your timeline, not the spec’s.


What to do Monday

  • Grep for session state. Every place you key state on Mcp-Session-Id or assume connection affinity is a rewrite. Start here; it’s the longest pole.
  • Inventory your elicitation and sampling flows. Anything built on a held-open stream where the server calls the client back needs to become MRTR. Check idempotency on every tool with side effects while you’re in there.
  • Find every intermediary that parses bodies to make decisions. Most of those can now read headers instead — and should, since it’s faster and the spec sanctions it.
  • If you run a gateway, add the version check first. Verify MCP-Protocol-Version mandates header–body validation before trusting any mirrored header for routing, metering, or tenant isolation. One conditional, and it’s the difference between L7 policy and a spoofable one.
  • Bound requestState. Size limits and logging on the opaque blob now transiting your policy layer, before someone discovers how much context fits in it.
  • Put a date on the deprecated features you use. Roots, sampling, logging, HTTP+SSE, DCR. You have a 12-month floor and a written policy; use it to schedule the work instead of discovering it.

The June version of this post said the interesting thing about MCP 2.0 was that it made the protocol scale. Having read the final spec, I’d revise that. Statelessness is what lets you run a lot of MCP. Header mirroring is what lets the infrastructure you already own govern a lot of MCP — and governability, not throughput, is the thing standing between agent platforms and the regulated environments where they’re headed.

Frequently asked questions

What changed in the MCP 2026-07-28 specification?

Five things matter. The protocol core went stateless — the initialize handshake and the Mcp-Session-Id header are gone, and every request now carries its own protocol version, client info, and capabilities in _meta. Server-to-client interactions (elicitation, sampling, roots) were redesigned as Multi Round-Trip Requests: the server returns an InputRequiredResult instead of calling the client back. Streamable HTTP now requires clients to mirror selected body fields into HTTP headers so gateways can route without parsing bodies. Authorization was hardened with RFC 9207 issuer validation and client credentials bound to their issuing authorization server. And roots, sampling, logging, and the legacy HTTP+SSE transport were formally deprecated under a new policy guaranteeing at least 12 months before removal.

Is MCP 2026-07-28 a breaking change?

Yes, in several places at once. Removing the initialize handshake and protocol-level sessions breaks any client or server that relied on session identifiers. The required Mcp-Method and Mcp-Name headers break non-conforming clients, which now get a 400 with JSON-RPC error -32020 (HeaderMismatch). Servers may no longer send JSON-RPC requests on SSE streams, so any elicitation or sampling implementation built on held-open streams needs rework. The GET stream endpoint and Last-Event-ID stream resumability are gone. The spec does define a backward-compatibility path: a client attempts a modern request first and inspects the 400 response body before falling back to initialize.

What are the Mcp-Method and Mcp-Name headers for?

They let intermediaries — load balancers, gateways, WAFs, observability tooling — route, meter, and inspect MCP traffic without parsing the JSON-RPC body. Mcp-Method mirrors the method field and is required on all requests; Mcp-Name mirrors params.name or params.uri and is required on tools/call, resources/read, and prompts/get. Servers can additionally annotate individual tool parameters with x-mcp-header to mirror them into Mcp-Param-{Name} headers. Critically, the body remains the source of truth: any server that processes the body MUST reject requests where a header disagrees with it, because a load balancer routing on the header while the server executes on the body is a security vulnerability, not a convenience.

Do I need to migrate my MCP servers immediately?

Not immediately, but the audit is urgent because the fixes are architectural rather than cosmetic. Deprecated features stay functional for at least 12 months, and Tier 1 SDKs (TypeScript, Python, Go, C#) already ship 2026-07-28 support, with Rust in beta. Start by finding every place you hold per-session state, every elicitation or sampling flow built on a held-open stream, and every intermediary that makes routing decisions from request bodies. Those three lists are your migration plan. If you operate a gateway, add one more item: verify the protocol version before trusting any mirrored header.

Comments