Tutorial
What is Stateless MCP? 2026 stateless architecture, JSON-RPC, and Remote Server explained
The 2026-07-28 MCP spec makes the protocol layer stateless: every JSON-RPC request carries its own version and capabilities, and Remote Servers can run behind ordinary HTTP load balancers. Tool arguments are still JSON — validate them locally in production.
Model Context Protocol (MCP) lets AI clients discover tools, resources, and prompts and wire them into model context. The biggest 2026 architectural shift moves MCP from a bidirectional stateful protocol — handshake first, then a Session ID — to stateless JSON-RPC where every request is self-describing and independently routable. If you already call Remote MCP Servers from Claude Desktop, Cursor, or a home-grown Agent, this change hits deployment, scaling, and gateway rate limits directly. When you finish, you can separate protocol statelessness from application state — and you still know tool argument JSON needs a local check.
What MCP and statelessness solve
MCP answers how a model can call external capabilities safely and discoverably. Clients (Claude, ChatGPT, IDE Agents) need a standard way to list your tools, read resources, and pull prompt templates; servers (GitHub, databases, internal APIs through an MCP adapter) need a standard way to expose those capabilities without a custom plugin per client.
Early MCP kept sessions at the transport layer: the client first initialize, the server returns a capability list, and later requests must carry Mcp-Session-Id, pinning traffic to one instance or a shared Session Store. That is fine for local stdio; once a Remote Server must scale horizontally, run on Cloud Run / Lambda, or pass through an API gateway for per-tool rate limits, session stickiness becomes the bottleneck.
The 2026-07-28 spec (Release Candidate) makes the protocol layer stateless: metadata needed to handle any request lives in the request itself, so any instance behind ordinary round-robin load balancing can take it. Official notes: MCP 2026-07-28 spec announcement and the Statelessness section.
What the stateful era left behind
In the old flow, Streamable HTTP clients usually ran a handshake first:
- Send
initialize, exchanging protocol version and client/server capabilities. - Receive the
initializednotification; the server returns theMcp-Session-Idresponse header. - After that,
tools/callandresources/readmust carry the same Session ID, or the gateway or instance memory cannot find the context.
Typical production costs: load balancers need sticky sessions; replicas share Sessions in Redis; after a Serverless cold start old Sessions die; popular servers like GitHub MCP once had to maintain a Redis layer. Google, in Scaling AI Agent Infrastructure, called this change “the biggest spec shift since MCP launched” — the core move is dropping transport-layer session management.
| Dimension | Stateful era (2025 and earlier) | Stateless core (2026-07-28) |
|---|---|---|
| Handshake | initialize / initializedRequired |
Retired; optionalserver/discover |
| Session identifier | Mcp-Session-Id response header |
Removed (SEP-2567) |
| Capability negotiation | Exchanged once when the connection opens | Each request's _meta carries them |
| Horizontal scaling | Sticky routing + shared Session Store | Ordinary round-robin is enough |
2026-07-28 stateless core
The spec's definition of “stateless” is strict: the server must not rely on earlier requests on the same connection to infer protocol version, client identity, or capabilities; every request must carry this information in _meta. Requests from multiple tasks, threads, or conversations may interleave on the same transport; the connection or stdio process itself is not a session boundary.
The client carries in each request's params._meta (or equivalent):
io.modelcontextprotocol/protocolVersion— required, e.g.2026-07-28.io.modelcontextprotocol/clientCapabilities— required; an empty object means no optional capabilities.io.modelcontextprotocol/clientInfo— recommended for logging and debugging (servers should not use it for security decisions).
If the client wants server capabilities first, it can call the new server/discover RPC, but that is not required — any request can be the first one on any instance. Servers can also add tools/list to responses like ttlMs, so clients cache the tool list within the TTL and skip repeated discovery calls.
Business state that must survive multiple tool calls (shopping cart, browser session, ticket draft) should not hide in a transport Session. Treat it like a normal HTTP API: the tool returns an explicit handle (basket_id and draft_id), and the model passes it back in later tools/call argument JSON. The model can see the handle, which is easier to debug than a black-box Session.
How JSON-RPC runs in MCP
The MCP message layer is always JSON-RPC 2.0: every request has jsonrpc and id and method and params; responses carry result or error; notifications have no id. That is the same shape as “tool name + argument object” in the Apple Agent article — MCP just standardizes the method name to tools/call, with name and the arguments.
A typical stateless tools/call looks like this (HTTP headers in the next section):
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: searchInvoices
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "searchInvoices",
"arguments": {
"status": "unpaid",
"limit": 10
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "jsonvue-demo",
"version": "1.0.0"
}
}
}
}
On success, result usually holds a content array of tool output (often a type: text JSON string or structured block). On failure, JSON-RPC error carries code and the message; under Streamable HTTP, if HTTP headers and the body method/name disagree, the spec requires a -32020-class header mismatch error.
For JSONVue readers, the field worth watching is arguments: external Agents often send numbers as strings or drop required keys. MCP going stateless does not validate business JSON for you — the same split as Structured Output for model output and MCP for tool calls. The on-site Apple AI agents and JSON article explains: the same domain function can serve App Intent, Foundation Models Tool, and MCP; only the adapter layer differs.
Remote Server and Streamable HTTP
A Remote MCP Server is an MCP endpoint the client reaches over HTTPS, not a local stdio child process. Streamable HTTP is the main transport for Remote deployment today: one POST can complete an RPC; long tasks may return an open notification stream, but state stays scoped to that request, not a connection-level Session.
Starting 2026-07-28, Streamable HTTP requests must carry three headers aligned with the body (SEP-2243), so gateways, WAFs, and rate limiters can route without parsing the JSON body:
MCP-Protocol-Version— must match_metaprotocolVersion, else 400.Mcp-Method— maps to JSON-RPCmethod, e.g.tools/call.Mcp-Name— tool, prompt, or resource name, e.g.searchInvoices.
Deployment gets simpler: the same Docker image with multiple replicas behind ordinary ALB/nginx round-robin; Cloud Run / Cloud Functions scale on demand without a Redis Session layer just for MCP; per-Mcp-Name QPS quotas are cheaper than deep body inspection. Production services like GitHub MCP Server are already upgrading to the stateless spec.
Local stdio Servers still work, but the spec is clear: unrelated requests may interleave on the same stdio process, and the Server must not treat process identity as a session ID. Local development and cloud Remote should share the same tool implementation — only the transport adapter differs.
Applications can still be stateful
“Protocol statelessness” does not mean “your business is stateless.” Shopping carts, multi-step approvals, half-filled browser forms can and should stay stateful — but state must be explicit, not tied to Mcp-Session-Id.
Recommended pattern:
- First tool call creates the resource and returns
{ "draftId": "dr_8k2", ... }. - Tool description should say later steps must pass
draftId. - Server looks up
draftIdin DB or cache; on a miss return a JSON-RPC business error, not a mysterious Session 404.
For long tasks, Tasks and other extensions support MRTR (Multi-Request Task Routing): a tool can return status: input_required first; the client attaches the user's follow-up in later requests' _meta to continue. Still request/response on a stateless protocol — the response may just span multiple rounds.
How this relates to Structured Output
MCP and Structured Output solve different layers, but JSON shapes often meet in the same Agent pipeline:
| Layer | Mechanism | What it constrains |
|---|---|---|
| Model output | Structured Output + JSON Schema | Fields and types in the model's final answer or extraction |
| Tool call | MCP tools/call + tool inputSchema |
The arguments object sent to the Server |
| Business API | REST / GraphQL JSON body | Real payload inside the Server or downstream HTTP |
Best practice: maintain one field table, generate MCP tool inputSchema, REST OpenAPI, and Structured Output Schema for the model from it. On-site the Gemini API JSON output guide covers the model side; the Gemini Structured Output tutorial has cloud examples. After MCP goes stateless, clients may cache tool lists — when the Schema version changes, bump the tool name or protocol version so stale caches do not send the wrong shape into arguments.
How to inspect JSON in production
When debugging a Remote MCP Server, put three JSON documents side by side: the client's tools/call arguments, the HTTP body your domain service receives, and the result.content the tool returns to the model. When shapes disagree, the bug is almost always in the adapter layer, not “the model isn't smart enough.”
Walk through it in the browser: JSON formatter to confirm it parses; JSON validator to catch trailing commas and type errors; use JSON Schema to validate fields shared by tool inputSchema and API body; JSON DiffCompare “model arguments” with “actual HTTP request body”. Keep three fixtures: mcp-args.valid.json and http-body.valid.json and mcp-tool-error.json, run the same Schema in CI.
FAQ
Does stateless MCP still need a WebSocket long connection?
Remote deployment is mainly Streamable HTTP: one POST completes an RPC, and long notification streams are request-scoped response streams, not the old “handshake then bind Session” connection state. Local stdio is still a long-lived process, but each request is independent at the protocol level.
Can old clients with Mcp-Session-Id still connect to new Servers?
2026-07-28 Servers no longer recognize a protocol-level Session ID. Clients must upgrade to send protocolVersion and clientCapabilities in _meta on every request, plus the required HTTP headers. When mixing versions, split traffic at the gateway by MCP-Protocol-Version.
Must tools/list run every time?
No. Servers can return ttlMs in the response; clients cache within the TTL. When tools or Schemas change, shorten the TTL or change the tool name/version to avoid a stale list.
Does MCP validate arguments for the Server?
Tools may declare inputSchema, but the Server must still validate server-side. External Agents often send wrong types; a stateless protocol does not reduce those errors. A structured JSON-RPC error helps the model retry more than a silent 500.
Summary and next steps
Stateless MCP brings 2026 Remote Servers back to ordinary HTTP operations: JSON-RPC 2.0 carries methods, _meta carries protocol context, Mcp-Method / Mcp-Name headers let gateways read the traffic. initialize and Mcp-Session-Id step aside for any-instance-any-request, Serverless-friendly deployments and simpler per-tool rate limits.
Pass business state as explicit IDs in arguments; still validate JSON contracts locally. Next: check your Remote endpoint against the 2026-07-28 spec for complete _meta and HTTP headers; pin MCP arguments and REST bodies to one Schema; use JSONVue in the browser to verify round-trip JSON.