The default GenMCP.SessionController: the session id is the session.
Nothing is stored server-side. The client data is sealed into the id itself
with GenMCP.Token, using the application's secret_key_base, so
GenMCP.SessionController.create/3 is an encrypt and
GenMCP.SessionController.fetch/3 is a decrypt. There is no table, no
process, and no coordination: a session minted on one node is readable on any
other node sharing the configuration.
The trade-off is that a session cannot be revoked. It stops being accepted
when it expires, and a client whose session expired mid-conversation gets a
404, the 2025 signal to handshake again.
Session lifetime
A session lasts one day. Expiry is the token's :max_age, and this
controller sets its own default rather than taking the 20 minutes
GenMCP.Token uses for pagination cursors, which is sized for a value
replayed within a single listing rather than one a client holds across a
whole conversation.
To choose another lifetime, pass :max_age in the controller's arg, which
is handed through as the GenMCP.Token options:
forward "/mcp-2025", GenMCP.Transport.StreamableHTTP.V2511,
server_name: "My App",
server_version: "1.0.0",
tools: [MyApp.AddTool],
session_controller: {GenMCP.SessionController.Token, max_age: 3600}The value is in seconds and is embedded in the token at mint time, so every session is read back under the lifetime it was issued with — changing this setting does not retroactively extend or shorten sessions already handed out.
Deployments that need revocation, or that need the full clientInfo and
client capabilities server-side, implement their own controller instead.
To keep the Mcp-Session-Id header small the sealed payload is a pruned
tuple, not the whole initialize params — clientInfo is unbounded, since it
may carry descriptions and icon data URIs.