August 27, 2026 · The MCPCloud team
MCP v2 removed the handshake, and our runtime barely noticed
What the stateless MCP v2 spec (2026-07-28) changes, and how our generated servers came to serve both protocol eras on a single endpoint.
On July 28, the Model Context Protocol shipped the biggest revision in its history. MCP 2026-07-28 — informally, v2 — deletes the initialize handshake and the whole notion of a protocol session. Every request now stands alone: it carries its protocol version, client identity, and capabilities with it, and required headers describe it from the outside so any gateway in the path knows what it is without opening the body.
We spent the last two weeks adopting it across the platform, and the honest headline is that our runtime barely had to change. Servers generated on MCPCloud have been stateless since the first deploy — there was never a session store to tear out. So v2 landed as a dependency migration plus a set of edge and auth upgrades, not a rewrite, and a server deployed today serves both protocol eras from the same endpoint.
2025 protocol · before
- initialize (client to server)protocol version, client info, capabilities
- capabilities + session id (server to client)
- notifications/initialized (client to server)
- tools/call get_charge (client to server)session id carried along
- result (server to client)
A negotiation and a session id before the first tool call — and state on both sides for as long as the session lives.
MCP 2026-07-28 · after
- tools/call get_charge (client to server)Mcp-Method + Mcp-Name headers; version, client info, and capabilities ride in _meta
- result (server to client)server identifies itself in _meta
The first request is the tool call. Nothing to negotiate, nothing to store, nothing to expire.
What the 2026-07-28 revision actually changes
- The initialize handshake and protocol sessions are gone. Each request carries its protocol version, client info, and capabilities in _meta, and discovery moved to a plain server/discover request.
- Two required headers, Mcp-Method and Mcp-Name, describe every request from the outside, so gateways can route and police traffic without parsing JSON-RPC bodies.
- Server-initiated requests — elicitation, sampling, roots — are replaced by a multi-round-trip pattern: the server answers resultType: "input_required" and the client retries with what was missing.
- Catalog results become cacheable. tools/list and its siblings can carry ttlMs and cacheScope, so clients and intermediaries finally know how long a listing is good for.
- Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents: an OAuth client identifies itself with an HTTPS URL it controls instead of registering first.
If you run MCP servers behind shared infrastructure, the theme is unmistakable: the spec moved toward proxies, gateways, and edges. Every one of those changes makes a server easier to front, cache, and observe — which is to say, easier to run the way hosted servers are actually run.
A package swap, not a rewrite
The first thing adoption taught us had nothing to do with the protocol. If you go looking for a v2 of the MCP TypeScript SDK, you will not find one:
npm view @modelcontextprotocol/sdk version
# 1.30.0 — and it always will be
# v2 was renamed and split into scoped packages:
npm view @modelcontextprotocol/server version
# 2.0.0The old package is frozen at 1.30.0 forever; v2 was renamed and split into scoped packages — core, server, client, and framework adapters — all published at 2.0.0 the day before the spec landed. There is a second trap inside: the v2 server package still exports LATEST_PROTOCOL_VERSION as 2025-11-25, because that constant describes the legacy era it also supports. If your migration script greps for the version constant to confirm the upgrade, it will lie to you.
Past that, the migration was what a stateless design earns you. Our runtime never negotiated a session, so per-request _meta, header validation, and server/discover arrived by swapping the SDK and keeping our legacy leg intact. The behaviors the spec mandates — header-and-body agreement, refusing contradictions with -32020, the input_required shape — are enforced by the SDK itself. Our work was wiring the two eras together, not reimplementing a protocol.
The general rule
A protocol revision is priced by your state. If every request already carries everything the server needs, a stateless revision is a dependency bump. If your server leans on a session, it is a redesign. Statelessness is the cheapest insurance an implementor can buy.
One endpoint, both eras
A 2026-07-28 client connects to a generated server with no handshake: server/discover, then tools/list, then tools/call, with the server identifying itself in each response’s _meta. A 2025-era client hitting the same URL still gets its initialize negotiation, byte-for-byte what it got before. Neither client can tell the other exists.
Catalog responses now carry cache hints, and the scope is derived from how the deployment is configured: public deployments advertise a public cacheScope with a 60-second TTL, everything else stays private. A 2025-era response carries no cache fields at all — one era’s vocabulary does not leak into the other.
Elicitation is where dual-era gets concrete. When a tool needs the user to complete an OAuth flow mid-call, a legacy client still receives our -32042 error with its elicitation payload, while a v2 client receives resultType: "input_required" for the same condition. And because there is no session, the runtime mints no requestState — there is nowhere to keep one, which is rather the point.
The claim is pinned by a conformance suite that drives the real v2 client with its protocol pinned to 2026-07-28 and fallback forbidden — the tests cannot quietly pass by downgrading — next to a legacy-era suite asserting the old behavior did not move.
What the edge does with the new headers
Our dispatch layer sits in front of every deployed server, and v2’s headers were designed for exactly that position. The first thing we shipped was not routing but telemetry: the edge records the protocol signals on every request, so we knew the client mix before changing any behavior. Flipping a protocol default without knowing who calls you is how you break customers politely and silently.
POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_chargeThen routing: the edge will trust those headers enough to skip parsing the body — but only when the caller declares the 2026-07-28 protocol and the deployment is pinned to a runtime that validates header-against-body agreement. If either half of that bargain is missing, it fails closed and parses the body as before. A tampered Mcp-Name is refused by the runtime with -32020, so a header cannot lie its way past a policy the body would have failed.
We are deliberately not claiming a latency win from this yet. The fast path applies to v2 traffic, and v2 traffic today is a trickle. The plumbing is there for the day it is not.
The sharpest edge we hit was our own. Code Mode executions fan out sub-requests on the caller’s behalf, and the obvious implementation — clone the incoming headers onto each sub-request — becomes a protocol violation the moment the body changes: the cloned Mcp-Method contradicts the synthesized call, and a v2 runtime is required to refuse the contradiction. Sub-requests now re-derive their routing headers from the body actually being sent. If you operate an MCP gateway, audit every place you copy headers onto a request you composed.
The authorization server moved too
- Every authorization response now names its issuer (RFC 9207), so a client can detect an authorization-server mix-up instead of trusting the redirect.
- Clients can identify themselves with a Client ID Metadata Document — an HTTPS URL serving their metadata — with no registration step. Dynamic Client Registration still works, but in maintenance mode, with a janitor for the abandoned registrations it accumulates.
- Tokens are minted with the resource the client asked for bound into the audience (RFC 8707).
Where this is honest about its limits
MCP v2 support is beta, and our capability list says so in those words. The status lives in one source of truth in the codebase, next to a comment recording the pass that earned it: on August 24, the pinned v2 client ran the full no-handshake flow against a real deployed staging server — wise-hornbill-230 — received its access-mode-derived public cache scope, and had a tampered Mcp-Name refused with -32020, while a 2025-era client on the same endpoint saw nothing change.
Beta and not live, because no production server has been redeployed onto a v2-capable runtime yet. A server deployed today gets runtime 0.15.0 and serves both eras out of the box; existing servers pick it up on their next redeploy. The legacy era is not going anywhere either — the spec itself floors support at twelve months. And some of our own surfaces, the CLI among them, still speak the 2025 protocol to servers that also speak the new one, which is precisely what dual-era serving is for.
One last change exists purely because claims need checking. During the rollout we caught a staging server labeled with a runtime version several releases newer than the bytes it was actually serving — it had drifted without anyone noticing. As of 0.15.0, every deployed server self-reports the runtime it is really running, so “this server speaks v2” is a thing we verify against the server, not against a deploy log.