From af450061112984fc277feeb3f85a481a48cb477a Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 5 Mar 2026 18:55:09 -0800 Subject: [PATCH] fix(mcp): resolve \$ref params in OpenAPI preview endpoint (test/tools/list) The _preview_openapi_tools function (called by the UI add-server form to show connection status and available tools) had the same bug as _register_openapi_tools: it accessed param["name"] directly without resolving \$ref parameters or merging path-level parameters from the path item. This caused "Failed to load OpenAPI spec: 'name'" for any spec that uses component-level parameter references (e.g. GitHub's official REST API spec). Apply the same fix: resolve \$ref against components/parameters and merge path-level params (with operation-level taking priority) before building schemas. --- .../mcp_server/rest_endpoints.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 16f8f835430..e3a140d21e0 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -671,16 +671,35 @@ if MCP_AVAILABLE: try: spec = await load_openapi_spec_async(spec_path) paths = spec.get("paths", {}) + components = spec.get("components", {}) tools: List[dict] = [] for path, path_item in paths.items(): for method in ("get", "post", "put", "patch", "delete"): operation = path_item.get(method) if operation is None: continue + + # Resolve $ref params and merge path-level params (same logic as + # _register_openapi_tools) so large specs like GitHub's work correctly. + def _resolve_ref(p: dict) -> dict: + ref = p.get("$ref", "") + if ref.startswith("#/components/parameters/"): + param_name = ref.split("/")[-1] + return components.get("parameters", {}).get(param_name, p) + return p + + path_level = [_resolve_ref(p) for p in path_item.get("parameters", [])] + op_level = [_resolve_ref(p) for p in operation.get("parameters", [])] + op_keys = {(p.get("name"), p.get("in")) for p in op_level} + merged = [p for p in path_level if (p.get("name"), p.get("in")) not in op_keys] + op_level + + resolved_op = dict(operation) + resolved_op["parameters"] = merged + op_id = operation.get("operationId", f"{method}_{path}") summary = operation.get("summary", "") description = operation.get("description", summary) - input_schema = build_input_schema(operation) + input_schema = build_input_schema(resolved_op) tools.append( { "name": op_id,