Stateless MCP server: sessions, Mcp-Session-Id, and handles
A stateless MCP server keeps no session between requests. What Mcp-Session-Id does, what 2026-07-28 removed, and where state goes instead.

A stateless MCP server keeps no session between HTTP requests: each request carries everything the server needs to answer it. In the 2025-11-25 revision, a Streamable HTTP server may assign a session ID, Mcp-Session-Id, at initialization. The 2026-07-28 revision, the current one, removes protocol sessions and that header, and servers that need state across calls pass explicit handles as ordinary tool arguments.
The protocol facts come from the MCP 2025-11-25 transports page, the 2026-07-28 changelog and tools pages, the versioning page, and the MCP Inspector's protocol eras page, read on 2026-09-28. The worked example is Sume's hosted MCP server, from its current code and the Jobs and results docs; Sume's basics page says hosted MCP still works but is not the primary path today.
What changed between the 2025-11-25 and 2026-07-28 revisions?
In the handshake-era revisions, an MCP session is the set of related interactions between a client and a server, beginning with initialization. The 2026-07-28 revision makes MCP stateless: every request carries its protocol version and client capabilities in _meta, and the MCP Inspector describes a modern connection as sessionless and per-request.
| Topic | 2025-11-25 | 2026-07-28 |
|---|---|---|
| Session | Server MAY assign MCP-Session-Id at initialization; the client sends it on every later request | No protocol-level sessions; Mcp-Session-Id removed |
| Handshake | initialize, then notifications/initialized | Removed; version and capabilities ride on each request |
| State across calls | Can live in a session the server establishes | Explicit, server-minted handles passed as tool arguments |
| Broken stream | A server MAY make it resumable with Last-Event-ID | Not resumable; the client re-issues the request with a new ID |
How does Mcp-Session-Id work, and why is my session not found?
On a 2025-11-25 Streamable HTTP server, the session ID arrives in an MCP-Session-Id header on the response that carries the InitializeResult. From then on:
- The client MUST include that header on all of its later HTTP requests.
- A server that requires a session SHOULD answer a request without the header, other than initialization, with
400 Bad Request. - A server may end the session at any time. After that it MUST answer that session ID with
404 Not Found, and the client MUST start a new session with a newInitializeRequestthat carries no session ID. So a session-not-found error calls for a fresh initialization, not a retry with the old ID. - A client that is done SHOULD send HTTP
DELETEwith the header; the server MAY answer405 Method Not Allowed.
How do I keep state without a session?
Return a handle. The 2026-07-28 tools page's guidance: a server that needs state across calls, such as a shopping cart, an open browser context, or a database transaction, returns an explicit handle from a creation tool and accepts it as an argument on later calls. The model carries the handle forward, and the server looks the state up on each call. The spec's design notes for handles come first, then its shopping-cart example:
- Authorization: a handle is a name, not a capability, so check the caller's rights against it on every call.
- Lifetime: state the retention policy in the creation tool's description, where the model can see it.
- Expiry: a call with an expired or unknown handle should return a tool execution error that says so.
// → tools/call
{ "name": "create_basket", "arguments": {} }
// ← result
{
"content": [{ "type": "text", "text": "Created basket bsk_a1b2c3" }],
"structuredContent": { "basket_id": "bsk_a1b2c3" }
}
// → tools/call
{ "name": "add_item", "arguments": { "basket_id": "bsk_a1b2c3", "sku": "..." } }Is Sume's MCP server stateless?
At the protocol level, yes. In current code, Sume's hosted server at https://mcp.sume.com/mcp handles each POST on its own with a 60-second deadline, answers with a JSON body, and sets no session ID. It still speaks the handshake era: it negotiates 2025-03-26, 2025-06-18, or 2025-11-25 through initialize.
Long work already uses handles. In current code generate_video answers with a job id, never a clip URL, and the agent passes that id to jobs_wait and jobs_result. The docs say jobs_wait takes 1 to 20 ids and holds at most 55 seconds per call, and unknown or foreign-workspace ids fail the whole call. MCP tool call timeouts on long-running video jobs covers the wait loop.
Will a 2026-07-28 client connect to a handshake-era server?
Only if it falls back. The 2026-07-28 transports page says clients and servers that interoperate with earlier revisions detect the other side's era and fall back. On Streamable HTTP, the version also travels in the MCP-Protocol-Version header.
Sume's server shows why the fallback matters. In current code it answers an MCP-Protocol-Version header of 2026-07-28 with HTTP 400, so a client pinned to that revision alone fails there, while one that falls back to initialize can connect. The MCP Inspector's legacy, auto, and modern era settings are covered in How to test an MCP server, and the codes Sume answers with in MCP error codes.
Sources
- MCP specification 2025-11-25: Transports (read 2026-09-28)
- MCP specification 2026-07-28: Key changes (read 2026-09-28)
- MCP specification 2026-07-28: Tools (read 2026-09-28)
- MCP specification 2026-07-28: Transports (read 2026-09-28)
- MCP specification: Versioning (read 2026-09-28)
- MCP Inspector: Protocol eras (read 2026-09-28)
- Jobs and results
- MCP overview
- Sume basics
Related posts
More in Developers
- Synthesia API documentation: the quickstart, step by step
Synthesia's API docs make a video in four steps: a Legacy (v2) key, POST /v2/videos, poll until complete, then download from a time-limited link.
- Text to image API: send a prompt, get image URLs back
A text-to-image API turns a prompt sent over HTTPS into generated images. How to call one: the request, the response, slow jobs, and the cost.
- Text to speech API in Java: pick a voice, save the MP3
Call a text to speech API from Java: choose a voice selector and an audio format, POST the text, wait for the job, then write the MP3 to disk.
- Text to video and image to video: how they differ
Text-to-video invents every frame from words; image-to-video starts on your picture and animates it. How they differ, and how one request does both.
Written by Sume