BerriAI/litellm · error · HTTPException

access_denied

access_denied

Error message

The key is not allowed to access server {server_id}

What it means

On the REST tool-call route, the server exists and is IP-accessible, but server_id is not in the union of MCP servers the caller's key is allowed to use, so the proxy returns 403 error=access_denied. Allowlists are inclusive: an explicit mcp_servers list on the key or team excludes everything not on it.

Source

Thrown at litellm/proxy/_experimental/mcp_server/rest_endpoints.py:487

                    detail={
                        "error": "ip_filtering",
                        "message": (
                            f"MCP server '{server_id}' is not accessible from your IP address "
                            f"({_rest_client_ip}). This server is restricted to internal "
                            "networks only. To make it externally accessible, set "
                            "'available_on_public_internet: true' in the server configuration."
                        ),
                    },
                )
            if _server is None:
                raise HTTPException(
                    status_code=404,
                    detail={
                        "error": "server_not_found",
                        "message": f"MCP server '{server_id}' was not found",
                    },
                )
            raise HTTPException(
                status_code=403,
                detail={
                    "error": "access_denied",
                    "message": f"The key is not allowed to access server {server_id}",
                },
            )

        # Build allowed_mcp_servers list (only include allowed servers)
        allowed_mcp_servers: Final[list[MCPServer]] = []
        for allowed_server_id in allowed_server_ids_set:
            server = global_mcp_server_manager.get_mcp_server_by_id(allowed_server_id)
            if server is not None:
                allowed_mcp_servers.append(server)

        return allowed_mcp_servers, canonical_server_id

    async def _get_tools_for_single_server(
        server,

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Add the server_id to the key's mcp_servers list (/key/update or dashboard), or add it to the team's allowlist.
  2. Or remove the per-key mcp_servers restriction so the key inherits the general-settings server set.
  3. Verify with the key info endpoint (/key/info) that the server now appears in the key's allowed mcp_servers.
Defensive patterns

Strategy: validation

Validate before calling

async def key_allows_server(key_info: dict, server_id: str) -> bool:
    allowed = key_info.get("mcp_servers")
    return allowed is None or server_id in allowed  # None = inherits general settings

Try / catch

resp = await client.post(f"{proxy}/mcp/tool-call", json=payload, headers=headers)
if resp.status_code == 403 and resp.json().get("detail", {}).get("error") == "access_denied":
    raise PermissionError(f"key not granted {payload['server_id']}; update key/team mcp_servers") from None
resp.raise_for_status()

Prevention

When it happens

Trigger: Key or team carries an mcp_servers allowlist that omits this server_id; a personal key used against a server granted only to a team; a server rolled out after the key's allowlist was frozen.

Common situations: New MCP server added to general settings but keys keep old explicit allowlists; least-privilege keys scoped for one integration then reused for another; team grants vs key grants confusion.

Related errors


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