Skip to content

Commit 2d5fc58

Browse files
committed
chore(release): 0.11.2 — HTTP protocol session, input hardening, package verification
Clears the fast-uri and ip-address advisories the overrides block was added for (#325).
1 parent 2b02210 commit 2d5fc58

9 files changed

Lines changed: 264 additions & 250 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Developer Protocol
22

33
**Package:** `@cyanheads/mcp-ts-core`
4-
**Version:** 0.11.1
4+
**Version:** 0.11.2
55
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
66
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.30.0
77
**Zod:** ^4.4.3

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Developer Protocol
22

33
**Package:** `@cyanheads/mcp-ts-core`
4-
**Version:** 0.11.1
4+
**Version:** 0.11.2
55
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
66
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.30.0
77
**Zod:** ^4.4.3

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<div align="center">
77

8-
[![Version](https://img.shields.io/badge/Version-0.11.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
8+
[![Version](https://img.shields.io/badge/Version-0.11.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
99

1010
[![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.30.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
1111

bun.lock

Lines changed: 107 additions & 225 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

changelog/0.11.x/0.11.2.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
summary: "Elicitation and cancellation now work over Streamable HTTP, SSRF blocking moves from pattern matching to full non-global IP range checks, parsers bound their input and stop echoing library diagnostics into client errors, and a hermetic lane verifies the published tarball."
3+
breaking: false
4+
security: true
5+
agent-notes: |
6+
`ctx.elicit` is now defined over Streamable HTTP for clients that advertise the
7+
capability. Servers whose handlers assumed the no-elicitation branch under HTTP
8+
— including any tool description that says elicitation is stdio-only — should
9+
re-read their fallback path.
10+
11+
Parser failures no longer carry the underlying library's message, stack, or a
12+
content sample in `McpError.data`. Consumers matching on those strings must
13+
match on `data.reason` instead (`json_parse_failed`, `yaml_parse_failed`,
14+
`xml_parse_failed`, `frontmatter_parse_failed`, `html_extract_failed`,
15+
`pdf_load_failed`, `pdf_text_extract_failed`, and siblings).
16+
17+
Text parsers now reject input over 1 MiB and binary parsers over 25 MiB. A
18+
server that legitimately handles larger documents passes `{ maxBytes }` — the
19+
final argument on `yamlParser.parse` / `xmlParser.parse` / `jsonParser.parse` /
20+
`frontmatterParser.parse` and the PDF methods, or the `maxBytes` option on
21+
`htmlExtractor.extractArticle` and `pdfParser.embedImage`.
22+
---
23+
24+
# 0.11.2 — 2026-08-10
25+
26+
## Added
27+
28+
- **`normalizeLogLevelAlias(value)`** (`/config`) — resolves a raw `LOG_LEVEL` string through the same alias table `ConfigSchema` applies (`warn``warning`, `trace``debug`, `fatal`/`silent``emerg`, `err``error`, `information``info`).
29+
- **`SupabaseClientHandle`** (root) — the dependency-free default for `CoreServices['supabase']`. `createApp<SupabaseClient<MyDatabase>>({ … })` opts a consumer into the concrete client type without making Supabase's generic graph part of every consumer's declarations.
30+
- **`ParserInputBudgetOptions`, `DEFAULT_TEXT_PARSER_MAX_BYTES`, `DEFAULT_BINARY_PARSER_MAX_BYTES`** (`/utils`) — the input budget every parser now applies, and the knob to raise or lower it per call.
31+
- **`SqliteMirrorStoreLimits`** (`/mirror`) — per-store ceilings on `query` and `getByIds`, supplied as `limits` on `sqliteMirrorStore(spec)`.
32+
- **`expected` on `ToolContractSuccessCase`** (`/testing/vitest`) — a structured-output subset asserted with `toMatchObject`, so a success case pins behavior rather than schema shape alone.
33+
- **Package verification lane**`bun run test:package` packs the tarball, installs it into an isolated production-only consumer, and exercises every public subpath through runtime imports, declaration typechecking, and the published `init` bin. `scripts/public-api-contract.ts` records the exact named exports each subpath promises; both the source barrels and the installed tarball are checked against it, and `bun run scripts/public-api-contract-update.ts` re-derives the manifest after an intentional barrel change.
34+
- **Deterministic-order and assertion-required lanes**`bun run test:order` reruns the suites shuffled under a fixed seed, and every vitest config sets `expect.requireAssertions` (the typecheck project opts out, since `expectTypeOf` is not a runtime assertion).
35+
- **`.github/workflows/ci.yml`** — an eight-job matrix running static checks, Bun, real Node, Worker, integration, package, async-leak, and shuffled-order lanes on every push and pull request.
36+
37+
## Changed
38+
39+
- **Text parsers reject input over 1 MiB and binary parsers over 25 MiB.** `yamlParser.parse`, `xmlParser.parse`, `jsonParser.parse`, and `frontmatterParser.parse` take an optional budget as their final argument; `htmlExtractor.extractArticle`, `pdfParser.loadDocument`, `embedImage`, `mergePdfs`, `splitPdf`, and `extractText` take `maxBytes` alongside their existing options. PDF text extraction also caps decoded images at 4096×4096 pixels, which pdf.js leaves unbounded by default.
40+
- **`fetchWithTimeout` rejects non-`http:`/`https:` URLs** before any network work, independent of `rejectPrivateIPs`.
41+
- **`sqliteMirrorStore` bounds every query** — at most 32 filters, 500 bound values, `limit` 1–1000, `offset` 0–1,000,000, and 500 `ids` per `getByIds` call, each overridable through `limits`.
42+
- **`StorageService` caps `getMany`, `setMany`, and `deleteMany` at 10,000 keys** and rejects a non-array key list or a non-`Map` entry set before dispatching to a provider.
43+
- **`xmlParser` pins `maxNestedTags: 100`**, fast-xml-parser's own default, so an upstream change cannot silently raise the depth bound.
44+
- **Resource URIs are stripped of credentials, query, and fragment** before reaching logs, spans, or metrics; the protocol response still carries the original URI, and a `resourceHasQuery` flag records whether one was present. Worker request URLs log the same way.
45+
- **`git-wrapup` skill 1.9** — commit bodies are one or two lines and never carry a closing keyword; the changelog `summary` is written last, derived from the body.
46+
47+
## Fixed
48+
49+
- **`ctx.elicit` works over Streamable HTTP** ([#312](https://github.com/cyanheads/mcp-ts-core/issues/312)). Client capabilities live on the SDK `Server` instance, and HTTP builds a fresh one per request (GHSA-345p-7cg4-v4c7), so the capability check never passed and every handler took its no-elicitation branch. The session store now captures the capabilities declared on a successful `initialize` and restores them onto each per-request server; elicitation requests route back through `RequestHandlerExtra.sendRequest` so they reach the caller on the open response stream, and an accepted payload is validated against the schema its request advertised.
50+
- **`notifications/cancelled` aborts the handler over HTTP** ([#311](https://github.com/cyanheads/mcp-ts-core/issues/311)). The notification arrives on its own POST with its own `Server`, so the SDK's abort-controller lookup found nothing. Tool and resource invocations now register with the session store, the transport mirrors an incoming cancellation into that registry, and `ctx.signal` merges the SDK signal with the session-scoped one. Terminating a session, sweeping it as stale, or destroying the store aborts everything still in flight.
51+
- **`MCP_HTTP_MAX_BODY_BYTES` is stream-enforced** ([#244](https://github.com/cyanheads/mcp-ts-core/issues/244)). A body with no `Content-Length` was buffered whole and measured afterward, so the cap bounded nothing on that path. The middleware now reads at most one chunk past the limit, cancels the stream, and answers `413`; a within-budget body is seeded into Hono's cache so the SDK still parses it.
52+
- **`rejectPrivateIPs` blocks the IPv6 unspecified address** ([#314](https://github.com/cyanheads/mcp-ts-core/issues/314)). `::` fell through every pattern and was treated as public. The regex tables are replaced with range checks: IPv4 now covers the protocol assignments in `192.0.0.0/24`, `192.0.2.0/24`, `192.88.99.0/24`, `198.18.0.0/15`, `198.51.100.0/24`, `203.0.113.0/24`, and everything at or above `224.0.0.0` alongside the existing private space; IPv6 admits only globally routable `2000::/3`, minus Teredo, 6to4, benchmarking, ORCHID, and the two documentation ranges.
53+
- **A missing PDF peer dependency surfaces as `ConfigurationError`** ([#306](https://github.com/cyanheads/mcp-ts-core/issues/306)). The lazy import sat inside each method's catch-all `try`, so the remediation hint was re-wrapped as an `InternalError`. An `McpError` raised inside a PDF operation now propagates with its own code intact.
54+
- **Error bodies are read to the diagnostic cap, not in full.** `fetchWithTimeout` and `httpErrorFromResponse` buffered the entire non-2xx body before slicing it to 500 bytes. Both now stop at the cap and cancel the stream, trimming the capture to a UTF-8 boundary so a split code point cannot add a replacement marker past the budget.
55+
- **`WWW-Authenticate` points at the configured public URL.** The `resource_metadata` parameter was derived from the inbound request origin, which behind a proxy is the internal container address; `MCP_PUBLIC_URL` now wins when set.
56+
- **A failed shutdown step no longer strands the ones after it.** Transport stop, task-manager cleanup, rate-limiter disposal, scheduler teardown, and GC-pressure teardown each run independently, with the first failure rethrown after all of them have been attempted. A transport start that fails now rolls back everything startup allocated — process listeners, task and storage resources, telemetry — before the error reaches the caller.
57+
- **The per-request HTTP server and transport close exactly once.** The error path closed only the transport, and a request that produced no response closed neither.
58+
- **The Worker entry point resolves `LOG_LEVEL` aliases and rethrows fetch failures.** It screened the raw binding against the canonical level list, so `warn` was discarded and silently downgraded to `info`; a handler error was logged and then swallowed, hiding the failure from the runtime.
59+
- **`InMemoryProvider` tenant bookkeeping.** A read against an unknown tenant allocated a map for it, `setMany` could commit past `maxEntries` because expired keys were counted as overwrites, a list cursor issued to another tenant was accepted whenever the requested tenant was empty, and a non-integer `maxEntries` was accepted at construction.
60+
- **`DuckdbProvider` validates `rowLimit` and `preview`** before opening a connection, rejecting a non-integer, a negative, or a `rowLimit` above the configured default.
61+
- **A cancelled `DataCanvas.acquire` no longer leaks a canvas.** The `AcquireOptions` signal was ignored; it is now checked before and after provider initialization, and a canvas created while cancellation was pending is destroyed.
62+
- **DuckDB exports reject symlinked destinations.** Lexical confinement to the export root did not stop an in-sandbox symlink pointing outside it; every existing segment of the destination chain is now rejected if it is a link. The check/open race remains open ([#334](https://github.com/cyanheads/mcp-ts-core/issues/334)).
63+
- **Mirror syncs are serialized and honor an abort mid-run.** Two concurrent `runSync` calls interleaved writes into one store; the runner only observed `ctx.signal` between pages, so an abort during the final count or state write still stamped the run complete. `sqliteMirrorStore.open()` no longer caches a rejected initialization, and `close()` is safe to call while an open is in flight.
64+
- **Parser errors no longer echo library diagnostics.** JSON, YAML, XML, frontmatter, HTML, and PDF failures carried the underlying error's message, stack, and a 200-character content sample into client-facing `McpError.data`. Each now returns a fixed message plus `data.reason`, with the original error preserved on `cause` for the internal pipeline.
65+
- **`fuzzTool`/`fuzzResource` leak-check the whole client-visible payload.** Only `error.message` was scanned, so a stack trace or internal path carried in `error.data` went unreported.
66+
- **Integration suites fail on a stale or missing build.** They previously skipped themselves when `dist/` was absent and passed silently against an out-of-date one; a setup file now compares build-output mtimes against every TypeScript build input.
67+
68+
## Security
69+
70+
- **SSRF blocking covers the full non-global IP surface** — see the `rejectPrivateIPs` entry above. `::` was reachable, and the IPv6 side previously matched only link-local and ULA prefixes.
71+
- **Parser input budgets** bound the work one request can schedule inside a parser dependency, and the PDF image cap bounds raster allocation during text extraction.
72+
- **Export-path symlink rejection** closes the in-sandbox redirect for DuckDB `COPY … TO`.
73+
- **Error and log redaction** — parser diagnostics no longer reach clients, and credentials, query strings, and fragments are stripped from resource URIs and Worker request URLs before they reach logs or telemetry.
74+
- **Batch and query ceilings** on `StorageService` and `sqliteMirrorStore` bound the fan-out a single client-shaped request can create.
75+
76+
## Dependencies
77+
78+
Runtime:
79+
80+
- `@hono/node-server` `^2.0.12``^2.1.0`
81+
- `hono` `^4.12.32``^4.13.1`
82+
- `jose` `^6.2.5``^6.2.8`
83+
84+
Dev:
85+
86+
- `@duckdb/node-api` `^1.5.5-r.2``^1.5.5-r.3`
87+
- `@supabase/supabase-js` `^2.111.0``^2.112.2`
88+
- `better-sqlite3` `^13.0.2``^13.0.3`
89+
- `execa` `^10.0.0``^10.0.1`
90+
- `js-yaml` `^5.2.2``^5.2.3`
91+
- `repomix` `^1.17.0``^1.18.0`
92+
93+
Overrides:
94+
95+
- `fast-uri` `^3.1.4``^3.1.5` ([#325](https://github.com/cyanheads/mcp-ts-core/issues/325)`^3.1.4` is satisfied at 3.1.4, still inside the `>=3.0.0 <3.1.5` range of [GHSA-7p8r-x3mc-p8w7](https://github.com/advisories/GHSA-7p8r-x3mc-p8w7) and [GHSA-v2hh-gcrm-f6hx](https://github.com/advisories/GHSA-v2hh-gcrm-f6hx))
96+
- `ip-address` `^10.3.1` added ([#325](https://github.com/cyanheads/mcp-ts-core/issues/325) — clears [GHSA-mwp4-54f8-5fhr](https://github.com/advisories/GHSA-mwp4-54f8-5fhr) at `<=10.3.0`, reached through `@modelcontextprotocol/sdk › express-rate-limit`)
97+
- `postcss` `^8.5.22``^8.5.23`
98+
- `@hono/node-server` `^2.0.11` override removed

0 commit comments

Comments
 (0)