Skip to main content
A handler often needs to remember something. It counts calls, caches a lookup, or carries a value from one step of a flow to the next. FastMCP gives you three ways to remember, each scoped to a different lifetime. Picking the right one is mostly a question of how long the value must live, and whether it must survive the modern era’s stateless requests.
  • Session state lives as long as one connection. Reach for it for per-connection scratch data.
  • Request state lives across the rounds of one input-required flow. Reach for it to carry flow state the client cannot forge.
  • Server-minted handles live as long as you keep the data. Reach for them for anything durable or portable — this is the era-agnostic default.

Session state

Session state is a per-connection key-value store. A handler writes with ctx.setState(key, value), reads with ctx.getState(key), and clears with ctx.deleteState(key). Write a value in one request and read it back in the next, without a database.
server.ts
State is scoped to one connection, never shared across them. Two clients never see each other’s values, and the store disappears when the connection closes.

The stateless modern boundary

Session state depends on a persistent session, and the modern era’s HTTP transport has none. Each modern HTTP request runs statelessly, against a fresh state store built for that one request. A value written in one call would be gone by the next. Rather than drop such a write silently, FastMCP makes the accessors throw a pointed error on a modern HTTP request. The message names the request-scoped replacements — ctx.requestState() to read per-request state, and ctx.mintRequestState() to carry state across a multi-round-trip flow. A silent no-op would hide the bug; the error surfaces it at the write. Where session state survives depends on the transport, not only the era. Stdio pins one server instance — and its state store — for the whole connection, so state persists on both eras. HTTP persists on the legacy era, which is sessionful, and throws on the modern era, which is stateless. The practical read: session state is safe on stdio and on legacy HTTP. For a server that must serve modern HTTP clients, treat session state as unavailable and use request state or a handle instead.

Request state

Request state carries data across the rounds of one input-required flow — the state the client echoes back verbatim when it retries a call. It exists precisely because modern HTTP has no session: it is how a multi-round-trip handler threads a value from the round that asks for input to the round that receives it. A handler mints state with ctx.mintRequestState(payload), returns it from inputRequired({ requestState }), and reads it back with ctx.requestState<T>() on the retry. Because the state passes through the client, it comes back as untrusted input. Configure FastMCPOptions.requestState with an HMAC key so every minted state is signed and verified — a tampered or expired state is rejected with -32602 before your handler runs. Without a key, mintRequestState returns an unsigned string and warns; never let unsigned state influence authorization, resource access, or business logic. The input-required guide shows request state in a full flow.

Server-minted handles

The durable, portable answer is a handle. Store the real data server-side under an identifier you mint, and return the identifier as an ordinary value. The model threads that handle back as a tool argument on later calls. Nothing rides the session, so the pattern works identically on stdio, legacy HTTP, and modern HTTP — the handle is the durable reference the spec recommends in place of session state.
server.ts
Give the store a cleanup policy that does not depend on a session closing. A time-to-live sweep works on every transport, where a session-close callback fires only when there is a session. FastMCP’s built-in file upload provider is the worked example: it mints a handle for each upload, stores the bytes under it, and expires them on a TTL, so an upload survives on stateless modern HTTP exactly as it does on stdio.