| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
`validate_tool_result` called one-shot `jsonschema.validate()` on every tool result, which rebuilt the validator class, re-ran `check_schema`, and re-crawled `$ref`s each time. Compile the validator once per schema instead, keyed on the identity of the cached schema object so a relisted tool can never reuse a validator built from its previous schema. Refs modelcontextprotocol#3133.
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Sorry, something went wrong.
_absorb_tool_listing now owns the compiled-validator lifecycle: an unchanged output schema (a re-listing, or the response cache re-absorbing a served hit) keeps its validator, a changed schema evicts it, and a complete listing prunes validators for tools the server no longer lists, alongside the other per-tool state. Reusing on schema value rather than object identity keeps validators across relistings — a re-parsed or deep-copied listing yields a fresh but equal schema object, which the identity check treated as a change and rebuilt for. Schemas are compared as canonical JSON so `const: true` and `const: 1` stay distinct. Also chains the structured-content RuntimeError to the underlying ValidationError so its structured fields remain reachable via __cause__.
…t-schema-validators # Conflicts: # src/mcp/client/session.py
| Back | FazBrowse Home | New Git URL |
Closes #3133.
ClientSession.validate_tool_result called one-shot jsonschema.validate() on every
tool result. That rebuilds the validator class, re-runs check_schema(), and re-crawls
$refs on each call, then throws the whole thing away. Compiling costs ~60x what
validating costs, so on an in-memory call_tool it was the dominant term. This compiles
the validator once and reuses it.
Measured end-to-end on an in-memory call_tool (median of 2000 calls after warm-up):
Behavior is unchanged. The cache stores the schema object it compiled alongside the
validator and only reuses it on an identity match — _absorb_tool_listing assigns a
freshly parsed schema object on every listing, so a server that changes a tool's output
schema forces a rebuild rather than being served the stale validator. A failed
check_schema() is never cached, so an invalid schema still raises
RuntimeError("Invalid schema for tool ...") on every call, as before. Validation errors
still go through best_match, which is what jsonschema.validate() used internally, so
error messages are identical. The jsonschema import stays inside the method to keep it
(and its attrs/referencing tree) off the cold-start path for clients that never
validate.
Tests cover the reuse (the regression guard) and the changed-schema rebuild (the
correctness guard), plus the invalid-schema path — which was previously marked
# pragma: no cover; those pragmas are now removed.
AI assistance disclosure: this change was written with Claude Code. I reviewed the design
and the diff, ran the suite, coverage, pyright, and ruff locally, and can speak to it.