You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: SEP-2106/1613 migration + changesets + schema-validators example
migration.md / migration-SKILL.md: new "JSON Schema 2020-12 posture"
section covering the Ajv2020 default (with the v1 draft-07 opt-back
recipe), the structuredContent: unknown source-level break and narrowing
pattern, the legacy {result:…} wrap for non-object outputSchema /
structuredContent (with the $ref-pointer rewrite), and the
provably-object-shaped typeless-root stamping rule. External $ref is
documented as not-dereferenced (unchanged from v1; engine MissingRefError,
surfaced per-tool on callTool) — not a new break.
client.md / server.md: structuredContent narrowing example; drop the
obsolete "use a type alias not an interface" note (structuredContent is no
longer index-signatured).
schema-validators example: new list-forecasts tool (array outputSchema /
structuredContent) demonstrating the auto TextContent fallback, the
known-server cast idiom on the modern leg, and the {result:…} unwrap on the
legacy leg.
Changesets: sep-2106-dialect-posture (minor across core/client/server);
client-response-cache-substrate updated for the per-tool InvalidParams
behavior.
Copy file name to clipboardExpand all lines: .changeset/client-response-cache-substrate.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,4 +4,4 @@
4
4
5
5
`Client.listTools()` / `listPrompts()` / `listResources()` / `listResourceTemplates()` now **auto-aggregate every page** when called without a `cursor` and return the complete result with `nextCursor: undefined` (matching the C#, Java, and mcp.d SDKs). Pass an explicit `{ cursor }` string to fetch a single page; the per-page path is unchanged. Existing manual pagination loops keep working — the first iteration returns everything and the loop exits — but can be deleted. The aggregated result is written to the new pluggable `ResponseCacheStore` (default: a fresh per-instance `InMemoryResponseCacheStore`); a `ClientResponseCache` collaborator owns the eviction-generation guard and the derived `tools/list` index that `callTool`'s output validation and SEP-2243 `Mcp-Param-*` mirroring read. New exports: `ResponseCacheStore`, `CacheKey`, `CacheEntry`, `CacheScope`, `MaybePromise`, `InMemoryResponseCacheStore`; new `ClientOptions.responseCacheStore` / `ClientOptions.listMaxPages` (caps the auto-aggregate walk at 64 pages by default; throws `SdkError` with `SdkErrorCode.ListPaginationExceeded` on overrun so a partial aggregate is never cached). The store interface is async-ready (`MaybePromise<…>`); the in-memory default stays synchronous. Entries are automatically scoped by the connected server's identity and (when set) the consumer-supplied `cachePartition`, so a shared store does not collide across servers or principals; evictions are likewise scoped to the connected server's partitions.
6
6
7
-
**Behavior change (every era):** output-schema validator compilation is now lazy — validators are compiled on the first `callTool()` against the cached `tools/list` entry, not eagerly inside `listTools()` — and non-throwing: an uncompilable `outputSchema`is `console.warn`-ed and validation is skipped for that tool only (previously `listTools()`threw). A pluggable `jsonSchemaValidator` provider therefore observes compilation at `callTool` time, not `listTools` time. The legacy-era `listTools()` path is unchanged at the wire level but is observably different at the validator-lifecycle level.
7
+
**Behavior change (every era):** output-schema validator compilation is now lazy — validators are compiled on the first `callTool()` against the cached `tools/list` entry, not eagerly inside `listTools()`. `listTools()` no longer throws on an uncompilable `outputSchema`(every tool stays listed; the compile failure is captured per-tool); calling `callTool()` on the affected tool throws `ProtocolError(InvalidParams, "Tool 'X' has an invalid outputSchema: …")`before the request is sent — output-schema validation is never silently skipped. A pluggable `jsonSchemaValidator` provider therefore observes compilation at `callTool` time, not `listTools` time. The legacy-era `listTools()` path is unchanged at the wire level but is observably different at the validator-lifecycle level.
SEP-1613 / SEP-2106 (JSON Schema 2020-12 posture): the Node default JSON Schema validator is now `Ajv2020` (true draft 2020-12) instead of the draft-07 `Ajv` class — `$defs`/`prefixItems`/`unevaluatedProperties`/`dependentRequired` are now enforced where they were previously silently ignored; to opt back, construct the draft-07 instance with the v1 defaults — `const ajv = new Ajv({ strict: false, validateFormats: true, validateSchema: false, allErrors: true }); addFormats(ajv);` — and pass `new AjvJsonSchemaValidator(ajv)`. Schemas declaring a `$schema` other than 2020-12 are rejected with a clear error rather than mis-validating. `outputSchema` may now have a non-object root and `CallToolResult.structuredContent` is widened to `unknown` (a deliberate source-level break for typed consumers — see the migration guide for the narrowing pattern). Toward 2025-era clients McpServer wraps a non-object `outputSchema` (and the matching `structuredContent`) in a `{result: …}` envelope so the tool stays callable, with same-document `$ref` pointers rewritten to keep resolving; it also auto-appends a `TextContent` JSON serialisation when a handler returns non-object `structuredContent` without its own text block. The `structuredContent` presence check is `!== undefined` (not falsy) on both sides. Thanks @mattzcarey (#2249).
Copy file name to clipboardExpand all lines: docs/client.md
+8-4Lines changed: 8 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -269,17 +269,21 @@ const result = await client.callTool({
269
269
console.log(result.content);
270
270
```
271
271
272
-
Tool results may include a `structuredContent` field — a machine-readable JSON object for programmatic use by the client application, complementing `content` which is for the LLM:
272
+
Tool results may include a `structuredContent` field — a machine-readable JSON value (any JSON type per SEP-2106) for programmatic use by the client application, complementing `content` which is for the LLM:
Copy file name to clipboardExpand all lines: docs/migration-SKILL.md
+13-1Lines changed: 13 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -574,7 +574,7 @@ side: auto-fulfilment is on by default (`ClientOptions.inputRequired`, `maxRound
574
574
575
575
`Client.listTools()` / `listPrompts()` / `listResources()` / `listResourceTemplates()` / `readResource()` now honour the server-stamped SEP-2549 `ttlMs`/`cacheScope`: a still-fresh cached entry is returned without a round trip. Opt-in by server hint — a server that sends `ttlMs: 0` (the SDK's default stamp) sees no behaviour change. Per-call override: pass `{ cacheMode: 'refresh' }` (always fetch and re-store) or `{ cacheMode: 'bypass' }` (fetch without touching the cache). Server `ttlMs` is clamped at 24 h (`MAX_CACHE_TTL_MS`). Entries are automatically scoped by connected-server identity; new `ClientOptions.cachePartition` (per-principal slot for `'private'`-scoped entries on a shared `responseCacheStore`; default `''`) and `ClientOptions.defaultCacheTtlMs` (TTL when the result lacks one, e.g. legacy-era responses; default `0`). `ResponseCacheStore` gained `delete(key)` (driven by `notifications/resources/updated`); `InMemoryResponseCacheStore` is now bounded (`{ maxEntries }`, default 512).
576
576
577
-
Output-schema validator compilation is now lazy (first `callTool()` against the cached `tools/list` entry) and non-throwing (an uncompilable `outputSchema`is `console.warn`-ed and validation is skipped for that tool only); `listTools()`no longer throws on an uncompilable `outputSchema`. Applies on every era — the legacy-era `listTools()` path is unchanged at the wire level only.
577
+
Output-schema validator compilation is now lazy (first `callTool()` against the cached `tools/list` entry); `listTools()` no longer throws on an uncompilable `outputSchema`— every tool stays listed and the compile failure is captured per-tool. Calling `callTool()`on the affected tool throws `ProtocolError(InvalidParams, "Tool 'X' has an invalid outputSchema: …")` before the request is sent (validation is never silently skipped). Applies on every era — the legacy-era `listTools()` path is unchanged at the wire level only.
578
578
579
579
No code changes required; wire-behavior note: on a 2026-07-28 Streamable HTTP connection, aborting an in-flight client request (caller `signal` / timeout) closes that request's SSE response stream as the spec cancellation signal — `notifications/cancelled` is no longer POSTed
580
580
there. 2025-era connections and stdio at any era still send `notifications/cancelled`. Custom `Transport` implementations that open one underlying request per outbound message and honor `TransportSendOptions.requestSignal` may declare `readonly hasPerRequestStream = true` to opt
@@ -661,6 +661,18 @@ Validator behavior:
661
661
`@modelcontextprotocol/{client,server}/validators/cf-worker` for `CfWorkerJsonSchemaValidator`. Importing from a subpath means the corresponding peer dep must be in your `package.json`.
662
662
- To replace validation entirely, pass `jsonSchemaValidator: myCustomValidator` with your own implementation of the `jsonSchemaValidator` interface.
663
663
664
+
JSON Schema 2020-12 posture (SEP-1613 / SEP-2106): the default validator supports JSON Schema 2020-12 only (the spec's only MUST) — on Node it is now `Ajv2020` instead of draft-07 `Ajv`. Schemas declaring a different `$schema` are rejected with a clear `Error("…unsupported
665
+
dialect…")`; to validate other dialects, pass a pre-configured Ajv instance: `new AjvJsonSchemaValidator(new Ajv({...}))`. `CallToolResult.structuredContent` is typed `unknown` (was `{ [k: string]: unknown }`). The presence check is `!== undefined`, not falsy. External `$ref`
666
+
is not dereferenced (unchanged from v1; Ajv throws `MissingRefError` at compile, surfaced per-tool on `callTool`). Toward 2025-era clients a non-object `outputSchema`/`structuredContent` is wrapped in a `{result:…}` envelope.
| relying on default `Ajv` being draft-07 |`const ajv = new Ajv({ strict: false, validateFormats: true, validateSchema: false, allErrors: true }); addFormats(ajv); new AjvJsonSchemaValidator(ajv)` (import `Ajv`, `addFormats` from `…/validators/ajv`) |
673
+
| draft-07 idioms via `fromJsonSchema(schema)`|`fromJsonSchema(schema, new AjvJsonSchemaValidator(ajv))` — the `McpServer`/`Client``jsonSchemaValidator` option does **not** reach `fromJsonSchema`-authored schemas |
674
+
|`outputSchema` or `inputSchema` with absolute-URI `$ref`| inline under `$defs` and reference with `#/$defs/Name`|
0 commit comments