-
Notifications
You must be signed in to change notification settings - Fork 2.1k
fix(core-internal): make zod toJSONSchema conversion wire-truthful for tool schemas #2556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
claude
wants to merge
37
commits into
main
Choose a base branch
from
fix/2464-tojsonschema-options
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
3c067c4
fix(core-internal): make zod toJSONSchema conversion wire-truthful fo…
claude 034ccc1
fix(core-internal): address review findings on zod conversion options
claude d11d6fc
fix(core-internal): harden the missing-key probe and cover enum-keyed…
claude 665a056
docs(core-internal): scope wire-truthfulness claims to exclude pipe/c…
claude 802ac99
docs(core-internal): note zod <4.3.0 override skip on reused cloned s…
claude d28811d
fix(core-internal): close async-default, .catch(), and unrepresentabl…
claude b2bd59d
fix(core-internal): root-safe .catch() degrade, x-* annotations, full…
claude a1484e8
fix(core-internal): protect root-composition catch members and unwrap…
claude 0cee901
fix(core-internal): position-independent catch degrade; unwrap pipe/p…
claude 16fab72
fix(core-internal): composition-aware root guard, symbol/function req…
claude 568ebcf
fix(core-internal): correct composition member classification; object…
claude 591c054
fix(core-internal): classify date members and non-finite literals; un…
claude 4a92acc
fix(core-internal): skeletonize oneOf as anyOf; accept mixed represen…
claude 6018eae
fix(core-internal): broaden structural tolerance walk; loud/quiet roo…
claude 8034645
fix(core-internal): preprocess-wrapped roots; optional/void/intersect…
claude 98a3379
fix(core-internal): loosen parent oneOf when a conversion degraded no…
claude faef197
Merge remote-tracking branch 'origin/main' into fix/2464-tojsonschema…
claude 19c5b62
fix(core-internal): null-tolerant array/tuple elements; quiet never v…
claude 41e50b7
fix(core-internal): output-root loudness parity; file verdicts; posit…
claude 36657a3
fix(core-internal): schema-position keyword classification; strip reg…
claude 8270f46
fix(core-internal): io-aware pipe loudness; quiet symbol literals; an…
claude f105c75
fix(core-internal): conjunction-preserving oneOf rewrite; skip negate…
claude 6814683
fix(core-internal): all-key object proof; contains skip; io-aware big…
claude 3b72567
fix(core-internal): restore every()-proof for untouched allOf; extend…
claude eaa15f2
fix(core-internal): complete reference-target keep; loud output date …
claude 8fdacf4
fix(core-internal): first-present-key proof; pointer rewrite for move…
claude 8494566
fix(core-internal): override-time pointer integrity; dangling-ref neu…
claude 66559b9
fix(core-internal): anchor/base-addressed dangle repair; polarity-awa…
claude 8007798
fix(core-internal): array/tuple representable verdicts; per-resource …
claude 21ae868
refactor(core-internal): replace ref-repair machinery with a referenc…
claude 36c5ea3
fix(core-internal): genuine object proof for the allOf-push stamp; po…
claude 31b6746
fix(core-internal): copy-on-wrap tuple prefixItems; legacy-draft guar…
claude c105ae7
fix(core-internal): wrap stamp reads the strict pre-loosen snapshot; …
claude 37d8eb4
fix(core-internal): nonoptional probe deferral; deferred id strip on …
claude 74ab2bd
fix(core-internal): pipe tolerance requires bare-transform OUT; unifo…
claude a0a0252
fix(core-internal): prefault/intersection/promise tolerance deferral;…
claude 53f2be5
fix(core-internal): checks-aware tolerance; plain-object fill gate; r…
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| --- | ||
| '@modelcontextprotocol/core-internal': patch | ||
| '@modelcontextprotocol/server': patch | ||
| --- | ||
|
|
||
| Make zod-to-JSON-Schema conversion wire-truthful for tool schemas. A `z.date()` (or another | ||
| unrepresentable type such as `z.bigint()`) in a registered tool's schema no longer throws | ||
| during conversion and fails the entire `tools/list` response — dates are advertised as | ||
| `{type: 'string', format: 'date-time'}` (the shape `JSON.stringify` actually produces), and | ||
| other unrepresentable types degrade to an unconstrained schema. (BigInt values embedded as | ||
| defaults or metadata, e.g. `.default(0n)`, still fail conversion — JSON cannot carry them — | ||
| and so do dynamic catch values, `.catch(ctx => …)`; the `.catch()` degrade covers static | ||
| fallback values only. And a misregistered non-object ROOT — `z.bigint()` or `z.map()` as | ||
| the whole `inputSchema`/`outputSchema` — still fails `tools/list` loudly by design, | ||
| preserving the pre-fix error instead of listing a permanently-broken tool.) | ||
| Registry metadata (`.meta({id: 'X'})`) no longer emits the draft-04 `id` keyword on either | ||
| io path — Ajv v8 hard-rejects it at compile time ('NOT SUPPORTED: keyword "id"'), so the | ||
| SDK's own client could never validate such advertisements. The key is kept only when the | ||
| document carries a hand-authored ref beyond zod's registry shapes: those may resolve | ||
| through the `id` base-URI on the `@cfworker/json-schema` engine (URI-form refs, and | ||
| fragment pointers inside an `id` resource), so stripping would break them — such documents | ||
| ship with `id` intact, exactly as pre-fix. | ||
| Output schemas no longer advertise constraints the server doesn't enforce on the raw | ||
| `structuredContent` it ships: fields that may be legitimately absent (`.default()`, | ||
| undefined-accepting types) are dropped from `required` — on objects and enum-keyed records — | ||
| and `additionalProperties: false` is dropped for plain `z.object()` (kept for | ||
| `z.strictObject()`), so validating clients no longer reject legitimate tool results for | ||
| these schema shapes. Once any such loosening applies, exactly-one `oneOf` compositions — | ||
| including zod's discriminated-union emissions over plain objects, i.e. most of them — are | ||
| advertised as `anyOf` (the loosened members may overlap; discriminator consts keep them | ||
| distinguishable), and the serialized wire forms of tolerant values are additionally | ||
| accepted: tolerant array/tuple elements also allow `null` (what `JSON.stringify` makes of | ||
| an undefined element), `z.file()` fields also allow `{}` (a `File` has no JSON form), and | ||
| non-finite number literals also allow `null`. (Output schemas containing | ||
| `.transform()`/`.pipe()`/`z.coerce` still | ||
| advertise the post-transform shape while the server ships the raw pre-transform value — a | ||
| pre-existing gap this change does not address. And on zod 4.0–4.2.x, `toJSONSchema` skips | ||
| the sanitization hook on a schema reused both bare and via a `.describe()`/`.meta()` clone | ||
| in the same conversion; full per-node sanitization requires zod >=4.3.0. And the | ||
| `z.date()` advertisement assumes a serializing transport: `InMemoryTransport` passes the | ||
| raw `Date` by reference, so a validating client rejects it over that testing transport. | ||
| On the input side, a required tool/prompt argument of a type JSON cannot carry makes the | ||
| tool listed yet uncallable: `z.date()` is advertised as `string`/`date-time` and other | ||
| unrepresentable types (`z.bigint()`, `z.map()`, `z.set()`, `z.symbol()`) as an | ||
| unconstrained `{}`, but input validation still runs the raw zod schema, which rejects | ||
| every JSON payload — use a JSON-representable type such as `z.iso.date()`/ | ||
| `z.iso.datetime()`, `z.number()`, `z.record(...)`, or `z.array(...)`, or make the field | ||
| optional. The same holds for required OUTPUT fields of such types: bigint results fail | ||
| JSON-RPC serialization and Map/Set values serialize as `{}`. And a degraded | ||
| object-`.catch()` node keeps `type: 'object'` for the 2025-era wrap proof even though | ||
| catch-validation does not enforce it on the raw value. | ||
| Hand-authored reference keywords disable the loosening: a `.meta()`/registry-injected | ||
| `$ref`/`$dynamicRef` beyond zod's own registry shapes (`#`, `#/$defs/<name>`), ANY ref — | ||
| registry-shaped included — consumed under a `not`/`if`/`contains` keyword, any | ||
| `$anchor`/`$dynamicAnchor` or `$id`, any `$recursiveRef`/`$recursiveAnchor`, a non-root | ||
| `$defs`, or any hand-authored `unevaluatedProperties`/`unevaluatedItems` makes the | ||
| conversion ship the strict pre-fix-shaped emission instead — such constructs would observe | ||
| the loosening's rewrites as dangling pointers, stale anchors, polarity-inverted negations, | ||
| or stripped evaluation annotations, so those schemas keep pre-fix strictness, compilable | ||
| and working by construction.) Elicitation is unaffected: | ||
| `inputRequired.elicit()` keeps throwing on schemas its restricted form grammar cannot | ||
| round-trip, including `z.date()`. | ||
|
claude[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.