MCP Goes Stateless: Why the 2026-07-28 Spec Landed at Exactly the Right Time
The 2026-07-28 revision of the Model Context Protocol deletes the stateful handshake — no initialize, no session IDs, no session store. Why that change arrived precisely when agent infrastructure needed it, what it looks like on the wire, and what migrating actually involves.
The 2026-07-28 revision of the Model Context Protocol is the largest change to MCP since it launched — and it is fundamentally a deletion. The stateful handshake is gone from the protocol: no initialize round trip, no Mcp-Session-Id header, no server-side session store. Every request is self-contained.
If you build agents that consume tools — or run an MCP server that agents depend on — this revision changes where your code can run, how intermediaries treat your traffic, and what your authentication layer must look like. This piece covers why the change matters, why it arrived at exactly the right moment, and what a migration involves in practice, based on the spec, its changelog, and early production migration reports.
Why the Timing Is Right
The 2025-era protocol made one foundational design choice: a client opens with initialize, receives a session identifier, and carries it for the lifetime of the connection. That choice was reasonable when MCP clients were long-lived desktop applications. It aged badly as agents moved to where they actually run in 2026.
Consider what session affinity leaked into, downstream of that one decision:
- Sticky load-balancer routing. A session created on instance A had to keep hitting instance A, or every instance had to share a session store.
- Body-parsing gateways. An intermediary that wanted to route or rate-limit by method had to parse JSON-RPC bodies, because the envelope carried nothing routable.
- Serverless impedance mismatch. An agent runtime calling tools from a short-lived function had to emulate a long-lived client — handshake, session, teardown — for what was semantically a single request.
- Re-fetch churn. With no protocol-level caching semantics, agents re-fetched tool catalogs every session, adding a latency round trip before the first useful call.
Meanwhile, the dominant deployment pattern for agents shifted decisively toward ephemeral compute: serverless functions, per-request container spin-up, horizontally scaled fleets behind round-robin balancers. The stateful handshake was a protocol-level assumption that the infrastructure no longer satisfied. The 2026-07-28 revision resolves the mismatch by moving identity and negotiation into each request, which yields three immediate properties:
- Any instance answers any request. Round-robin works. Cold starts stop being a session problem.
- Intermediaries get smarter without getting invasive. Header/body agreement (SEP-2243) means a gateway can route and cache on
Mcp-Method/Mcp-Nameheaders without touching the body. - Caching becomes explicit. List results carry
resultType,ttlMs, andcacheScope, so a client can stop re-fetching a tool catalog on every turn.
For agent builders specifically, discovery is the headline: server/discover is now a MUST, so an agent can enumerate a server's full surface without a handshake round trip. That is one less latency hit per session and one less client-side state machine to get wrong. If you've internalized the failure modes of tool use in LLM agents, you'll recognize this as removing an entire class of them — the ones caused by stale or missing session state — at the transport layer.
What the New Era Looks Like on the Wire
A modern request is ordinary JSON-RPC plus a _meta envelope and a routing header. Protocol version, client info, and client capabilities travel on every call:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
Mcp-Method: tools/list
The spec is deliberately strict about malformed requests, and the error codes are specific:
| Failure | Error code | Meaning |
|---|---|---|
Missing envelope field (e.g. clientCapabilities) | -32602 | Invalid params, naming the missing field |
Header/body disagreement (missing or wrong Mcp-Method / Mcp-Name) | -32020 | Intermediary-visible routing metadata doesn't match the body |
| Unsupported protocol version | -32022 | Returned with the server's supported version list |
The strictness is the point. Intermediaries can only trust headers if servers refuse requests where headers and body disagree — so failures are loud, and silent fallbacks are off the table.
Discovery and Cache Hints
server/discover returns the server's surface stamped with cache metadata from server-configured hints:
new McpServer(info, {
capabilities: { tools: {} },
cacheHints: {
'tools/list': { ttlMs: 60_000, cacheScope: 'private' },
'server/discover': { ttlMs: 60_000, cacheScope: 'private' },
},
});
Warning
cacheScope is a security decision, not a performance knob. If your tool list is filtered by the caller's OAuth scopes, it differs per principal — and marking it 'public' lets a shared cache serve one tenant's tool surface to another. That is a cross-tenant leak hiding inside a performance feature. The SDK default (ttlMs: 0, cacheScope: 'private') is safe; the moment you tune hints, audit every one.
MRTR: What Replaces Server-to-Client Callbacks
Server-initiated callbacks — sampling, elicitation, roots — cannot exist in a stateless world, because the server has no channel back to the client between requests. The 2026-07-28 spec replaces them with multi-round-trip requests (MRTR): a handler returns inputRequired, and the client re-issues the request with answers attached. State that used to live in a session now rides in _meta as requestState.
That relocation has a security consequence worth internalizing: requestState comes back from the client, which makes it attacker-controlled input. If it influences anything server-side, it must be integrity-protected — the v2 TypeScript SDK ships createRequestStateCodec for HMAC-sealing it. Read-only tool servers can defer MRTR entirely; anything write-heavy or interactive should treat MRTR adoption as a redesign, not a port.
The Migration Path Is Dual-Era, Not Flag-Day
The v2 SDK did not ship as a major-version bump. It's a new package — @modelcontextprotocol/server — that lives alongside the v1 line, and its core is a handler factory with dual-era serving built in:
const handler = createMcpHandler(
(ctx) => buildServer(actorFrom(ctx.authInfo)), // fresh instance per request
{ legacy: 'stateless' }, // the default — serve 2025-era clients too
);
The routing logic is simple: an initialize body goes to the legacy leg; a _meta version envelope goes to the stateless leg. Both eras are answered from the same server definition, so they cannot drift apart — arguably the most important design decision in the SDK, because most clients in the wild still speak 2025-11-25. A flag-day cutover is how you get paged; dual-era is the only sane rollout. (legacy: 'reject' exists for strict modern-only endpoints — useful in CI to prove you aren't quietly leaning on the fallback.)
Two more properties of the SDK worth knowing before you plan the work:
- Authentication stays outside the SDK. Token verification happens in your resource-server layer; the authenticated principal passes through
authInfo. The SDK does no token verification of its own — a clean separation you should preserve. - Schemas are Standard Schema + JSON Schema. Tool inputs are defined with Standard Schema validators that also expose JSON Schema conversion — in practice, zod 4.2+ today. The payoff: tool schemas compile to full JSON Schema 2020-12 (
oneOf,anyOf,$ref), so you can stop flattening rich inputs into bags of optional strings. If your codebase is still on zod 3, budget for the dependency work — it is a real cost of the migration, not a footnote.
Note
What stateless costs: per-request server construction (the SDK memoizes schema conversion to absorb it), double the test surface while you serve both eras, and session state relocated into _meta — where it must be treated as untrusted input.
The Auth Layer Has to Exist First
The protocol migration is half the work. The 2026-07-28 authorization spec is where agents actually connect, and it composes several OAuth building blocks with sharp edges:
- Protected Resource Metadata (RFC 9728) is a MUST. Your 401 responses must point at it via
WWW-Authenticate: Bearer resource_metadata="…", and it must live at the well-known path carrying your resource's suffix (e.g./.well-known/oauth-protected-resource/api/mcp). - Authorization Server metadata (RFC 8414) at the origin root. Clients probe the root — including the strict path-insertion form (
/.well-known/oauth-authorization-server/<as-path>). If your auth server mounts metadata under a base path, mirror it at the root, and enable CORS for browser-based clients. scopebelongs in the 401 challenge. Clients use it to decide what to request on the first handshake. Andoffline_accessmust not appear in the resource metadata'sscopes_supported— refresh tokens are not a resource requirement.- Dynamic Client Registration (RFC 7591) is deprecated in favor of Client ID Metadata Documents (CIMD), where the
client_idis an HTTPS URL hosting its own metadata. CIMD is SHOULD-level; DCR remains valid for backwards compatibility, so keeping both while CIMD support matures upstream is a defensible position. - Issuer validation (RFC 9207) is effectively required — advertise
authorization_response_iss_parameter_supportedand validateisson callbacks.
Two operational details that early migrations consistently report: scope allow-lists must include the mundane OIDC scopes (profile, email) or mainstream agent hosts fail registration with a generic error; and default rate limits on registration endpoints produce the identical user-facing failure under client retry storms. Both present as "the client is broken" when neither is.
A Verification Checklist
Adapted from a production migration walkthrough — steal it:
- Legacy leg.
initializewithprotocolVersion: 2025-11-25negotiates, andtools/listreturns the full catalog. Old clients keep working untouched. - Modern leg.
server/discoverwith the full_metaenvelope answers withsupportedVersions,resultType,ttlMs,cacheScope; thentools/listand a realtools/call(withMcp-Name) round-trip actual data. - Negative paths. Wrong protocol version →
-32022. Missing envelope field →-32602. MissingMcp-Method→-32020. No token → 401 carrying bothresource_metadataandscope. If any failure is silent, you're not done. - A real client. Point an actual agent host at the endpoint and complete the full OAuth dance: registration, login, consent, token exchange, first tool call. Migration reports agree that the last stubborn bugs live in this step — often in the sign-in and consent UX, not the protocol — so budget for it explicitly.
What to Take Away
The 2026-07-28 revision is the moment MCP stopped assuming its clients are long-lived desktop processes and started matching how agents are actually deployed: ephemeral, horizontally scaled, behind caches and gateways. Statelessness isn't a feature bolted on — it's the removal of the one assumption that made MCP servers operationally interesting, in the bad sense.
The practical read for teams: the protocol change is mechanical if you go dual-era through the v2 SDK, the schema layer forces a zod 4 (or equivalent Standard Schema) decision, and the OAuth discovery stack is the real project — it must be complete and spec-precise before any mainstream agent host can connect at all. Done in that order, nobody's client breaks on the way there, and what you end up with is the difference between a demo transport and production infrastructure.
References
- MCP 2026-07-28 specification and changelog — the normative source
- Authorization and Client Registration — including the CIMD transition
- MCP TypeScript SDK — v1 and v2 packages side by side
- Build an MCP Server on the 2026-07-28 Spec: The Stateless Guide — a full migration walkthrough
- RFC 9728 (Protected Resource Metadata), RFC 8414 (AS Metadata), RFC 9207 (issuer identification), RFC 8707 (resource indicators), RFC 7591 (DCR) — the OAuth layers the spec composes
- OAuth Client ID Metadata Documents (draft) — DCR's designated successor
- Standard Schema and zod 4 — the validator contract behind tool schemas