diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 0d6b3fd9107..4a4123ca1d5 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -100,7 +100,12 @@ if MCP_AVAILABLE: if cred and cred.get("access_token"): return {"Authorization": f"Bearer {cred['access_token']}"} except Exception: - pass + verbose_logger.debug( + "Failed to fetch OAuth credential for user %s / server %s", + user_id, + server_id, + exc_info=True, + ) return None def _create_tool_response_objects(tools, server_mcp_info): diff --git a/ui/litellm-dashboard/src/components/mcp_server_management/MCPToolPermissions.tsx b/ui/litellm-dashboard/src/components/mcp_server_management/MCPToolPermissions.tsx index df3e5958c14..40ee35ea4e9 100644 --- a/ui/litellm-dashboard/src/components/mcp_server_management/MCPToolPermissions.tsx +++ b/ui/litellm-dashboard/src/components/mcp_server_management/MCPToolPermissions.tsx @@ -86,7 +86,10 @@ const MCPToolPermissions: React.FC = ({ }, [servers]); const handleCrudPanelChange = (serverId: string, allowed: string[] | undefined) => { - onChange({ ...toolPermissions, [serverId]: allowed ?? [] }); + // `undefined` from the panel means "allow all" — expand to the full tool list + // rather than collapsing to [] ("allow none"). + const resolved = allowed ?? (serverTools[serverId] || []).map((t) => t.name); + onChange({ ...toolPermissions, [serverId]: resolved }); }; const handleSelectAll = (serverId: string) => { diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx index 8b2bc492c65..01126df956e 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx @@ -456,7 +456,7 @@ const MCPToolConfiguration: React.FC = ({ onAllowedToolsChange(allowed ?? [])} + onChange={(allowed) => onAllowedToolsChange(allowed ?? tools.map((t) => t.name))} /> )} diff --git a/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts b/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts index d5de23608f1..5ae4eb5cf0d 100644 --- a/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts +++ b/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts @@ -15,21 +15,25 @@ export interface MCPToolEntry { * the name alone yields no match. This prevents incidental phrasing in * free-form descriptions (e.g. "removes noise from…") from promoting a safe * tool into a high-risk bucket. + * + * READ is checked before DELETE/UPDATE so that tools like `get_removed_entries` + * or `list_deleted_items` — where the primary verb is a read operation — are + * not silently blocked by the delete-by-default policy for new servers. */ export function classifyToolOp(name: string, description = ""): CrudOp { const nameLower = name.toLowerCase(); + if (READ_RE.test(nameLower)) return "read"; if (DELETE_RE.test(nameLower)) return "delete"; if (UPDATE_RE.test(nameLower)) return "update"; if (CREATE_RE.test(nameLower)) return "create"; - if (READ_RE.test(nameLower)) return "read"; // Only consult description when the name is unrecognised. if (description) { const descLower = description.toLowerCase(); + if (READ_RE.test(descLower)) return "read"; if (DELETE_RE.test(descLower)) return "delete"; if (UPDATE_RE.test(descLower)) return "update"; if (CREATE_RE.test(descLower)) return "create"; - if (READ_RE.test(descLower)) return "read"; } return "unknown";