33import logging
44from typing import Any , cast
55
6- from fastmcp .utilities .json_schema import compress_schema
7-
86from .models import HTTPRoute , JsonSchema , ResponseInfo
97
108logger = logging .getLogger (__name__ )
@@ -314,10 +312,42 @@ def _combine_schemas_and_map_params(
314312 }
315313 # Add schema definitions if available
316314 if route .schema_definitions :
317- result ["$defs" ] = route .schema_definitions
318-
319- # Use compress_schema to remove unused definitions
320- result = compress_schema (result )
315+ result ["$defs" ] = route .schema_definitions .copy ()
316+
317+ # Use lightweight compression - prune additionalProperties and unused definitions
318+ if result .get ("additionalProperties" ) is False :
319+ result .pop ("additionalProperties" )
320+
321+ # Remove unused definitions (lightweight approach - just check direct $ref usage)
322+ if "$defs" in result :
323+ used_refs = set ()
324+
325+ def find_refs_in_value (value ):
326+ if isinstance (value , dict ):
327+ if "$ref" in value and isinstance (value ["$ref" ], str ):
328+ ref = value ["$ref" ]
329+ if ref .startswith ("#/$defs/" ):
330+ used_refs .add (ref .split ("/" )[- 1 ])
331+ for v in value .values ():
332+ find_refs_in_value (v )
333+ elif isinstance (value , list ):
334+ for item in value :
335+ find_refs_in_value (item )
336+
337+ # Find refs in the main schema (excluding $defs section)
338+ for key , value in result .items ():
339+ if key != "$defs" :
340+ find_refs_in_value (value )
341+
342+ # Remove unused definitions
343+ if used_refs :
344+ result ["$defs" ] = {
345+ name : def_schema
346+ for name , def_schema in result ["$defs" ].items ()
347+ if name in used_refs
348+ }
349+ else :
350+ result .pop ("$defs" )
321351
322352 return result , parameter_map
323353
@@ -339,17 +369,63 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
339369 return schema
340370
341371
372+ def _has_one_of (obj : dict [str , Any ] | list [Any ]) -> bool :
373+ """Quickly check if schema contains any 'oneOf' keys without deep traversal."""
374+ if isinstance (obj , dict ):
375+ if "oneOf" in obj :
376+ return True
377+ # Only check likely schema containers, skip examples/defaults
378+ for k , v in obj .items ():
379+ if k in [
380+ "properties" ,
381+ "items" ,
382+ "allOf" ,
383+ "anyOf" ,
384+ "additionalProperties" ,
385+ ] and isinstance (v , dict | list ):
386+ if _has_one_of (v ):
387+ return True
388+ elif isinstance (obj , list ):
389+ for item in obj :
390+ if isinstance (item , dict | list ) and _has_one_of (item ):
391+ return True
392+ return False
393+
394+
342395def _adjust_union_types (
343- schema : dict [str , Any ] | list [Any ],
396+ schema : dict [str , Any ] | list [Any ], _depth : int = 0
344397) -> dict [str , Any ] | list [Any ]:
345398 """Recursively replace 'oneOf' with 'anyOf' in schema to handle overlapping unions."""
399+ # MAJOR OPTIMIZATION: Skip entirely if schema has no oneOf keys
400+ if _depth == 0 and not _has_one_of (schema ):
401+ return schema
402+
403+ # OPTIMIZATION: Early termination for very deep structures to prevent exponential slowdown
404+ if _depth > 30 : # Reduced from 50 for better performance
405+ return schema
406+
346407 if isinstance (schema , dict ):
347- if "oneOf" in schema :
348- schema ["anyOf" ] = schema .pop ("oneOf" )
349- for k , v in schema .items ():
350- schema [k ] = _adjust_union_types (v )
408+ # Work on a copy to avoid mutating the input
409+ result = schema .copy ()
410+ if "oneOf" in result :
411+ result ["anyOf" ] = result .pop ("oneOf" )
412+ # OPTIMIZATION: Only recurse into values that could contain more schemas
413+ for k , v in result .items ():
414+ if isinstance (v , dict | list ) and k not in [
415+ "examples" ,
416+ "example" ,
417+ "default" ,
418+ ]:
419+ result [k ] = _adjust_union_types (v , _depth + 1 )
420+ return result
351421 elif isinstance (schema , list ):
352- return [_adjust_union_types (item ) for item in schema ]
422+ # Process list items without mutating the input list
423+ return [
424+ _adjust_union_types (item , _depth + 1 )
425+ if isinstance (item , dict | list )
426+ else item
427+ for item in schema
428+ ]
353429 return schema
354430
355431
@@ -436,10 +512,42 @@ def extract_output_schema_from_responses(
436512
437513 # Add schema definitions if available
438514 if schema_definitions :
439- output_schema ["$defs" ] = schema_definitions
440-
441- # Use compress_schema to remove unused definitions
442- output_schema = compress_schema (output_schema )
515+ output_schema ["$defs" ] = schema_definitions .copy ()
516+
517+ # Use lightweight compression - prune additionalProperties and unused definitions
518+ if output_schema .get ("additionalProperties" ) is False :
519+ output_schema .pop ("additionalProperties" )
520+
521+ # Remove unused definitions (lightweight approach - just check direct $ref usage)
522+ if "$defs" in output_schema :
523+ used_refs = set ()
524+
525+ def find_refs_in_value (value ):
526+ if isinstance (value , dict ):
527+ if "$ref" in value and isinstance (value ["$ref" ], str ):
528+ ref = value ["$ref" ]
529+ if ref .startswith ("#/$defs/" ):
530+ used_refs .add (ref .split ("/" )[- 1 ])
531+ for v in value .values ():
532+ find_refs_in_value (v )
533+ elif isinstance (value , list ):
534+ for item in value :
535+ find_refs_in_value (item )
536+
537+ # Find refs in the main schema (excluding $defs section)
538+ for key , value in output_schema .items ():
539+ if key != "$defs" :
540+ find_refs_in_value (value )
541+
542+ # Remove unused definitions
543+ if used_refs :
544+ output_schema ["$defs" ] = {
545+ name : def_schema
546+ for name , def_schema in output_schema ["$defs" ].items ()
547+ if name in used_refs
548+ }
549+ else :
550+ output_schema .pop ("$defs" )
443551
444552 # Adjust union types to handle overlapping unions
445553 output_schema = cast (dict [str , Any ], _adjust_union_types (output_schema ))
0 commit comments