Running Handshake and Stateless MCP in the Same Gateway

mooreds1 pts0 comments

Running Handshake and Stateless MCP in the Same Gateway · William Collins&darr;<br>Skip to main content

William Collins

Table of Contents<br>Table of Contents

On July 28th, the MCP maintainers shipped the 2026-07-28 revision. No more initialize. No more initialized. No more Mcp-Session-Id. David Soria Parra called it &ldquo;MCP&rsquo;s most important release since remote MCP first launched over a year ago,&rdquo; and I couldn&rsquo;t agree more. In the weeks leading up to the release, I had the opportunity to have Angie Jones on the podcast to talk through some of the changes, community, and excitement on Episode 80 of The Cloud Gambit Podcast - (have a listen!)<br>I maintain gridctl, an MCP gateway that sits between your LLM client and a fleet of downstream MCP servers. Sessions were not a detail in that design. They were the spine. Identity, access scoping, telemetry, and rate limiting all hung off a session created at handshake time. The spec removing sessions was a big deal that took some time to plan out.<br>This post is about what it actually took to support the new revision without breaking the old one, plus a second standards drop nine days later that turned out to be the easier half of the story.<br>What Actually Changed<br>The headline is that MCP went from a bidirectional stateful protocol to plain request/response. That single change cascades:<br>Sessions are gone. Every request carries its own protocol version, client capabilities, and client identity in a _meta block. There&rsquo;s no handshake to negotiate against.<br>server/discover replaces initialize. A read-only method that tells you what the server is, with no side effects.<br>Multi Round-Trip Requests (MRTR) replace server-initiated requests. Instead of holding an open stream so the server can ask the client something, the server returns resultType: "input_required" and the client comes back with inputResponses.<br>Method and tool names travel in HTTP headers. Mcp-Method and Mcp-Name mirror fields from the body.<br>List results are cacheable. Responses carry ttlMs and cacheScope.<br>Roots, Sampling, Logging, and HTTP+SSE are deprecated with a 12-month support window.<br>That header change is the one I want to sit on for a second, because it exists specifically for people building what I&rsquo;m building. If the method and tool name are in headers, a gateway can route and authorize a request without parsing the JSON body at all. That&rsquo;s a real gift to intermediaries! A gift that I would love discussing over Christmas dinner, but alas, people in my family would just think I lost the rest of my marbles.

Note

The spec is careful here: headers are a mirror, not a source of truth. Any server that actually processes the request has to validate the header against the body and reject a mismatch with error code -32020. Trusting the header blindly is a request-smuggling primitive.

You Cannot Just Flip<br>Let&rsquo;s talk constraints that shape things. A gateway has two sides. Upstream, your LLM client connects to it. Downstream, it connects to some number of MCP servers you don&rsquo;t control. Claude Desktop might be on the new revision while three of your five downstream servers are still on 2025-11-25 and one is a hosted endpoint that will upgrade whenever its vendor feels inclined to do so.<br>So &ldquo;support the new spec&rdquo; really means &ldquo;speak both revisions concurrently, per peer, in both directions.&rdquo; I ended up calling them eras , and the whole trick was deciding where an era is allowed to exist:<br>10<br>11<br>// Era is a property of the peer, never of an individual request, and it<br>// is normalized at the transport edges: everything inboard (router,<br>// access policy, call gates, pins, telemetry) stays era-free.

// EraHandshake covers revisions that establish a session via the<br>// initialize handshake (2025-11-25 and earlier).<br>EraHandshake ProtocolEra = "handshake"

// EraStateless covers revisions that carry per-request metadata<br>// with no handshake (2026-07-28 and later).<br>EraStateless ProtocolEra = "stateless"

Era gets resolved at the transport boundary and never travels inward. The router doesn&rsquo;t know. The access policy doesn&rsquo;t know. Telemetry doesn&rsquo;t know. If era had leaked into the middle of the gateway, every one of those subsystems would have grown a branch, and the branches would have drifted out of sync within a month.<br>Downstream negotiation is a probe. Send server/discover, see what comes back:<br>// Downstream era resolution: gridctl probes each MCP server with<br>// server/discover before anything else. A positively modern answer<br>// selects the stateless era; a recognized modern error identifies a<br>// modern server that needs different handling; everything else falls<br>// back to the legacy initialize handshake on the same connection.

Note the asymmetry: only positive evidence of a modern peer selects the stateless era. Auth challenges and 5xx responses reject outright rather than getting classified. A legacy server that silently swallows...

server rsquo handshake request gateway client

Related Articles