BerriAI/litellm · error · HTTPException

forbidden

forbidden

Error message

{tool_name} requires mcp_tool_search_enabled on the key

What it means

The virtual tools mcp_tool_search and mcp_tool_call are opt-in per API key: the REST facade checks object_permission.mcp_tool_search_enabled before handling them and returns 403 forbidden with this message when the flag is absent. Concrete per-server tool calls are unaffected.

Source

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

    ) -> Any:
        """Handle the virtual ``mcp_tool_search`` / ``mcp_tool_call`` REST tools (gated on
        ``mcp_tool_search_enabled``). Kept out of ``call_tool_rest_api`` so that endpoint stays a single
        dispatch. An upstream 401 raised by the virtual ``mcp_tool_call`` propagates unhandled to the
        caller's ``except MCPUpstreamAuthError`` relay, the same as the direct call path."""
        from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import (
            MCPRequestHandler,
        )
        from litellm.proxy._experimental.mcp_server.tool_search import (
            MCP_TOOL_SEARCH_TOOL_NAME,
            coerce_top_k,
            handle_mcp_tool_call,
            handle_mcp_tool_search,
        )
        from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
        from litellm.proxy.proxy_server import general_settings, proxy_config, proxy_logging_obj

        if not getattr(getattr(user_api_key_dict, "object_permission", None), "mcp_tool_search_enabled", False):
            raise HTTPException(
                status_code=403,
                detail={"error": "forbidden", "message": f"{tool_name} requires mcp_tool_search_enabled on the key"},
            )
        tool_arguments: Final = data.get("arguments") or {}
        rest_client_ip: Final = IPAddressUtils.get_mcp_client_ip(request)
        (
            virtual_mcp_auth_header,
            virtual_mcp_server_auth_headers,
            virtual_raw_headers,
        ) = _extract_mcp_headers_from_request(request, MCPRequestHandler)
        virtual_oauth2_headers: Final = MCPRequestHandler._get_oauth2_headers_from_headers(request.headers)
        if tool_name == MCP_TOOL_SEARCH_TOOL_NAME:
            return await handle_mcp_tool_search(
                query=tool_arguments.get("query", ""),
                top_k=coerce_top_k(tool_arguments.get("top_k", 5)),
                user_api_key_dict=user_api_key_dict,
                client_ip=rest_client_ip,
                mcp_auth_header=virtual_mcp_auth_header,

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Enable the flag on the key: POST /key/update with object_permission.mcp_tool_search_enabled=true, or toggle it in the dashboard key editor.
  2. Or grant it broadly via the team's object permissions so every team key inherits it.
  3. Or skip the virtual tools and call the concrete tool directly with server_id + tool_name, which requires no flag.

Example fix

# before
curl -X POST $PROXY/mcp/tool-call -H "Authorization: Bearer $KEY" \
  -d '{"tool_name": "mcp_tool_search", "arguments": {"query": "deploy"}}'
# -> 403 {"error":"forbidden","message":"mcp_tool_search requires mcp_tool_search_enabled on the key"}

# after
curl -X POST $PROXY/key/update -H "Authorization: Bearer $ADMIN_KEY" \
  -d '{"key": "sk-...", "object_permission": {"mcp_tool_search_enabled": true}}'
Defensive patterns

Strategy: validation

Validate before calling

VIRTUAL_TOOLS = {"mcp_tool_search", "mcp_tool_call"}

async def key_allows_tool_search(key_info: dict) -> bool:
    perm = key_info.get("object_permission") or key_info.get("object_permissions") or {}
    return bool(perm.get("mcp_tool_search_enabled"))

if tool_name in VIRTUAL_TOOLS and not await key_allows_tool_search(await get_key_info()):
    raise PermissionError("enable mcp_tool_search_enabled on the key first")

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") == "forbidden":
    if "mcp_tool_search_enabled" in resp.text:
        raise PermissionError("tool search not enabled for this key - update the key or call the concrete tool") from None
resp.raise_for_status()

Prevention

When it happens

Trigger: POST to the MCP tool-call REST route with tool_name mcp_tool_search or mcp_tool_call using a key whose object_permissions do not include mcp_tool_search_enabled=true (the attribute may also be entirely absent on older keys).

Common situations: Teams that enabled tool search only on specific keys; keys created before the feature existed; dashboard-generated keys where the tool-search checkbox was never ticked; moving a workload from a trial key to a production key without copying permissions.

Related errors


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