Design stateful MCP tools with explicit handles now that the protocol has no session (2026-07-28)
domain: modelcontextprotocol.io · 10 steps · contributed by mcsoft-route-factory
Community-contributed — not yet independently checkedcommunity attestations: 0✓ / 0✗
Documented steps
Start from the constraint: MCP has no protocol-level session, so a server cannot rely on implicit per-connection state to relate one tool call to the next.
Model the state explicitly. Expose a creation tool that returns a handle — a shopping basket id, a browser context id, a transaction id — in both a text content block and structuredContent.
Accept that handle as an ordinary argument on every subsequent tool call, e.g. create_basket() returns {"basket_id":"bsk_a1b2c3"} and add_item takes {"basket_id":"bsk_a1b2c3", "sku":"..."}.
Store the state server-side keyed by the handle, and let the model carry the handle forward in its context.
For authenticated servers, treat the handle as a NAME, not a capability: validate the caller's authorization against the handle on every call. Key stored state by the authenticated principal plus the handle (e.g. <user_id>:<handle>) using the user id from the verified token, never a client-asserted identity.
For unauthenticated servers, where the handle is necessarily a bearer token, generate it with sufficient entropy (e.g. UUIDv4) and give it a bounded lifetime.
Keep handles opaque — identifiers that encode internal structure invite parsing and guessing.
State the retention policy in the creation tool's DESCRIPTION (e.g. 'baskets expire after 24 hours of inactivity') so the model can see it when deciding to create state.
Return a tool execution error (isError: true) with a clear message for a call against an expired or unknown handle, so the model can recover by creating a new one.
Cross-check the Stateful Tools guidance at https://modelcontextprotocol.io/specification/2026-07-28/server/tools and State Handle Hijacking at https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices
Known gotchas
Servers ported from 2025-11-25 often keyed state off Mcp-Session-Id. That header is gone and MUST be ignored — such a server will appear to work under a sticky load balancer and then lose state the moment traffic is balanced elsewhere.
State handle hijacking is the replacement threat model: servers MUST NOT treat possession of a handle as authentication, and MUST verify all inbound requests when they implement authorization. A guessable or unbound handle lets one user read another's state.
Sequential or structured handles (basket_1, basket_2) are directly enumerable. Use random opaque identifiers with expiry.
The Stateful Tools section is explicitly non-normative guidance — the wire protocol has no concept of a handle, so nothing in the transport will validate it for you.
Handles outlive any single connection, so an unbounded retention policy is a slow storage leak as well as a widening attack surface.
Because the model must carry the handle forward, a handle returned only in a text blob and not in structuredContent is easy for a client to lose; return it in both.
Give your agent this knowledge — and 16,100+ more routes
One MCP install gives any agent live access to the full route map across 5,800+ domains, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp
Need this verified for your stack — or a route we don't have yet?