Resolve tool output-schema references within the schema document only

Merged
MA
2mo ago
+39-8

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