BerriAI/litellm · error · HTTPException

MCP Server with id {server_id} not found

Error message

MCP Server with id {server_id} not found

What it means

Returned (404) by the single MCP server fetch endpoint when the server cannot be resolved through its whole lookup chain: the database record, then the in-memory registry by server_id (dropped if _is_server_accessible_from_ip fails), and finally the registry by server_name/alias. It means the caller-supplied identifier matched nothing the proxy can serve for this request's from_db mode and client IP.

Source

Thrown at litellm/proxy/management_endpoints/mcp_management_endpoints.py:1459

        if mcp_server is None:
            # Fallback: check registry (config-based servers) - list endpoint uses get_registry()
            from litellm.proxy.auth.ip_address_utils import IPAddressUtils

            client_ip: Final = IPAddressUtils.get_mcp_client_ip(request)
            registry_server = global_mcp_server_manager.get_mcp_server_by_id(server_id)
            if registry_server is not None and not global_mcp_server_manager._is_server_accessible_from_ip(
                registry_server, client_ip
            ):
                registry_server = None
            if registry_server is None:
                # Try lookup by server_name or alias (client may use display name in URL)
                registry_server = global_mcp_server_manager.get_mcp_server_by_name(server_id, client_ip=client_ip)
            if registry_server is not None:
                mcp_server = global_mcp_server_manager._build_mcp_server_table(registry_server)

        if mcp_server is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail={"error": f"MCP Server with id {server_id} not found"},
            )

        # Implement authz restriction from requested user
        is_admin_view: Final = _user_has_admin_view(user_api_key_dict)
        is_restricted_virtual_key: Final = _is_restricted_virtual_key_request(user_api_key_dict)

        if not is_admin_view:
            # Perform authz check BEFORE any health check (avoid side-effects for
            # unauthorized callers).
            if from_db:
                mcp_server_records: Final = await get_all_mcp_servers_for_user(prisma_client, user_api_key_dict)
                exists = does_mcp_server_exist(mcp_server_records, server_id)
            else:
                # Registry/config server: use same access logic as list endpoint
                allowed_server_ids: Final = await global_mcp_server_manager.get_allowed_mcp_servers(user_api_key_dict)
                exists = mcp_server.server_id in allowed_server_ids

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. List all servers visible to your key (GET the mcp server list endpoint) and copy the exact server_id.
  2. If you are using a display name or alias, retry with the canonical server_id (or vice versa if you know it is a registry/config server).
  3. For IP-restricted servers, verify the allowed_ips configuration and that the proxy sees the correct client IP (X-Forwarded-For handling) — an IP mismatch suppresses the server and reports 404.
  4. Confirm the server still exists (it may have been deleted or rejected).

Example fix

# before: guessing the id
requests.get(f"{PROXY}/v1/mcp/server/{server_id}", headers=AUTH).raise_for_status()

# after: resolve the real id from the list endpoint first
servers = requests.get(f"{PROXY}/v1/mcp/server", headers=AUTH).json()
resolved = next(s for s in servers["servers"] if s["server_id"] == server_id or s.get("server_name") == server_id)
requests.get(f"{PROXY}/v1/mcp/server/{resolved['server_id']}", headers=AUTH).raise_for_status()
Defensive patterns

Strategy: validation

Validate before calling

servers = requests.get(f"{PROXY}/v1/mcp/server", headers=AUTH).json()["servers"]
known = {s["server_id"] for s in servers} | {s.get("server_name", "") for s in servers}
assert server_id in known, f"unknown server id: {server_id}"

Type guard

def is_known_server_id(server_id: str, servers: list[dict]) -> bool:
    return server_id in {s["server_id"] for s in servers} | {s.get("server_name") for s in servers}

Try / catch

try:
    fetch(server_id)
except HTTPError as e:
    if e.response.status_code == 404:
        # re-resolve id from list endpoint, check IP allowlist config, then retry once
        raise
    raise

Prevention

When it happens

Trigger: GET /v1/mcp/server/{server_id} with a typo'd or stale id; passing a display name/alias where the from_db branch expects the canonical server_id; the server was deleted after the caller cached its id; the registry entry exists but the request's client IP is not allowed for that server, so it is nulled out and the request falls through to 404.

Common situations: Mixing up server_id with server_name/alias between config-defined and DB-defined servers; servers deleted by another admin while a script holds old ids; IP-restricted MCP servers (allowed_ips) accessed through a proxy that mangles the client IP, turning what should be a 403 into a 404.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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