BerriAI/litellm · error · FileNotFoundError

OpenAPI spec not found at {filepath}

Error message

OpenAPI spec not found at {filepath}

What it means

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.

Source

Thrown at litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py:157

        )
    except RuntimeError as e:
        # "no running event loop" is fine; other RuntimeErrors we re-raise
        if "no running event loop" not in str(e).lower():
            raise
    return asyncio.run(load_openapi_spec_async(filepath))


async def load_openapi_spec_async(filepath: str) -> dict[str, Any]:
    if filepath.startswith("http://") or filepath.startswith("https://"):
        client: Final = get_async_httpx_client(llm_provider=httpxSpecialProvider.MCP)
        r: Final[httpx.Response] = await async_safe_get(client, filepath)
        r.raise_for_status()
        return r.json()

    # fallback: local file
    # Local filesystem path
    if not os.path.exists(filepath):
        raise FileNotFoundError(f"OpenAPI spec not found at {filepath}")
    with open(filepath, "r", encoding="utf-8") as f:
        return json.load(f)


def get_base_url(spec: Mapping[str, Any], spec_path: str | None = None) -> str:
    """Extract base URL from OpenAPI spec."""
    # OpenAPI 3.x
    if "servers" in spec and spec["servers"]:
        server_url: Final[str] = spec["servers"][0]["url"]

        # If the server URL is relative (starts with /), derive base from spec_path
        if server_url.startswith("/") and spec_path:
            if spec_path.startswith("http://") or spec_path.startswith("https://"):
                # Extract base URL from spec_path (e.g., https://petstore3.swagger.io/api/v3/openapi.json)
                # Combine domain with the relative server URL
                from urllib.parse import urlparse

                parsed: Final = urlparse(spec_path)

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. 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).
  2. If containerized, mount the spec into the container and reference the in-container absolute path.
  3. 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.

Example fix

# before (config.yaml)
mcp_servers:
  myapi:
    url: https://api.internal
    spec_path: ./openapi.json   # relative to proxy cwd -> FileNotFoundError

# after
mcp_servers:
  myapi:
    url: https://api.internal
    spec_path: /etc/litellm/specs/openapi.json  # absolute, mounted into the container
Defensive patterns

Strategy: validation

Validate before calling

import os
from urllib.parse import urlparse

def spec_source_ok(spec_path: str) -> bool:
    if spec_path.startswith(("http://", "https://")):
        return urlparse(spec_path).scheme in ("http", "https")
    return not spec_path.startswith("file://") and os.path.isabs(spec_path) and os.path.exists(spec_path)

Try / catch

from litellm.proxy._experimental.mcp_server.openapi_to_mcp_generator import load_openapi_spec_async

try:
    spec = await load_openapi_spec_async(spec_path)
except FileNotFoundError as e:
    raise RuntimeError(f"Spec not found; check mount/cwd: {e}") from e

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/7ed10fa420f63681. Report an issue: GitHub.