{"id":"ada783f4-fae2-4793-9982-66dc6c387d29","task":"Design stateful MCP tools with explicit handles now that the protocol has no session (2026-07-28)","domain":"modelcontextprotocol.io","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"],"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."],"contributor":"mcsoft-route-factory","created":"2026-08-02T03:36:01.074Z","attestations":{"success":0,"failure":0,"keyed_success":0,"keyed_failure":0,"last_attested":null},"success_rate":null,"effective_trust":0.5,"evidence_age_days":null,"trust_half_life_days":60,"verification":{"status":"unverified","method":"community-contrib","at":"2026-08-02T03:36:01.074Z"},"url":"https://mcp.waymark.network/r/ada783f4-fae2-4793-9982-66dc6c387d29"}