{"record":{"id":"7ed10fa420f63681","repo":"BerriAI/litellm","slug":"openapi-spec-not-found-at-filepath","errorCode":null,"errorMessage":"OpenAPI spec not found at {filepath}","messagePattern":"OpenAPI spec not found at (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py","lineNumber":157,"sourceCode":"        )\n    except RuntimeError as e:\n        # \"no running event loop\" is fine; other RuntimeErrors we re-raise\n        if \"no running event loop\" not in str(e).lower():\n            raise\n    return asyncio.run(load_openapi_spec_async(filepath))\n\n\nasync def load_openapi_spec_async(filepath: str) -> dict[str, Any]:\n    if filepath.startswith(\"http://\") or filepath.startswith(\"https://\"):\n        client: Final = get_async_httpx_client(llm_provider=httpxSpecialProvider.MCP)\n        r: Final[httpx.Response] = await async_safe_get(client, filepath)\n        r.raise_for_status()\n        return r.json()\n\n    # fallback: local file\n    # Local filesystem path\n    if not os.path.exists(filepath):\n        raise FileNotFoundError(f\"OpenAPI spec not found at {filepath}\")\n    with open(filepath, \"r\", encoding=\"utf-8\") as f:\n        return json.load(f)\n\n\ndef get_base_url(spec: Mapping[str, Any], spec_path: str | None = None) -> str:\n    \"\"\"Extract base URL from OpenAPI spec.\"\"\"\n    # OpenAPI 3.x\n    if \"servers\" in spec and spec[\"servers\"]:\n        server_url: Final[str] = spec[\"servers\"][0][\"url\"]\n\n        # If the server URL is relative (starts with /), derive base from spec_path\n        if server_url.startswith(\"/\") and spec_path:\n            if spec_path.startswith(\"http://\") or spec_path.startswith(\"https://\"):\n                # Extract base URL from spec_path (e.g., https://petstore3.swagger.io/api/v3/openapi.json)\n                # Combine domain with the relative server URL\n                from urllib.parse import urlparse\n\n                parsed: Final = urlparse(spec_path)","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py#L139-L175","documentation":"load_openapi_spec_async raises FileNotFoundError when the configured spec location is not an http(s) URL and no local file exists at that path. It means an MCP server registered with a file-based OpenAPI spec (spec path in mcp_servers config or via the registry) points at a file the proxy process cannot see from its working directory.","triggerScenarios":"Defining an MCP server with a relative spec path like ./specs/api.json (resolved from the proxy's cwd, not the config file's directory); a typo'd path; a file:// URL (not stripped, treated as a literal filename); a container image that does not mount or copy the spec file.","commonSituations":"Docker/k8s deployments that mount the config but not the spec directory; running the proxy from a different cwd during debugging vs. systemd; CI pipelines generating specs to a path the runtime stage doesn't include; specs moved/renamed without updating config.","solutions":["Use an absolute filesystem path for the spec file (or switch to an http(s) URL, which is fetched with the shared MCP httpx client instead).","If containerized, mount the spec into the container and reference the in-container absolute path.","Verify from the proxy's own cwd: python -c \"import os; print(os.path.exists('YOUR_PATH'))\" run in the same directory/service the proxy runs in; also strip any file:// prefix and check for typos."],"exampleFix":"# before (config.yaml)\nmcp_servers:\n  myapi:\n    url: https://api.internal\n    spec_path: ./openapi.json   # relative to proxy cwd -> FileNotFoundError\n\n# after\nmcp_servers:\n  myapi:\n    url: https://api.internal\n    spec_path: /etc/litellm/specs/openapi.json  # absolute, mounted into the container","handlingStrategy":"validation","validationCode":"import os\nfrom urllib.parse import urlparse\n\ndef spec_source_ok(spec_path: str) -> bool:\n    if spec_path.startswith((\"http://\", \"https://\")):\n        return urlparse(spec_path).scheme in (\"http\", \"https\")\n    return not spec_path.startswith(\"file://\") and os.path.isabs(spec_path) and os.path.exists(spec_path)","typeGuard":null,"tryCatchPattern":"from litellm.proxy._experimental.mcp_server.openapi_to_mcp_generator import load_openapi_spec_async\n\ntry:\n    spec = await load_openapi_spec_async(spec_path)\nexcept FileNotFoundError as e:\n    raise RuntimeError(f\"Spec not found; check mount/cwd: {e}\") from e","preventionTips":["Always use absolute spec paths (or http(s) URLs) in config.yaml.","In containers, mount the spec directory and reference the in-container path; add a startup health check that os.path.exists the spec.","Watch proxy startup logs for registration errors instead of discovering the missing spec on the first tool call."],"tags":["mcp","openapi","file-not-found","configuration","docker","relative-path"],"backgroundTag":"config-file-not-found","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}