Skip to content

Resolve tool output-schema references within the schema document only - #3394

Merged
maxisbey merged 1 commit into
mainfrom
local-ref-resolution
Aug 26, 2026
Merged

Resolve tool output-schema references within the schema document only#3394
maxisbey merged 1 commit into
mainfrom
local-ref-resolution

Conversation

@maxisbey

@maxisbey maxisbey commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Builds the client's output-schema validator with an explicit empty referencing.Registry, so $refs in a tool's outputSchema resolve within that schema and the bundled metaschemas, per the 2026-07-28 spec's $ref resolution section. A reference that doesn't resolve there now surfaces from call_tool as the RuntimeError that validate_tool_result documents.

Motivation and Context

Spec conformance for the structuredContent validation 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 $id resources and $refs to the standard metaschema URIs resolve exactly as before. Passing registry= also matches typeshed's Validator.__init__, which lets the old cast go.

How Has This Been Tested?

New test in tests/client/test_output_schema_validation.py through in-memory Client(server); the existing recursive-$defs test (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 $ref outside the schema document now fails with RuntimeError: Invalid schema for tool <name>: …, and a dangling in-document $ref surfaces as that same RuntimeError rather than a referencing exception, matching the documented contract.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

referencing is jsonschema's own dependency and the type of its registry= parameter; it is imported directly, the same way pydantic_core already is. The one + … # pyright: ignore[reportUnknownMemberType] in the diff is the existing suppression on best_match, re-indented into the try.

AI Disclaimer

@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3394.mcp-python-docs.pages.dev
Deployment https://f2add2f5.mcp-python-docs.pages.dev
Commit 9e21cd0
Triggered by @maxisbey
Updated 2026-08-26 11:15:07 UTC

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`.
@maxisbey
maxisbey force-pushed the local-ref-resolution branch from 55ae9fe to 1a69105 Compare August 26, 2026 12:25
@maxisbey
maxisbey marked this pull request as ready for review August 26, 2026 12:29

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/mcp/client/session.py
Comment on lines +1129 to +1130
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 6705402 into main Aug 26, 2026
38 checks passed
@maxisbey
maxisbey deleted the local-ref-resolution branch August 26, 2026 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant