---
summary: "HTTP+MCP_AUTH_MODE=none defaults tenantId to 'default' so ctx.state works without minting JWTs. New ctx.recoveryFor opt-in contract resolver carries the contract recovery onto the wire — single source of truth for handler and service throws."
breaking: false
---

# 0.8.5 — 2026-04-29

Two changes shipped together. `MCP_AUTH_MODE=none` over HTTP now behaves as the documented "single-tenant, no auth" mode (closes [#87](https://github.com/cyanheads/mcp-ts-core/issues/87)). And the contract `recovery` field gets a typed runtime resolver — `ctx.recoveryFor(reason)` — that carries the lint-validated string onto the wire without authoring duplication, while preserving the "no auto-population, no hidden transformation" stance from [#85](https://github.com/cyanheads/mcp-ts-core/issues/85) (closes [#89](https://github.com/cyanheads/mcp-ts-core/issues/89), supersedes [#88](https://github.com/cyanheads/mcp-ts-core/issues/88)).

## Added

- **`ctx.recoveryFor(reason)`** (`src/core/context.ts`) — opt-in contract resolver. Returns the canonical wire shape `{ recovery: { hint } }` for declared reasons; returns `{}` when no contract is attached or the reason is unknown. Always present on `Context` (spread-safe with no optional chaining); strictly typed on `HandlerContext<R>` against the declared reason union. Same resolver works in services that accept `ctx`.
- **`createRecoveryFor(errors)`** (`src/core/context.ts`) — internal builder, mirrors `createFail`. Public re-export from `@cyanheads/mcp-ts-core` for advanced use.
- **`TypedRecoveryFor<R>`** type alias for the strict signature, exported alongside `TypedFail<R>`.
- **First member of a planned family of opt-in resolution helpers.** Future contract-bound fields (`troubleshootingFor`, `userMessageFor`, …) follow the same shape: single-purpose method, spreadable wire-shape return, `{}` fallback, strict variant on `HandlerContext<R>`.

## Fixed

- **`createContext`** (`src/core/context.ts`) — `tenantId` defaults to `'default'` when `MCP_AUTH_MODE=none`, regardless of transport. Stdio behavior unchanged. HTTP+`jwt`/`oauth` without `tid` still fails closed (security invariant: distinct authenticated callers must not silently share state).
- **`ctx.state` error message** — references `MCP_AUTH_MODE` semantics instead of stale "via createApp()" wording.

## Changed

- **`HandlerContext<R>`** — when `R` is a literal union, the type now uses `Omit<Context, 'recoveryFor'> & { fail: TypedFail<R>; recoveryFor: TypedRecoveryFor<R> }` (intersecting the loose `Context.recoveryFor` would widen `parameter(0)` back to `string` and lose typo-catching). Behavior unchanged for callers; type is strictly narrower for declared reasons.
- **`attachTypedFail`** (`src/core/context.ts`) — wires both `fail` and `recoveryFor` when the definition declares an `errors[]` contract. Bare `Context` carries a no-op `recoveryFor` so service-side spreads are always safe.

## Docs

- `AGENTS.md` / `CLAUDE.md` / `README.md` — `tenantId` resolution table covers all three modes; error handling section and Context interface documents `ctx.recoveryFor`; exports table includes `createRecoveryFor`, `TypedRecoveryFor`.
- `skills/api-context/SKILL.md` v1.1 → v1.2 — new `ctx.recoveryFor` section with signature, behavior table, and family-pattern note; identity field table and `ctx.state` notes carry the auth-none default.
- `skills/api-errors/SKILL.md` v1.3 → v1.4 — opt-in resolution subsection replaces the const-pattern guidance; gap callout removed; service-layer pattern updated.
- `skills/api-auth/SKILL.md` — `tenantId` sources table covers HTTP+`none`.
- `skills/add-tool/SKILL.md` v2.3 → v2.4 — template demonstrates static (`...ctx.recoveryFor('reason')`) and dynamic (inline `{ recovery: { hint } }`) recovery side by side; service-layer subsection now passes `ctx` and uses the resolver.
- `skills/add-service/SKILL.md` v1.4 → v1.5 — service-side spread pattern with `...ctx.recoveryFor('reason')`.

## Security note

HTTP+`MCP_AUTH_MODE=none` is **single-tenant by design** — all unauthenticated callers share the `'default'` tenant. Servers requiring per-caller isolation must use `MCP_AUTH_MODE=jwt` or `oauth` with tokens that carry a `tid` claim.

## Design principle (#85 / #88 / #89)

`ctx.recoveryFor` is **opt-in resolution**, not auto-population. The framework never attaches `data.recovery.hint` without an explicit signal at the throw site — authors opt in by typing the helper, the same way `ctx.fail('reason')` opts into resolving the contract `code`. The contract is the single source of truth for the recovery hint; the resolver is a typed lookup keyed by the same reason the author already typed. No magic, no hidden transformation. The `≥5 words` lint rule on contract `recovery` becomes load-bearing — every `ctx.recoveryFor` call site benefits from the thoughtfulness the contract enforced.
