MCP servers lose tool state between requests
A developer builds an agent that calls a tool, waits for a follow-up question, then calls another tool expecting the server to remember what happened. It doesn't. The second request arrives at a serve
A developer builds an agent that calls a tool, waits for a follow-up question, then calls another tool expecting the server to remember what happened. It doesn't. The second request arrives at a server instance with no memory of the first, and the workflow breaks.
This is the core pain point behind MCP tool state management stateful sessions discussions across the developer community right now. The Model Context Protocol was designed with a session mechanism to solve exactly this problem, but that mechanism is being restructured in the 2026-07-28 revision. Understanding why state disappears, and what's replacing the old approach, matters for anyone shipping agentic tools today.
This article walks through how MCP session state has worked, why it's changing, and what practical steps to take if your tool integrations depend on remembering context across calls.
Why Tool Context Disappears Between Requests
MCP tools run as request-response cycles. Each call is, in principle, independent. If the server storing that state is swapped out, restarted, or simply routes a request to a different instance, the model loses track of what came before.
Traditionally, MCP handled this with an Mcp-Session-Id header assigned during the initialize handshake. According to the MCP C# SDK documentation, this header lets a client attach an identifier to every subsequent request, so the server can look up prior context. It's a reasonable pattern, similar to how web apps use session cookies.
The problem shows up in production when that session identifier gets separated from the server instance holding the actual state. A load balancer sends request one to server A, which builds up conversational context. Request two arrives and lands on server B, which has never heard of the session. The tool call fails, or worse, it silently returns wrong results because it operates on empty state.
This is the practical reality of MCP multi-step workflow context loss. It's not a bug in any single implementation. It's a structural tension between keeping servers stateless for scalability and giving them enough memory to run multi-step agent tasks.
Stateful vs Stateless: The Architectural Shift
MCP servers generally fall into two camps, and the distinction affects almost every architectural decision downstream.
Stateful servers keep the McpServer and transport instances alive between requests. According to CodeSignal's documentation on stateful MCP sessions, this preserves context and prior interaction state directly in memory, which makes multi-turn tool use straightforward. The tradeoff is that the client must always reach the same server instance, a requirement known as sticky routing.
Stateless servers treat every request as a blank slate. Nothing is retained between calls except what the client explicitly sends back. According to the MCP C# SDK docs, this pattern removes the session establishment step required by stateful mode, per Solo.io's engineering breakdown of the spec changes.
| Approach | Behavior |
|---|---|
| Stateful | Persistentserver memory across calls |
| Stateless | Client-carriedstate, no server memory |
This shows the fundamental tradeoff between stateful and stateless MCP session handling.
Neither option is universally correct. Stateful sessions are simpler to reason about for complex agent chains, but they fight against horizontal scaling and load balancer flexibility. Stateless sessions scale cleanly, according to Wire Blog's analysis of the spec direction, because any server instance can answer any request. But they push the burden of tracking state onto the client and the protocol messages themselves.
How Mcp-Session-Id Worked, and Why It's Changing
The Mcp-Session-Id header has been the backbone of stateful MCP for a while. A client calls initialize, the server responds with a session ID, and every future call in that conversation carries the ID along.
According to Solo.io's breakdown of the spec, stateful MCP requires this session establishment step before any tool invocation can happen at all. That's a meaningful constraint. It means the first network round trip in any MCP conversation is pure overhead, just to get a session token before real work starts.
The 2026-07-28 revision removes this entirely. According to Mervin Praison's analysis of the change, the new spec drops both the initialize handshake and the Mcp-Session-Id header, replacing bidirectional session state with a stateless request-response core.
Why make this change? Two forces are pushing it:
- Scaling friction. Sticky sessions fight against modern load balancing, autoscaling, and container orchestration patterns that assume any instance can handle any request.
- Tool list caching problems. According to the Sessionless MCP proposal (SEP-2567), opt-in sessions prevent clients from safely caching a server's tool list across session boundaries, since clients can't know in advance whether a server might mutate its tools mid-session.
The result is a protocol that pushes state out of the transport layer entirely, and toward explicit mechanisms the client and server negotiate directly.
Hybrid State: Splitting Resource State from Application State
The cleanest way to think about state in an MCP deployment is to split it into two categories that behave very differently.
Resource state is durable. Think database records, uploaded files, or configuration that should survive restarts and outlast any single conversation. Application state is ephemeral. It's the running context of a conversation, like which step of a workflow the agent is on, or which tool was called last.
According to Zeo's overview of MCP server architecture, advanced deployments use a hybrid approach: persistent resource state lives in a database, while ephemeral application state stays separate and short-lived. This separation matters because it lets you scale the ephemeral layer without touching your durable data.
A practical version of this pattern looks like:
# Resource state: durable, lives in a database
def get_user_profile(user_id):
return db.query("SELECT * FROM profiles WHERE id = %s", user_id)
# Application state: ephemeral, passed by the client
def handle_tool_call(request):
conversation_context = request.get("context_handle")
step = conversation_context.get("current_step", 0)
# process step, return updated context_handle to client
return {
"result": run_step(step),
"context_handle": {"current_step": step + 1}
}Notice the second function never reads from server memory for conversational state. The client carries context_handle forward on every call. This is the essence of stateless-compatible design: durable facts live in the database, transient progress rides along with the request.
Migration Guide: Preparing for Stateless MCP
If you're running a stateful MCP server today, the shift toward stateless operation isn't optional forever, even if your current setup works fine. Here's a practical migration checklist.
The Sessionless MCP proposal, SEP-2567, introduces explicit state handles as a replacement mechanism. Instead of the server implicitly remembering a session by ID, the client holds an opaque handle and sends it back with each request. The server can look up whatever it needs using that handle, without ever needing sticky routing.
This is a meaningful shift in responsibility. Client libraries will need updating to carry these handles correctly, and any custom MCP server code that assumes persistent memory between calls needs rewriting.
Load Balancing Without Sticky Sessions
One of the strongest arguments for stateless MCP is operational simplicity. According to a related proposal, SEP-2575, removing the initialize handshake directly enables stateless operation without requiring sticky routing at the load balancer level.
Sticky sessions have always been an operational headache. They complicate autoscaling, make rolling deployments riskier, and create hot spots when certain sessions generate disproportionate load on a single instance. According to Wire Blog, stateless MCP eliminates this requirement, letting any server instance answer any request without regard to history.
For teams running MCP servers behind standard cloud load balancers, this is the practical payoff of the spec change. You stop configuring session affinity rules. You stop worrying about what happens when an instance holding session state gets terminated mid-conversation during a deploy.
MRTR: A New Pattern for Multi-Step Tool Calls
The obvious question with stateless MCP is: how do you handle tools that genuinely need multiple steps, like waiting for user confirmation before executing an action?
The answer emerging in the spec is Mid-Request Tool Response, or MRTR. According to the MCP C# SDK documentation, MRTR lets a server that needs user confirmation or additional LLM reasoning still operate in stateless mode. The mechanism works by having the client fulfill whatever is being requested and retry the call with the response attached.
In practice, this looks like a tool call returning a "needs input" signal rather than an error. The client gathers what's needed (a confirmation, a piece of reasoning, additional data) and resubmits the same call with that information included. No server-side memory of the first attempt is required, because the client carries the entire conversation forward itself.
{
"status": "needs_confirmation",
"action": "delete_file",
"target": "example.internal/reports/q3.csv",
"retry_with": {"confirmed": true}
}This pattern shifts the mental model for anyone building agentic system session persistence. Instead of the server remembering "I asked this user a question," the protocol treats each exchange as complete and self-contained, with the client responsible for supplying whatever context makes the next call meaningful.
Debugging State Loss in Production
When tool state loss shows up in production, it rarely announces itself clearly. Symptoms include tools that "forget" earlier steps, intermittent failures that only happen under load, or agents that repeat actions because they can't tell a step already completed.
A few checks help isolate the cause:
- Confirm routing behavior. Log which server instance handled each request in a conversation. If instances vary and the server is stateful, that's your bug.
- Check session ID propagation. If you're still on the older handshake-based approach, verify the client is actually sending
Mcp-Session-Idon every call, not just the first. - Inspect context handle passing. For stateless-style implementations, confirm the client is round-tripping the full context object, not a stale or truncated copy.
- Watch for tool list drift. According to SEP-2567, servers that mutate their tool list mid-session can confuse clients caching an old list, producing calls to tools that no longer behave as expected.
Most of these issues trace back to an assumption baked in early: that the server will remember something the client never explicitly sent back. Removing that assumption, even before the spec forces it, tends to make debugging far easier.
Frequently Asked Questions
Q: Do I need to rewrite my MCP server right now for the stateless spec change?A: Not immediately, but you should start auditing which tool handlers assume server memory. Waiting until the old handshake is deprecated in your SDK version will make the migration more painful.
Q: Can stateful and stateless MCP servers coexist in the same system?A: Yes. According to AgentScope's documentation, client libraries already support both HttpStatefulClient and HttpStatefulClient types, differing only in session management approach, so mixed deployments during a transition period are workable.
A: Sticky routing dependencies. If your infrastructure or load balancer configuration assumes session affinity and the underlying spec drops that requirement, you may end up carrying operational complexity you no longer need, or worse, hitting subtle bugs when instances rotate unexpectedly.
Takeaways
Tool state loss between MCP requests almost always traces back to a mismatch between where state lives and where the protocol expects it to live. The spec is moving decisively toward stateless-by-default, with explicit state handles and MRTR replacing implicit session memory.
Practical next steps:
- Separate resource state (durable, database-backed) from application state (ephemeral, client-carried) now, regardless of which MCP version you're running.
- Test your tool handlers against non-sticky routing to surface hidden assumptions before the spec forces the issue.
- Adopt explicit context handles for multi-step workflows rather than relying on server memory.
- Watch the SEP-2567 and SEP-2575 proposals directly, since they define the concrete replacement mechanisms for session state.
The teams that treat this as an architecture review now, rather than a breaking change to react to later, will have a much easier migration when the stateless core becomes the only option.
Sources
Researched from the following. Figures and claims were current when this piece was written and may have moved since.
- Stateless and stateful mode | MCP C# SDKcsharp.sdk.modelcontextprotocol.io
- MCP Stateless Spec Changes: The Engineering Details | Solo.iosolo.io
- Stateful vs Stateless MCP: Sticky Sessions Are Gone - Mervin Praisonmer.vin
- Managing Stateful MCP Server Sessionscodesignal.com
- SEP-2567: Sessionless MCP via Explicit State Handles - Model Context Protocolmodelcontextprotocol.io
- Stateless MCP: the spec is dropping session state | Wire Blogusewire.io
- MCP Server Architecture: State Management, Security & Tool Orchestration | Zeozeo.org
- MCP - AgentScopedoc.agentscope.io