{"record":{"id":"883b86ede7b385f8","repo":"BerriAI/litellm","slug":"user-not-allowed-to-call-this-tool","errorCode":null,"errorMessage":"User not allowed to call this tool.","messagePattern":"User not allowed to call this tool\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"litellm/proxy/_experimental/mcp_server/server.py","lineNumber":2734,"sourceCode":"                            \"message\": (\n                                f\"Tool '{name}' belongs to MCP server \"\n                                f\"'{mcp_server.name}' but request specified \"\n                                f\"server_id for '{requested_server.name}'.\"\n                            ),\n                        },\n                    )\n                if mcp_server is None:\n                    mcp_server = requested_server\n                    server_name = requested_server.name\n                    original_tool_name = strip_known_server_prefix(name, requested_server)\n\n        # Only enforce server-level permissions when we can resolve a server\n        if server_name:\n            if not MCPRequestHandler.is_tool_allowed(\n                allowed_mcp_servers=[server.name for server in allowed_mcp_servers],\n                server_name=server_name,\n            ):\n                raise HTTPException(\n                    status_code=403,\n                    detail=\"User not allowed to call this tool.\",\n                )\n\n        standard_logging_mcp_tool_call: Final[StandardLoggingMCPToolCall] = _get_standard_logging_mcp_tool_call(\n            name=original_tool_name,  # Use original name for logging\n            arguments=arguments,\n            server_name=server_name,\n            session_id=_mcp_session_id_from_headers(raw_headers),\n        )\n        litellm_logging_obj: Final[LiteLLMLoggingObj | None] = kwargs.get(\"litellm_logging_obj\", None)\n        if litellm_logging_obj:\n            litellm_logging_obj.model_call_details[\"mcp_tool_call_metadata\"] = standard_logging_mcp_tool_call\n            litellm_logging_obj.model = f\"MCP: {name}\"\n            litellm_logging_obj.model_call_details[\"model\"] = f\"MCP: {name}\"\n        # Resolve the MCP server early so BYOK checks and credential injection\n        # apply to ALL dispatch paths (local tool registry AND managed MCP server).\n        if mcp_server is None:","sourceCodeStart":2716,"sourceCodeEnd":2752,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/_experimental/mcp_server/server.py#L2716-L2752","documentation":"Server-level authorization on the MCP tool-call path: after resolving which server owns the tool, LiteLLM checks that server's name against the caller's allowed MCP servers (derived from key/team/org mcp_servers grants). If the server is not in the allowed list, the call fails with HTTP 403 \"User not allowed to call this tool.\" — enforced only when a server could be resolved.","triggerScenarios":"A key scoped to specific mcp_servers calls a tool from an ungranted server; an unprefixed tool name resolves to a server outside the caller's grants.","commonSituations":"An admin narrows key or team scopes after an incident; a new MCP server is registered but never added to the team's grants; users moved between teams keep calling old tools.","solutions":["Use a key whose grants include the server that owns the tool.","Admin: add the server (or a wildcard) to the key's or team's mcp_servers scope in the admin UI.","Verify visibility first: GET /mcp-rest/tools/list with the same key shows exactly which servers/tools are permitted."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async def tool_visible_to_key(client: httpx.AsyncClient, tool_name: str) -> bool:\n    resp = await client.get(f\"{base}/mcp-rest/tools/list\")\n    tools = resp.json().get(\"tools\", [])\n    return any(t.get(\"name\") == tool_name for t in tools)\n\nif not await tool_visible_to_key(client, name):\n    raise PermissionError(f\"key cannot access tool {name}; request grant first\")","typeGuard":null,"tryCatchPattern":"except httpx.HTTPStatusError as e:\n    if e.response.status_code == 403 and \"not allowed to call this tool\" in e.response.text:\n        # scope problem, not transient: request access or switch key; do not retry\n        raise ToolNotPermitted(name) from e\n    raise","preventionTips":["List tools with the production key before wiring a tool into an agent — visibility equals permission.","When admins change key/team mcp_servers scopes, re-run the tool-visibility check in CI."],"tags":["mcp","authorization","http-403","permissions"],"backgroundTag":"permission-denied-403","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}