---
summary: "Adopts mcp-ts-core 0.12.3 and MCP SDK v2: the consent gate becomes a multi-round-trip request that now reaches Streamable HTTP clients, tool inputs turn strict, and the advertised outputSchema declares the error envelope"
breaking: true
security: false
---

# 2.3.1 — 2026-08-22

Every change below rides the framework upgrade to `@cyanheads/mcp-ts-core` `^0.12.3`, which adopts MCP SDK v2 and serves protocol revision 2026-07-28 alongside the 2025 era. Existing clients keep working, apart from the wire-tightening called out here.

## Added

- **`.github/CONTRIBUTING.md`** and **`.github/CODE_OF_CONDUCT.md`** — issue-filing guidance, the server-vs-framework split, and the conduct policy.

## Changed

- **The consent gate on `ntfy_manage_message` and `ntfy_publish_message` is now a multi-round-trip request.** The gated call returns an `input_required` result carrying an `elicitation/create` request naming the exact target instead of a tool result; reissuing the same call with the answer attached carries the operation out. Approving mid-call is gone — `ctx.elicit` was removed upstream.
- **The consent gate reaches Streamable HTTP.** The proceed-anyway branch is gone — `ctx.requestInput` exists on every transport and era, so a gated call is fail-closed everywhere it previously fell through to the tool annotations. A client that cannot present the prompt now fails the call naming the missing elicitation capability instead of publishing unasked.
- **`MCP_SESSION_MODE` is `stateful` on every launch path, the Docker image included** — it shipped `stateless` while source, `bunx`, and `npm start` all resolved to `stateful`. Only the stateful arm keeps the live session the SDK's legacy shim needs, so the consent prompt on destructive and outbound calls can now complete for HTTP clients hitting the container.
- **Tool inputs are strict at the top level.** A top-level argument key a tool's schema does not declare is now rejected by name before the handler runs, where it was silently dropped; the advertised `inputSchema` carries `additionalProperties: false`. Nested objects are unchanged — an unknown key inside a `ntfy_publish_message` `actions[]` entry is still dropped silently. All four tools take a closed set of top-level arguments, so none opts out via `.passthrough()`.
- **The advertised `outputSchema` declares the error envelope.** Success fields become optional and `error` is declared, so a client that validates `structuredContent` without checking `isError` no longer rejects a typed failure with `-32602`. Advertised JSON Schema is 2020-12, not draft-07.
- **`ctx.log` reaches the client.** Every level also emits `notifications/message`, gated by `logging/setLevel`. No handler logs a topic name, so a topic stays out of the log stream the client now sees.
- **`confirmAction` is synchronous** and returns `'confirmed' | 'declined'`; the `'unsupported'` outcome is gone. Callers run it before any side effect, and the handler stretch above it re-runs on the approval round.
- **Bun pins move to `1.4.0`** — both stages of `Dockerfile` and `packageManager`. `engines.bun` stays `>=1.3.0`.
- **The production Docker stage omits optional peers** — `--omit=peer` on both the production `bun install` and the OTEL `bun add`, so the runtime image no longer ships the framework's opt-in tiers.
- **`tsconfig.json` typechecks `tests/`** alongside `src/` with `noEmit`; `tsconfig.build.json` carries the emit path.
- **`.env.example`** documents the framework's SSE-replay knobs — `MCP_HTTP_RESUMABILITY`, `MCP_HTTP_RESUMABILITY_MAX_EVENTS`, `MCP_HTTP_RESUMABILITY_TTL_MS`.

## Dependencies

- `@cyanheads/mcp-ts-core` `^0.11.0` → `^0.12.3`
- `typescript` `^6.0.3` → `^7.0.2` (and dropped from the `devcheck.config.json` outdated allowlist, matching the framework template)
- `@biomejs/biome` `^2.5.5` → `^2.5.9`
- `@types/node` `^26.1.1` → `^26.2.0`
- `tsc-alias` `^1.9.1` → `^1.9.2`
- `vitest` `^4.1.10` → `^4.1.11`
