Visitar URL original
Don't mutate the caller's schema in compress_schema by winklemad · Pull Request #4492 · PrefectHQ/fastmcp · GitHub
Skip to content

Don't mutate the caller's schema in compress_schema - #4492

Merged
jlowin merged 1 commit into
PrefectHQ:mainfrom
winklemad:fix/compress-schema-no-mutate
Jul 17, 2026
Merged

jlowin merged 1 commit into
PrefectHQ:mainfrom
winklemad:fix/compress-schema-no-mutate

Conversation

@winklemad

@winklemad winklemad commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Closes #4493.

Summary

compress_schema() mutates the caller's schema in place, directly contradicting its own documented contract. _single_pass_optimize (which compress_schema calls) states:

Immutable design prevents shared reference bugs
schema: JSON schema dict to optimize (not modified)
Returns: A new optimized schema dict

But its pruning phases .pop() title, additionalProperties, and unused $defs off the passed-in dict:

>>> from fastmcp.utilities.json_schema import compress_schema
>>> schema = {"type": "object", "title": "S", "additionalProperties": False,
...           "properties": {"a": {"type": "string", "title": "A"}},
...           "$defs": {"Unused": {"type": "string"}}}
>>> compress_schema(schema, prune_titles=True, prune_additional_properties=True)
{'type': 'object', 'properties': {'a': {'type': 'string'}}}
>>> schema  # the caller's object was stripped in place
{'type': 'object', 'properties': {'a': {'type': 'string'}}}

This bites at real call sites that pass a live object — e.g. client/sampling/handlers/google_genai.py calls compress_schema(tool.input_schema, ...) when converting a tool for Gemini, which permanently strips title/additionalProperties/$defs off the shared Tool object — exactly the "shared reference bug" the docstring says it prevents.

Fix

Deep-copy the schema before the in-place pruning phases, matching the sibling _prune_param which already does schema = copy.deepcopy(schema). One line; copy is already imported. Because the copy happens at the single entry point (_single_pass_optimize), it covers every compress_schema caller.

Test

Adds TestCompressSchema::test_does_not_mutate_input (mirroring the existing _prune_param non-mutation test): a schema with a title, additionalProperties, nested titles and an unused $def is left untouched after compress_schema(..., prune_titles=True, prune_additional_properties=True), and the returned copy is confirmed to actually be optimized (so it isn't a no-op). It fails on the current code and passes with the fix; the full test_json_schema.py suite passes and ruff check + format are clean.

`_single_pass_optimize` (the core of `compress_schema`) documents itself as
immutable -- "schema not modified", "returns a new optimized schema dict",
"immutable design prevents shared reference bugs" -- but its pruning phases
pop `title`, `additionalProperties` and unused `$defs` off the passed-in
dict. Callers that pass a live object (e.g. `compress_schema(tool.input_schema)`
when converting a tool for Gemini) have that shared object permanently
stripped. Deep-copy the schema before pruning, matching the sibling
`_prune_param`, which already does the same.
@github-actions

This comment has been minimized.

@github-actions github-actions Bot closed this Jul 10, 2026
@marvin-context-protocol marvin-context-protocol Bot added the bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. label Jul 10, 2026
@github-actions github-actions Bot reopened this Jul 17, 2026
@jlowin
jlowin merged commit 6202008 into PrefectHQ:main Jul 17, 2026
22 of 25 checks passed
jlowin added a commit that referenced this pull request Jul 27, 2026
…mpress_schema (#4663)

* Don't mutate the caller's schema in compress_schema (#4492)

(cherry picked from commit 6202008)

* Copy schemas iteratively so deep nesting still compresses

(cherry picked from commit 2662e67)

* Keep $defs when the reference scan hits its depth limit

(cherry picked from commit 054a6cf)

---------

Co-authored-by: WinkleMad <winklemad@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

compress_schema mutates the caller's schema in place (contradicts its docstring)

2 participants