What does a stateful MCP session enable and what is the key scaling tradeoff?
Explain what a stateful MCP session enables that a stateless per call model cannot. Describe at least one concrete multi-step scenario and the scaling tradeoff this model introduces.
A stateful MCP session remembers context across calls (enabling transactions and server-pushed notifications) but the cost is sticky routing that breaks easy horizontal scaling.
Think of a stateless call like a fast-food counter where a new cashier serves you each time. You must repeat your whole order every visit, and nobody can start cooking now to finish later. A stateful MCP session is a sit-down restaurant with one waiter who remembers your table all night. They open a tab, take several courses, and close it at the end. That memory is powerful: the waiter can also walk over and tell you news without being asked. The catch is you are tied to that one waiter. If they go on break, nobody else knows your tab. The restaurant cannot just throw any free waiter at your table the way the fast-food counter could.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
This is a hard senior question because it rewards balance. Plenty of candidates can recite that MCP supports stateful sessions and that this enables transactions. Far fewer connect that benefit to its production cost, which is exactly the dimension a staff interviewer is probing. The question explicitly asks for both a concrete multi-step scenario and the scaling tradeoff, so an answer that names only one half is incomplete by construction.
The core idea: a stateful session lets an MCP server keep memory across many calls within one connection. The client and server negotiate capabilities once during the lifecycle handshake, the server assigns a session id, and every later request reuses it. That memory is the foundation for multi-step operations and for server-initiated traffic. Without it, MCP would collapse to a glorified per-call function dispatcher with no way to group calls or push events.
The flip side is operability. State that lives in one instance's memory pins the client to that instance, which forces sticky routing and complicates horizontal scaling, deploys, and failure recovery. This deep dive walks both halves, shows the lifecycle that creates a session, then shows where the current Streamable HTTP transport lets you opt back into stateless operation and why that choice is per-server, not global.
What a stateful session actually remembers
A stateful MCP session begins with the lifecycle handshake. The client sends initialize, the two sides negotiate protocol version and capabilities, the client confirms with initialized, and the server may assign a session id the client echoes on every subsequent request. From that point the server can attach in-memory state to the session and trust that capabilities were agreed once, not renegotiated per call.
The contrast with a stateless per-call model is sharp. In a stateless design each request is fully self-contained: the server holds nothing between calls, so it cannot know that two requests belong to the same logical workflow. Capability negotiation, if it happens at all, repeats per request, and the server cannot assume the client supports any optional feature without re-checking. Each call is an island.
With a session, the server gets a stable identity to hang state on. That is the enabling primitive. Everything else in this answer, transactions, notifications, subscriptions, sampling, depends on the server being able to say 'this request belongs to the same session as that earlier one.' Negotiating once also amortizes the handshake cost: a long-lived stdio session to a local server pays it at startup and never again, which is why editor integrations favor it.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
// Stateful session: the server holds a DB transaction across calls.
const sessions = new Map<string, { tx: Transaction; lastSeen: number }>();
server.setRequestHandler("tools/call", async (req, sessionId) => {
const { name, arguments: args } = req.params;
if (name === "begin_transaction") {
const tx = await db.beginTransaction();
sessions.set(sessionId, { tx, lastSeen: Date.now() });
return ok("transaction opened");
}
const session = sessions.get(sessionId);
if (!session) throw new Error("no active session"); // sticky routing failed
session.lastSeen = Date.now();
if (name === "execute_query") return ok(await session.tx.query(args.sql));
if (name === "commit") {
await session.tx.commit();
sessions.delete(sessionId); // cleanup on graceful close
return ok("committed");
}
});
// Idle sweeper prevents leaks from ungraceful disconnects.
setInterval(() => {
const cutoff = Date.now() - 30 * 60 * 1000;
for (const [id, s] of sessions) {
if (s.lastSeen < cutoff) { s.tx.rollback(); sessions.delete(id); }
}
}, 60 * 1000);| Concern | Stateful session | Stateless per-call |
|---|---|---|
| Multi-step transactions | Yes, handle persists across calls | No, each call is isolated |
| Server-initiated push | Notifications, subscriptions, sampling | Not supported |
| Capability negotiation | Once at initialize | Per request, if at all |
| Load balancing | Needs sticky routing | Plain round-robin works |
| Replica failure | Live sessions lost | Any replica can serve |
Real products, models, and research that use this idea.
- A Postgres MCP server opens a transaction, runs queries across several tool calls, then commits; the handle lives in session memory the whole time.
- Claude Code and Cursor keep long-lived stdio sessions to local servers, negotiating capabilities once at startup rather than per request.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you externalize MCP session state so any replica can serve a follow-up request?
Move the session map to a shared store like Redis; keep handles serializable; accept that live resources like an open transaction handle cannot leave one process, so only some state externalizes cleanly.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Naming only the benefit and forgetting the cost. Senior answers must connect in-memory session state to sticky routing and the loss of stateless load balancing.
60 second bullets to scan on the way to the call.
What a stateful session enables that a per-call model cannot
A concrete transaction example with state living across calls
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.