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
- 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.
- Or grant it broadly via the team's object permissions so every team key inherits it.
- 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
- Encode the flag requirement in provisioning: any key intended for tool search gets mcp_tool_search_enabled at creation.
- Fail fast in your client by checking key metadata before issuing virtual-tool calls.
- Prefer concrete server_id + tool_name calls when search is not needed - no flag required.
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
- access_denied
- User not allowed to call this tool.
- Access denied to prompt version '{prompt_version_id}'. Check
- Access denied to file '{file_path}'. Check your BitBucket pe
- User does not have permission to test MCP server connections
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/3ffe53274a0b8432.
Report an issue: GitHub.