BerriAI/litellm · error · HTTPException

Call not allowed to delete MCP server. User is not a proxy a

Error message

Call not allowed to delete MCP server. User is not a proxy admin. route={}

What it means

Returned (403) by the delete MCP server endpoint: deletion is restricted to exactly LitellmUserRoles.PROXY_ADMIN, checked after the prisma client is obtained but before any deletion work. The error text helpfully embeds the route ('DELETE /v1/mcp/server') so callers can identify which management call was denied.

Source

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

        ),
    ):
        """
        Delete MCP Server from db and associated MCP related server entities.

        Parameters:
        - server_id: str - Required. The unique identifier of the mcp server to delete.
        ```
        curl -X "DELETE" --location 'http://localhost:4000/v1/mcp/server/server_id' \
        --header 'Authorization: Bearer your_api_key_here'
        ```
        """
        prisma_client: Final = get_prisma_client_or_throw(
            "Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys"
        )

        # Authz - restrict only admins to delete mcp servers
        if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail={
                    "error": "Call not allowed to delete MCP server. User is not a proxy admin. route={}".format(
                        "DELETE /v1/mcp/server"
                    )
                },
            )

        # try to delete the mcp server
        mcp_server_record_deleted: Final = await delete_mcp_server(prisma_client, server_id)

        if mcp_server_record_deleted is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail={"error": f"MCP Server not found, passed server_id={server_id}"},
            )
        global_mcp_server_manager.remove_server(mcp_server_record_deleted)

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Perform deletions with the proxy master key or a PROXY_ADMIN user's key.
  2. Gate lifecycle automation on the key's role (check /key/info) before issuing DELETE calls.
  3. Route deletion requests from non-admins through an admin-owned workflow.

Example fix

# before
requests.delete(f"{PROXY}/v1/mcp/server/{server_id}", headers={"Authorization": f"Bearer {TEAM_KEY}"})

# after
requests.delete(f"{PROXY}/v1/mcp/server/{server_id}", headers={"Authorization": f"Bearer {os.environ['LITELLM_MASTER_KEY']}"})
Defensive patterns

Strategy: validation

Validate before calling

info = requests.get(f"{PROXY}/key/info", headers=AUTH, params={"key": KEY}).json()
if info.get("key_info", info).get("user_role") != "proxy_admin":
    raise PermissionError("MCP server deletion requires a PROXY_ADMIN key")

Type guard

def can_delete_mcp_server(user_role: str | None) -> bool:
    return user_role == "proxy_admin"

Try / catch

try:
    delete_server(server_id)
except HTTPError as e:
    if e.response.status_code == 403:
        raise PermissionError("re-run DELETE with the master/admin key")
    raise

Prevention

When it happens

Trigger: DELETE /v1/mcp/server/{server_id} with an internal-user, team, or org-admin key; cleanup scripts run with a team virtual key; a non-admin attempting to remove a server they can view but not manage.

Common situations: Tearing down test servers with the wrong key in CI; team admins assuming view access implies delete rights; automation that authenticates with a per-team key for lifecycle operations.

Related errors


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