Sessions
Use sessions with the current published Askr packages.
@askrjs/askr0.0.59
How to use sessions
Create one MCP server, register typed primitives on it, and let the selected transport supply auth, cancellation, sessions, and progress.
- Import the published @askrjs/askr entrypoint.
- Keep sessions configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { schema } from '@askrjs/schema';
import { createMcpServer } from '@askrjs/server/mcp';
export const mcp = createMcpServer({
name: 'project-tools',
version: '1.0.0',
}).tool(
'lookup-project',
{ input: schema.object({ id: schema.uuid() }) },
async (context, { id }) => ({
content: [{ type: 'text', text: await lookup(id, context.signal) }],
})
);Session identity
In stateful HTTP mode, a session is identified purely by the `mcp-session-id` header: the server mints one with `crypto.randomUUID()` the moment it sees an `initialize` call with no existing session ID, and the client is expected to send that same header on every subsequent request. Stdio sessions work differently — `connectMcpStdio` generates a single random session ID per process connection and uses it for every message on that stream, since there's only ever one "client" per stdio pipe.
State ownership
Two separate stores back a session in stateful HTTP mode: the `McpSessionStore` (just `create`/`has`/`delete`, in-memory `Set` by default) tracks which session IDs are currently valid, while the MCP server itself keeps a parallel `Map` of initialized session state — negotiated `client` info, `clientCapabilities`, and `protocolRevision` — keyed by that same ID. The two are meant to stay in sync: a request is only dispatched once `sessions.has(id)` is true at the HTTP layer and the server-side session map shows `initialized: true`, and `terminateSession` clears the server-side entry and any SSE listeners for that ID.
Expiry
There's no built-in TTL or idle-timeout on sessions — the default in-memory store keeps an ID valid until it's explicitly deleted via `DELETE path` or the process restarts. If you need sessions to expire on their own, implement that in a custom `sessionStore`: `has(id)` can return `false` once a session has aged out, which the adapter treats identically to an explicitly deleted session (`404 MCP session not found` on the next request).
Horizontal scaling
The default in-memory session store and the server's own initialized-session map are both process-local, so a stateful deployment behind multiple instances needs sticky routing on `mcp-session-id`, a shared `sessionStore` implementation, or both. Because SSE channels (the `Map` of open `send` functions) are also held in the process that accepted the GET request, notifications for a given session can only be pushed by the instance holding that connection — routing by session ID isn't optional if you scale out and want push to keep working, not just request/response.