diff --git a/litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py b/litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py index 179d9997750..546154fa696 100644 --- a/litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py +++ b/litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py @@ -182,7 +182,13 @@ def _parse_openapi_spec(text: str) -> Mapping[str, Any]: except json.JSONDecodeError: import yaml - parsed = yaml.safe_load(text) + class _NoMergeSafeLoader(yaml.SafeLoader): + def flatten_mapping(self, node: Any) -> None: + if any(key_node.tag == "tag:yaml.org,2002:merge" for key_node, _ in node.value): + raise yaml.YAMLError("YAML merge keys are not supported") + super().flatten_mapping(node) + + parsed = yaml.load(text, Loader=_NoMergeSafeLoader) if not isinstance(parsed, dict): raise TypeError("OpenAPI spec must be a JSON or YAML object") diff --git a/tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py b/tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py index 9e1b3186fdc..232ccb27784 100644 --- a/tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py +++ b/tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py @@ -312,6 +312,24 @@ def test_parse_openapi_spec_rejects_non_object_document() -> None: gen._parse_openapi_spec("- not an OpenAPI object\n") +def test_parse_openapi_spec_rejects_yaml_merge_aliases() -> None: + import yaml + + with pytest.raises(yaml.YAMLError, match="YAML merge keys are not supported"): + gen._parse_openapi_spec( + """ +base: &base + openapi: 3.0.0 + info: + title: Base + version: '1.0.0' + paths: {} +merged: + <<: *base +""" + ) + + def _create_mock_client(method: str, response_text: str, status_code: int = 200) -> AsyncMock: """Utility to create a mocked async httpx client for the given method.