The 2026-07-28 revision of the Model Context Protocol — the final spec, publishing July 28, 2026 — deletes the session. No initialize handshake, no Mcp-Session-Id, no server-side memory pinned to a connection. We covered the server-side migration and why it matters for founders already. This piece is about the part that trips people up second: once you delete the session, how does a server stop mid-tool-call to ask the user "are you sure?"
The one-line answer: it doesn't stream the question — it returns it. The tool call comes back with an InputRequiredResult, the client answers, and the client re-throws the same call with the answers attached. That pattern is called a Multi-Round-Trip Request (MRTR), and it's the piece that makes stateless confirmation prompts possible.
The old way: hold a stream open#
Under the current spec, a server that needs input mid-call — a confirmation before it charges a card, a missing parameter, an elicitation — pushes a request back to the client over a held-open Server-Sent Events stream. That works, but it quietly forces two things: the connection stays alive for the whole exchange, and every subsequent message has to come back to the same server instance. That's session affinity, and it's exactly what the stateless spec is trying to kill. A held-open SSE stream is a session by another name.
The new way: return the question, get the call re-issued#
MRTR turns the interruption inside-out. Instead of pushing over a stream, the server returns a result that says "I can't finish yet, I need input":
// Server → client: the tool call returns, but not with an answer
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"type": "input_required",
"inputRequests": [
{ "name": "confirm_charge",
"prompt": "Charge $48.00 to card •••4242?",
"schema": { "type": "boolean" } }
],
"requestState": "eyJjYXJ0IjoiYzE4Iiwic3RlcCI6ImNoYXJnZSJ9"
}
}
Two fields carry the weight. inputRequests is what to ask the user. requestState is an opaque blob the server hands to the client to hold — everything the server needs to resume, serialized and (in practice) signed or encrypted so the client can't forge it. The client shows the prompt, collects the answer, and then re-issues the original tool call, echoing the state straight back:
// Client → server: same call, now with answers + the echoed state
{
"jsonrpc": "2.0",
"id": 8,
"method": "tools/call",
"params": {
"name": "checkout",
"arguments": { "cart_id": "c18" },
"inputResponses": [ { "name": "confirm_charge", "value": true } ],
"requestState": "eyJjYXJ0IjoiYzE4Iiwic3RlcCI6ImNoYXJnZSJ9"
}
}
Because the server stuffed its resume-state into requestState and got it back verbatim, any instance can finish the call. The one that asked the question and the one that receives the answer don't have to be the same box. Nothing is held open; nothing is pinned. That's the whole trick — the state that used to live in a session now rides in the payload.
The mental model for a server author: your tool handler becomes a small state machine keyed off requestState. On the first call there's no state, so you do work until you hit a decision point, then return input_required with your state serialized. On the re-issued call you deserialize the state, read inputResponses, and continue. Keep the blob small, sign it, and set an expiry inside it — treat it like a cookie you don't trust the client to have kept honest.
Routable headers: how a plain gateway routes this#
Statelessness only pays off if the infrastructure in front of your servers is dumb and cheap. That's what the second change buys you. Every Streamable HTTP request in the new spec carries two required headers:
POST /mcp HTTP/1.1
Mcp-Method: tools/call
Mcp-Name: checkout
Content-Type: application/json
{ "jsonrpc": "2.0", "method": "tools/call",
"params": { "name": "checkout", ... } }
Mcp-Method and Mcp-Name duplicate — in the HTTP header — the JSON-RPC method and the tool name that are already inside the body. Trivial-looking, but it's the difference between a gateway that has to buffer and parse every JSON body to know what it's looking at, and one that routes, rate-limits, or shards by reading a header. You can send every tools/call for checkout to a warm pool, apply a stricter rate limit to one method, or split read and write traffic — all at the edge, with an nginx map or an API-gateway rule, never touching the body.
The catch that keeps it honest: a compliant server rejects any request whose headers and body disagree. The header is a routing hint, not a source of truth you can spoof past the server. Set both, set them correctly, or get a clean rejection.
The cache fields, so you can stop asking#
One more thing the held-open stream used to do: tell a client when tools/list changed. With no stream, list and resource-read results now carry explicit cache metadata — ttlMs and cacheScope, modeled directly on HTTP Cache-Control. The client caches the tool list for ttlMs and refetches on expiry instead of subscribing to change notifications. It's the same instinct as the rest of the revision: replace a long-lived connection with a value you put in the response.
What to actually do this week#
Three moves, in order of who owns them:
- If you own a server that elicits input (confirmations, missing params, human-in-the-loop): find every place you push a request over SSE and rewrite it as an MRTR return-and-resume. Serialize your resume state into a small signed
requestState; make the handler idempotent on retry. The step-by-step server diff is in our full stateless migration walkthrough — this piece is the why behind its Steps 2 and 5. - If you run a gateway or load balancer in front of MCP: start reading
Mcp-Method/Mcp-Name. Even if you don't route on them yet, logging them now gives you per-method traffic shape for free before the traffic arrives. - If you own a client: send both routable headers on every request, honor
ttlMson lists, and implement the re-issue loop — hold the original call so you can replay it withinputResponses.
MRTR is the least-discussed change in a spec full of big ones, but it's load-bearing: it's the reason "stateless" doesn't mean "can't ask the user anything." Get it right and your confirmation prompt survives being deployed behind the cheapest, most boring load balancer you can rent — which, per the rest of the stateless story, was the entire point.



