@@ -358,6 +358,39 @@ def test_preserves_property_named_discriminator(self):
358358class TestCompressSchema :
359359 """Tests for the compress_schema function."""
360360
361+ def test_does_not_mutate_input (self ):
362+ """compress_schema must return a new dict and leave the caller's schema
363+ untouched, even when it prunes titles, additionalProperties and unused
364+ $defs (a live Tool.input_schema is passed straight in at some call sites)."""
365+ schema = {
366+ "type" : "object" ,
367+ "title" : "MySchema" ,
368+ "additionalProperties" : False ,
369+ "properties" : {
370+ "a" : {"type" : "string" , "title" : "A" },
371+ "b" : {
372+ "type" : "object" ,
373+ "title" : "B" ,
374+ "properties" : {"c" : {"type" : "integer" , "title" : "C" }},
375+ },
376+ },
377+ "$defs" : {"Unused" : {"type" : "string" , "title" : "Unused" }},
378+ }
379+ original = copy .deepcopy (schema )
380+
381+ result = compress_schema (
382+ schema , prune_titles = True , prune_additional_properties = True
383+ )
384+
385+ # The input is untouched...
386+ assert schema == original
387+ assert result is not schema
388+ # ...and the returned copy really was optimized (so it is not a no-op).
389+ assert "title" not in result
390+ assert "title" not in result ["properties" ]["b" ]["properties" ]["c" ]
391+ assert "additionalProperties" not in result
392+ assert "$defs" not in result
393+
361394 def test_preserves_refs_by_default (self ):
362395 """Test that compress_schema preserves $refs by default."""
363396 schema = {
0 commit comments