Sessions

Opt-in Mcp-Session-Id lifecycle, legacy only.

Note

Protocol-level sessions were removed in 2026-07-28. Modern servers are stateless; carry cross-call state in explicit handles passed as ordinary tool arguments.

Sessions are off by default. Enable them and initialize starts issuing session ids that clients must return on every subsequent request:

defineMcpHandler({
  name: "my-server",
  version: "1.0.0",

  // Simple — uses crypto.randomUUID()
  session: true,

  // Or configured
  session: {
    generateId: (event) => `session-${Date.now()}`,
    maxSessions: 1000,
  },

  tools: [/* ... */],
});

#Lifecycle

initialize creates a session and returns it in the mcp-session-id response header.
Every later POST, GET, and DELETE must carry that header.
DELETE removes it from the store.
SituationResponse
Missing mcp-session-id400
Unknown session id404
Session created by a different credential404
maxSessions exceeded503

Session ids are format-checked before they are looked up or stored — bounded length, alphanumerics plus - and _. A custom generateId must stay inside that alphabet.

With auth on, a session is bound to the credential that created it (event.context.mcp.principal: the validator's subject, else a digest of the token). A request that presents the id with a different credential — or with none, or with one when the session was created without auth — is answered exactly like an unknown session, so the response never confirms that the session exists. A subject-less token that rotates is a new principal, and its session must be re-initialized.

#The Store Is In-Memory

The built-in store is a bounded Map in the handler's closure. That has consequences worth being explicit about:

  • It does not survive a restart. Clients get 404 and must re-initialize.
  • It is per-process. Behind a load balancer without sticky sessions, requests will land on a process that has never seen the id.
  • maxSessions is unset by default. The store is a client-growable, server-lifetime collection: without a cap, anything that can reach initialize can grow it. Set maxSessions on any endpoint that is not fully trusted — exceeding it returns 503 rather than consuming more memory.

For anything multi-process, prefer keeping real state in your own store keyed by the session id, so losing the id costs a handshake rather than data.

#Reading the Session

defineTool({
  name: "remember",
  handler: (event) => {
    const sessionId = event.context.mcp?.sessionId;
    return { content: [{ type: "text", text: `session: ${sessionId ?? "none"}` }] };
  },
});

Important

A session id is a bearer credential for the session's state: whoever holds it continues that session on a server without auth. It is not an authentication mechanism — pair sessions with transport auth, which also binds each session to the credential that created it, rather than treating the id as proof of identity.

h3-mcp  MCP servers, built on H3.