The current design of the MCP session model assumes tool invocations which live only for a limited amount of time. A stateless approach is needed to solve issues which occur due to long-running agents which operate via distributed architecture.
Consider a case where an agent has been running for six hours – it queries databases, invokes APIs, waits for the results from a background process, and survives a network interruption. There is no problem with the state of this agent itself, but one of the MCP servers which the agent connects to has just restarted 20 minutes ago.
Currently, MCP (spec 2025-11-25) starts each client/server interaction with an initialize handshake. The server may provide an MCP-Session-Id, which the client is supposed to include in all further requests. When the server closes that session, the client receives a 404 response and is forced to start the process a new session.
Here lies the problem: the term "session" acquires at least four distinct interpretations:
These sessions have quite disparate lifespans — the transport session can last just a few seconds, while an application session might require several hours, and an agent task may take days. MCP makes them use the same lifecycle mechanism. Moreover, the clients do not have a consistent definition of session scope: some define it per-tool invocation, others per-app startup, still others per-page load. Very few support session recovery after the restart.
Stateless MCP Depiction with Long Running Agents
Not that servers can’t keep anything – that servers don’t require data from previous interactions in order to understand the request being made. Every request must either contain all data necessary for its processing or explicitly state the application state.
This is formally specified by two SEPs:
MCP-Session-Id header altogether.Both of these are in the draft specification. The live specification is still 2025-11-25 — this is a breaking change designed for a future version, not one to expect from today’s clients.
A client that wishes to look at a server before calling it can do so via a new RPC called server/discover it is an optional RPC, not a required handshake. “Pay as you go”: simple calls are simple.
Now, when the tool really does need some state between calls, it explicitly provides a handle which is passed by the client, instead of using an implicit session behind the scenes.
connect_database() → { "connection_id": "conn_8Kp31..." }
query(connection_id: "conn_8Kp31...", sql: "SELECT ...")
close_database(connection_id: "conn_8Kp31...")
And for agents, here comes the true benefit:
The one catch: agent platforms need to preserve live handles during context compaction, or the server-side state they point to becomes orphaned.
Instead of routing each request to the single replica responsible for the session, a stateless request can be sent to any live replica:
MCP Shared State Inner Workings - Explained
That gives you:
tools/list and similar responses will not vary per session, which is critical for multi-agent setups where currently each sub-agent is forced to fetch the tool definition on every sessionMore metadata is added to the request.
There has to be a mechanism of maintaining backward/forward compatibility when moving from the old client side to the new one.
Handles have to receive full security treatment: the authenticated server has to verify both the handle and the caller's identity each time; for the unauthenticated server, the handle is essentially a bearer token that needs proper randomness, TTLs, and cleanup.
Agent needs to handle these securely while summarizing or passing control.
Agent owns the workflow. MCP delivers the requests. Tools own application resources. The real power of the stateless MCP comes from its unwillingness to allow those three lifecycles to converge into one fragile session. This assumption becomes false once agents start living for hours rather than seconds.