Resolve tool output-schema references within the schema document only - #3394
Conversation
📚 Documentation preview
|
Build the client's output-schema validator with an explicit empty `referencing.Registry`, so `$ref`s resolve within the tool's schema and the bundled metaschemas as the 2026-07-28 spec's `$ref` resolution section requires, and surface a reference that does not resolve there from `call_tool` as the documented `RuntimeError`.
55ae9fe to
1a69105
Compare
There was a problem hiding this comment.
Beyond the inline finding, I also checked the new direct referencing imports: referencing is an unconditional runtime dependency of jsonschema (required by both jsonschema and jsonschema-specifications in uv.lock), so it is always installed and no new dependency declaration is needed. In-document $ref/$defs resolution staying intact is covered by the existing recursive-schema test in tests/server/mcpserver/test_server.py.
Extended reasoning...
One candidate issue was investigated and ruled out: the diff imports referencing directly in src/mcp/client/session.py and in the new test without adding it to the project's declared dependencies. This is safe because referencing is a mandatory transitive dependency of jsonschema>=4.20.0 (declared by both jsonschema and jsonschema-specifications in uv.lock), and its Registry is the documented type of the registry= validator parameter, so it cannot be absent at runtime. The behavioural implications of the empty registry (remote/file $refs now failing instead of being fetched) are covered by the inline finding and are a maintainer-level design call, so this is left for a human to weigh rather than approved here.
| RuntimeError: Structured content is missing or does not conform to the schema, or the | ||
| schema is invalid or has a `$ref` that does not resolve within the schema document. |
There was a problem hiding this comment.
🟡 nit: This is a user-visible behaviour change — tool output schemas whose $refs point outside the document (previously auto-fetched by jsonschema, so such tools worked) now make call_tool raise RuntimeError: Invalid schema for tool ... — but no page under docs/ is updated, and AGENTS.md requires docs updates for user-visible behaviour changes in the same PR. docs/advanced/low-level-server.md (the section documenting the client-side validation RuntimeError) or docs/servers/structured-output.md should note that $refs resolve only within the schema document and how the failure surfaces.
Extended reasoning...
The PR's own "Breaking Changes" section states that a result whose validation reaches a $ref outside the schema document now fails with RuntimeError, and that a dangling in-document $ref changes exception type from a referencing error to RuntimeError. On the base branch, jsonschema's default registry auto-retrieves remote/file $refs (with a DeprecationWarning), so a tool declaring e.g. {"$ref": "https://example.com/schema.json"} validated successfully; after merge the same tool's every call_tool raises RuntimeError. AGENTS.md ("Documentation") says: "When a change affects public API or user-visible behaviour, update the relevant page(s) under docs/ in the same PR." The diff touches only src/mcp/client/session.py and tests/client/test_output_schema_validation.py; no docs file changes. docs/advanced/low-level-server.md:112 is the existing page documenting the client-side RuntimeError validation contract that users upgrading will consult when their external-$ref tools start failing, and it says nothing about $ref resolution scope. A correct fix adds a sho
Verification: nit. The diff touches only src/mcp/client/session.py and tests/client/test_output_schema_validation.py (verified via git diff e7284ed..HEAD --name-only) — no file under docs/ is updated. AGENTS.md (applicable per scope) states: "When a change affects public API or user-visible behaviour, update the relevant page(s) under docs/ in the same PR." The change is user-visible: at src/mcp/cli
Builds the client's output-schema validator with an explicit empty
referencing.Registry, so$refs in a tool'soutputSchemaresolve within that schema and the bundled metaschemas, per the 2026-07-28 spec's$refresolution section. A reference that doesn't resolve there now surfaces fromcall_toolas theRuntimeErrorthatvalidate_tool_resultdocuments.Motivation and Context
Spec conformance for the
structuredContentvalidation path.referencing.Registry()is jsonschema's documented configuration point for reference resolution, and jsonschema merges its bundled metaschemas into any registry it is given, so#/$defs/…, draft-07#/definitions/…,$anchor, embedded$idresources and$refs to the standard metaschema URIs resolve exactly as before. Passingregistry=also matches typeshed'sValidator.__init__, which lets the oldcastgo.How Has This Been Tested?
New test in
tests/client/test_output_schema_validation.pythrough in-memoryClient(server); the existing recursive-$defstest (test_recursive_tool_return_type_lists_and_calls_on_legacy_session) covers in-document references. Full suite, coverage, pyright and ruff clean locally.Breaking Changes
No API changes. A result whose validation reaches a
$refoutside the schema document now fails withRuntimeError: Invalid schema for tool <name>: …, and a dangling in-document$refsurfaces as that sameRuntimeErrorrather than areferencingexception, matching the documented contract.Types of changes
Checklist
Additional context
referencingis jsonschema's own dependency and the type of itsregistry=parameter; it is imported directly, the same waypydantic_corealready is. The one+ … # pyright: ignore[reportUnknownMemberType]in the diff is the existing suppression onbest_match, re-indented into thetry.AI Disclaimer