Stateless MCP: How the Protocol Finally Learned to Scale

MCP's 2026-07-28 spec drops handshakes and session IDs. Here's why stateful transports broke cloud-native agents, and what the new request model actually changes for builders.

Abstract diagram of self-contained requests flowing through a load balancer to interchangeable server nodes
R

Rajkumar

Software Engineer

MCP just shipped its biggest change since launch, and the headline is almost boring on purpose: the protocol is now stateless. No more initialize handshake. No more Mcp-Session-Id. Every request carries what it needs.

If that sounds like "just how HTTP works," you're right. The interesting part is how long it took the ecosystem to get there, and what broke while we pretended sessions were fine.

I've watched remote MCP servers go from "cool local stdio trick" to production agent infrastructure in under two years. The pain was predictable. Sticky sessions. Redis for session lookup. Pod restarts that nuked active chats. Cloudflare Durable Objects holding protocol state because the protocol demanded it. Google Cloud teams hitting the same wall at millions of concurrent queries.

The 2026-07-28 specification is the reset. This post is what that reset means if you build or run MCP servers.

The Old Flow Was Elegant Until You Scaled It

Early MCP was designed for a single client talking to a single server, usually over stdio on one machine. That model negotiated capabilities once, kept a session, and reused it for tools, resources, and prompts. For local agents, it felt clean.

When that same model moved to remote HTTP, the handshake stayed:

  1. Client POSTs initialize
  2. Server mints a session ID and returns it
  3. Every follow-up request must carry that ID
  4. The ID effectively pins you to the instance that remembers the session

That pin is the whole problem. Put three pods behind a normal load balancer and the second request lands on a different pod. That pod has never heard of your session. You get 400 Session Not Found. Crash one pod and every client glued to it fails the same way.

flowchart LR
    C[Client] -->|initialize| LB[Load balancer]
    LB --> A[Pod A<br/>owns session]
    LB -.->|next request| B[Pod B<br/>session unknown]
    B --> E[400 Session Not Found]

Workarounds existed. Sticky affinity at the balancer. Shared Redis for session stores. Gateway-level packet inspection. They all added latency, cost, and operational surface area for something most tool calls never needed.

As SEP-2575 puts it: modern cloud systems favor services that can process each request in isolation. MCP's mandatory handshake fought that design.

What Stateless Actually Means in the New Spec

Two proposals did the heavy lifting:

  • SEP-2575 removes the initialize / initialized handshake
  • SEP-2567 removes the protocol-level Mcp-Session-Id and the session that came with it

Protocol version, client info, and client capabilities no longer live in a one-time negotiation. They ride along in a _meta field on every request. Optional discovery moves to server/discover when a client wants server capabilities first. Otherwise a client can just call a tool and go.

A tool call under 2026-07-28 looks like ordinary, self-describing HTTP:

POST /mcp HTTP/1.1
Host: mcp-server.example
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": { "q": "otters" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-app",
        "version": "1.0"
      }
    }
  }
}

Any healthy instance can handle that request. Round-robin is fine. Autoscaling is fine. A crashed pod is invisible to the next call. Serverless platforms can scale to zero because nothing needs an open connection just to "keep the protocol alive."

Cloudflare's write-up makes the deployment implication explicit: MCP no longer requires Durable Objects to speak the protocol. Keep Durable Objects when your app needs state. Stop using them as a tax for protocol sessions.

HTTP Headers and Caching Catch Up

Stateless alone was not enough. Gateways still needed a way to route, rate-limit, and audit without deep-parsing JSON bodies.

SEP-2243 promotes key fields into HTTP headers mirrored from the JSON-RPC payload:

  • MCP-Protocol-Version
  • Mcp-Method (for example tools/call)
  • Mcp-Name (the tool, prompt, or resource name)

If headers and body disagree, the server rejects the request. Proxies can make decisions from headers alone. That is a real latency and security win at the edge.

The spec also borrows HTTP caching ideas. List and read results can return ttlMs and cacheScope, so clients know how long a tools/list response stays fresh and whether it is safe to share across users. You no longer need a long-lived SSE connection just to watch whether the tool catalog changed.

State Did Not Disappear. It Moved.

People ask the right question immediately: what if I actually need state?

You still do, for shopping carts, browser sessions, multi-step workflows, and long jobs. The difference is ownership. Protocol state is gone. Application state is yours.

Mint an explicit handle the same way HTTP APIs always have. A basketId, browserId, or jobId becomes an ordinary tool argument on later calls. You choose Redis, Postgres, object storage, or Durable Objects based on the product, not because the protocol forced a sticky session.

That "pay as you go" model is the design principle behind SEP-2575: default to self-contained requests, prefer state references when needed, and treat long-lived streams as a last resort.

How Servers Ask Clients Questions Without a Stream

Elicitation used to need an open stream so the server could push Are you sure? back to the client. That meant holding connections open, dealing with timeouts, and sometimes prompting users out of band. Bad UX and a messy security surface.

Multi Round-Trip Requests (MRTR / SEP-2322) rebuilds that flow as discrete steps:

  1. Client calls a tool
  2. Server returns an inputRequired / input_required result plus a serialized requestState
  3. Client gathers the answer from the user
  4. Client retries with inputResponses and the echoed requestState
  5. Any server instance can resume because the state rides in the payload
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: tools/call delete_files
    Server-->>Client: inputRequired + requestState
    Note over Client: Prompt user for confirmation
    Client->>Server: tools/call + inputResponses + requestState
    Server-->>Client: final result

Confirmation prompts, color pickers, refund approvals: same pattern. No sticky stream. No "which pod owns this elicitation?"

Long Jobs Get a First-Class Escape Hatch

Some tools take 10 to 60 seconds. Holding the HTTP call open blocks the conversation and piles up connections.

Tasks graduate from experimental to a real protocol extension. Kick off work, store task state in shared storage, return a taskId immediately, then let the client poll with tasks/get or subscribe for updates. The agent can tell the user "refund is processing" and keep moving.

That matches how good APIs already behave. MCP stopped fighting that pattern.

Deprecations, SDKs, and the Migration Reality

This is not a quiet patch. Roots, Sampling, and Logging enter formal deprecation, with a minimum 12-month window before removal. Dynamic Client Registration and legacy HTTP+SSE also move onto a clearer lifecycle. Auth hardens around issuer verification (RFC 9207) and resource indicators (RFC 8707).

Tier-1 SDKs shipped beta or day-one support for 2026-07-28. TypeScript splits the old monolithic package into focused client and server libraries, with a codemod for common renames. Python has an mcp v2 beta. Cloudflare Agents SDK can use createMcpHandler without McpAgent for protocol sessions.

Expect a real upgrade, not a one-line bump. The trade is worth it: MCP starts behaving like the rest of your HTTP estate.

Closing: Boring Infrastructure Is the Feature

Stateful MCP made local demos delightful and production fleets expensive. Stateless MCP makes load balancing boring, failover invisible, and serverless a default option instead of a special case.

That is the whole point. Agents need tools that scale like normal web services: independent requests, explicit app state, headers gateways understand, and async work that does not hostage a connection.

If you are building remote MCP today, treat 2026-07-28 as the new baseline. Read Google's scale write-up, Cloudflare's Workers angle, and SEP-2575. Then delete the session store you never wanted.

The protocol finally matches the infrastructure we already know how to run.

Get new posts by email

Get notified when I publish new content. No spam, unsubscribe at any time.