mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 21:44:18 +02:00
Merge pull request #954 from jlowin/claude-wt-20250625-210215
Fix external schema reference handling in OpenAPI parser
This commit is contained in:
commit
159324ea46
2 changed files with 111 additions and 0 deletions
|
|
@ -274,6 +274,12 @@ class OpenAPIParser(
|
|||
result = {}
|
||||
|
||||
return _replace_ref_with_defs(result)
|
||||
except ValueError as e:
|
||||
# Re-raise ValueError for external reference errors and other validation issues
|
||||
if "External or non-local reference not supported" in str(e):
|
||||
raise
|
||||
logger.error(f"Failed to extract schema as dict: {e}", exc_info=False)
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to extract schema as dict: {e}", exc_info=False)
|
||||
return {}
|
||||
|
|
@ -406,12 +412,30 @@ class OpenAPIParser(
|
|||
request_body_info.content_schema[media_type_str] = (
|
||||
schema_dict
|
||||
)
|
||||
except ValueError as e:
|
||||
# Re-raise ValueError for external reference errors
|
||||
if "External or non-local reference not supported" in str(
|
||||
e
|
||||
):
|
||||
raise
|
||||
logger.error(
|
||||
f"Failed to extract schema for media type '{media_type_str}': {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to extract schema for media type '{media_type_str}': {e}"
|
||||
)
|
||||
|
||||
return request_body_info
|
||||
except ValueError as e:
|
||||
# Re-raise ValueError for external reference errors
|
||||
if "External or non-local reference not supported" in str(e):
|
||||
raise
|
||||
ref_name = getattr(request_body_or_ref, "ref", "unknown")
|
||||
logger.error(
|
||||
f"Failed to extract request body '{ref_name}': {e}", exc_info=False
|
||||
)
|
||||
return None
|
||||
except Exception as e:
|
||||
ref_name = getattr(request_body_or_ref, "ref", "unknown")
|
||||
logger.error(
|
||||
|
|
@ -455,6 +479,17 @@ class OpenAPIParser(
|
|||
media_type_obj.media_type_schema
|
||||
)
|
||||
resp_info.content_schema[media_type_str] = schema_dict
|
||||
except ValueError as e:
|
||||
# Re-raise ValueError for external reference errors
|
||||
if (
|
||||
"External or non-local reference not supported"
|
||||
in str(e)
|
||||
):
|
||||
raise
|
||||
logger.error(
|
||||
f"Failed to extract schema for media type '{media_type_str}' "
|
||||
f"in response {status_code}: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to extract schema for media type '{media_type_str}' "
|
||||
|
|
@ -462,6 +497,16 @@ class OpenAPIParser(
|
|||
)
|
||||
|
||||
extracted_responses[str(status_code)] = resp_info
|
||||
except ValueError as e:
|
||||
# Re-raise ValueError for external reference errors
|
||||
if "External or non-local reference not supported" in str(e):
|
||||
raise
|
||||
ref_name = getattr(resp_or_ref, "ref", "unknown")
|
||||
logger.error(
|
||||
f"Failed to extract response for status code {status_code} "
|
||||
f"from reference '{ref_name}': {e}",
|
||||
exc_info=False,
|
||||
)
|
||||
except Exception as e:
|
||||
ref_name = getattr(resp_or_ref, "ref", "unknown")
|
||||
logger.error(
|
||||
|
|
@ -562,6 +607,17 @@ class OpenAPIParser(
|
|||
logger.info(
|
||||
f"Successfully extracted route: {method_upper} {path_str}"
|
||||
)
|
||||
except ValueError as op_error:
|
||||
# Re-raise ValueError for external reference errors
|
||||
if "External or non-local reference not supported" in str(
|
||||
op_error
|
||||
):
|
||||
raise
|
||||
op_id = getattr(operation, "operationId", "unknown")
|
||||
logger.error(
|
||||
f"Failed to process operation {method_upper} {path_str} (ID: {op_id}): {op_error}",
|
||||
exc_info=True,
|
||||
)
|
||||
except Exception as op_error:
|
||||
op_id = getattr(operation, "operationId", "unknown")
|
||||
logger.error(
|
||||
|
|
@ -907,6 +963,12 @@ def _replace_ref_with_defs(
|
|||
if ref_path.startswith("#/components/schemas/"):
|
||||
schema_name = ref_path.split("/")[-1]
|
||||
schema["$ref"] = f"#/$defs/{schema_name}"
|
||||
elif not ref_path.startswith("#/"):
|
||||
raise ValueError(
|
||||
f"External or non-local reference not supported: {ref_path}. "
|
||||
f"FastMCP only supports local schema references starting with '#/'. "
|
||||
f"Please include all schema definitions within the OpenAPI document."
|
||||
)
|
||||
elif properties := schema.get("properties"):
|
||||
if "$ref" in properties:
|
||||
schema["properties"] = _replace_ref_with_defs(properties)
|
||||
|
|
|
|||
|
|
@ -614,3 +614,52 @@ def test_http_trace_method_path(parsed_http_methods_routes):
|
|||
|
||||
assert trace_route is not None
|
||||
assert trace_route.path == "/resource"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def schema_with_external_reference() -> dict[str, Any]:
|
||||
"""Fixture that returns a schema with external schema references like in issue #926."""
|
||||
return {
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "External Reference API", "version": "1.0.0"},
|
||||
"paths": {
|
||||
"/products": {
|
||||
"post": {
|
||||
"summary": "Create a product",
|
||||
"operationId": "createProduct",
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"obj": {
|
||||
"$ref": "http://cyaninc.com/json-schemas/market-v1/product-constraints"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"responses": {"201": {"description": "Product created"}},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# --- Tests for external schema reference handling --- #
|
||||
|
||||
|
||||
def test_external_reference_raises_clear_error(schema_with_external_reference):
|
||||
"""Test that external schema references raise a clear, helpful error message."""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_openapi_to_http_routes(schema_with_external_reference)
|
||||
|
||||
error_message = str(exc_info.value)
|
||||
assert "External or non-local reference not supported" in error_message
|
||||
assert (
|
||||
"http://cyaninc.com/json-schemas/market-v1/product-constraints" in error_message
|
||||
)
|
||||
assert "FastMCP only supports local schema references" in error_message
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue