---
summary: "Surface `ctx.sessionId` on `Context` for HTTP handlers ([#116](https://github.com/cyanheads/mcp-ts-core/issues/116)) — fail-closed under stateless mode with `createApp({ context: { exposeStatelessSessionId } })` opt-in."
breaking: false
---

# 0.8.17 — 2026-05-05

The SDK already threads the `Mcp-Session-Id` header through `RequestHandlerExtra.sessionId` and both handler factories already extracted it for log correlation — but the value was dropped before constructing the public `Context`. Downstream servers that want session-scoped state, per-session tool surfaces, or per-session enumeration on top of token-addressable primitives (canvases, dataframes) had no path to do so without reaching into the SDK extras directly. `ctx.sessionId` now surfaces it cleanly. Default behavior is fail-closed under stateless HTTP — under `MCP_SESSION_MODE=stateless` the SDK still mints a per-request token, but exposing that as `ctx.sessionId` would silently give every request a unique "session" and break any session-keyed isolation pattern. Opt in via `createApp({ context: { exposeStatelessSessionId: true } })` when downstream code is structured around the field.

## Added

- **`ctx.sessionId`** ([#116](https://github.com/cyanheads/mcp-ts-core/issues/116)) — `readonly sessionId?: string | undefined` on `Context`. Defined for HTTP in `stateful` / `auto` session mode (durable, request-spanning); `undefined` for stdio and for stateless HTTP unless the server opts in. Use as a discovery / scoping key on top of tenant-keyed `ctx.state` — not as an authorization principal. Under `MCP_AUTH_MODE=jwt|oauth`, hijack-mismatched callers are rejected by the `SessionStore` before the handler runs; under `MCP_AUTH_MODE=none`, possession of the header value is the only authorization (matching the framework's capability-token model).
- **`ContextOptions` exported from the package root** — new public type carrying the `exposeStatelessSessionId?: boolean` flag. Surfaced via `CreateAppOptions.context`. Defaults to `false`; set `true` to populate `ctx.sessionId` from the SDK's per-request token under `MCP_SESSION_MODE=stateless`.
- **`MockContextOptions.sessionId`** — `createMockContext({ sessionId: 'sess-xyz' })` for unit tests that exercise handlers branching on `ctx.sessionId`.

## Changed

- **`HandlerFactoryServices` / `ResourceHandlerFactoryServices`** gain an optional `exposeStatelessSessionId?: boolean` field. Wired from `createApp({ context: { exposeStatelessSessionId } })` through `composeServices` to both registries. Existing consumers passing `{ logger, storage }` are unaffected.
- **`skills/api-context` (v1.2 → v1.3)** — new `## ctx.sessionId` section covering the when-it's-defined matrix across stdio / stateless / stateful × auth modes, the stateless-mode opt-in, capability-token coherence note, and strict / lax state-keying recipes. `sessionId` row added to the Identity fields and Quick reference tables.
