From 14d1b8a94d8d56ff66c80e07cfddf41b28c35f35 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Fri, 20 Jun 2025 14:30:55 -0400 Subject: [PATCH] openapi: Rewrite recursive #/components/schemas/ references When a schema referenced another schema as #/components/schemas/... that wasn't properly rewritten into #/$defs/.. --- src/fastmcp/utilities/openapi.py | 8 ++++--- .../openapi/test_openapi_advanced.py | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/fastmcp/utilities/openapi.py b/src/fastmcp/utilities/openapi.py index 44c0216f6..0cae85cb2 100644 --- a/src/fastmcp/utilities/openapi.py +++ b/src/fastmcp/utilities/openapi.py @@ -262,16 +262,18 @@ class OpenAPIParser( if isinstance(resolved_schema, (self.schema_cls)): # Convert schema to dictionary - return resolved_schema.model_dump( + result = resolved_schema.model_dump( mode="json", by_alias=True, exclude_none=True ) elif isinstance(resolved_schema, dict): - return resolved_schema + result = resolved_schema else: logger.warning( f"Expected Schema after resolving, got {type(resolved_schema)}. Returning empty dict." ) - return {} + result = {} + + return _replace_ref_with_defs(result) except Exception as e: logger.error(f"Failed to extract schema as dict: {e}", exc_info=False) return {} diff --git a/tests/utilities/openapi/test_openapi_advanced.py b/tests/utilities/openapi/test_openapi_advanced.py index 6b7ec8af3..979ca9b28 100644 --- a/tests/utilities/openapi/test_openapi_advanced.py +++ b/tests/utilities/openapi/test_openapi_advanced.py @@ -294,6 +294,28 @@ def test_complex_schema_route_count(parsed_complex_routes): assert len(parsed_complex_routes) == 3 +def test_complex_schema_ref_rewriting(parsed_complex_routes): + """Test that all #/components references have been rewritten.""" + + def no_components(value): + if isinstance(value, dict): + for k, v in value.items(): + if k == "$ref": + assert not v.startswith("#/components/"), ( + f"reference '{v}' was not rewritten" + ) + else: + no_components(v) + elif isinstance(value, list): + for v in value: + no_components(v) + + for route in parsed_complex_routes: + no_components(route.schema_definitions) + for param in route.parameters: + no_components(param.schema_) + + def test_complex_schema_list_users_query_param_limit(complex_route_map): """Test that a reference to a limit query parameter is correctly resolved.""" list_users = complex_route_map["listUsers"]