From 9e767ce77ffbcd211e7e8622c08409b8f69f8e85 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 11 Mar 2026 20:24:41 -0700 Subject: [PATCH] fix: double-add race, conditional bulk query, narrow DELETE_RE, hoist search input --- .../mcp_server/rest_endpoints.py | 13 +++--- .../src/components/chat/MCPAppsPanel.tsx | 6 ++- .../mcp_tools/mcp_tool_configuration.tsx | 43 ++++++++----------- .../src/utils/mcpToolCrudClassification.ts | 2 +- 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 2118804472c..5e4d88355a7 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -369,11 +369,6 @@ if MCP_AVAILABLE: list_tools_result = [] error_message = None - # Bulk-fetch all OAuth credentials for this user in a single DB query - # so per-server calls below can use a dict lookup (O(1)) instead of - # issuing one DB query per server (N+1 pattern). - bulk_oauth_headers = await _get_bulk_user_oauth_headers(user_api_key_dict) - # If server_id is specified, only query that specific server if server_id: # Resolve a server name to its UUID if needed (MCPConnectPicker passes @@ -422,7 +417,8 @@ if MCP_AVAILABLE: server_auth_header = _get_server_auth_header( server, mcp_server_auth_headers, mcp_auth_header ) - user_oauth_extra_headers = bulk_oauth_headers.get(server.server_id) + # Single-server request: targeted lookup is more efficient than a bulk fetch. + user_oauth_extra_headers = await _get_user_oauth_extra_headers(server, user_api_key_dict) try: list_tools_result = await _get_tools_for_single_server( @@ -464,7 +460,10 @@ if MCP_AVAILABLE: }, ) - # Query all servers the user has access to + # Query all servers the user has access to. + # Bulk-fetch OAuth creds once so each per-server call below can + # do an O(1) dict lookup instead of N individual DB queries. + bulk_oauth_headers = await _get_bulk_user_oauth_headers(user_api_key_dict) errors = [] for allowed_server_id in allowed_server_ids: server = global_mcp_server_manager.get_mcp_server_by_id( diff --git a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx index e1ad1d92f84..fb41956caa8 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx @@ -193,7 +193,11 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange message.warning(`Could not load tools for ${serverName}`); return; } - onChange([...selectedServers, serverName]); + // Use the ref so we read the most up-to-date list; guard against duplicates + // that the oauthConnected effect may have already added while we awaited. + if (!selectedServersRef.current.includes(serverName)) { + onChange([...selectedServersRef.current, serverName]); + } } catch { message.warning(`Could not load tools for ${serverName}`); } finally { 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 4cc4c3731aa..164ee8a458a 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 @@ -451,39 +451,30 @@ const MCPToolConfiguration: React.FC = ({ + {/* Search box shared by both views */} + } + value={toolSearchTerm} + onChange={(e) => setToolSearchTerm(e.target.value)} + allowClear + className="rounded-lg" + size="large" + /> + {/* CRUD grouped view */} {viewMode === "crud" && ( - <> - } - value={toolSearchTerm} - onChange={(e) => setToolSearchTerm(e.target.value)} - allowClear - className="rounded-lg" - size="large" - /> - onAllowedToolsChange(allowed)} - /> - + onAllowedToolsChange(allowed)} + /> )} {/* Flat list view */} {viewMode === "flat" && ( <> - } - value={toolSearchTerm} - onChange={(e) => setToolSearchTerm(e.target.value)} - allowClear - className="rounded-lg" - size="large" - /> {filteredTools.length === 0 ? (
diff --git a/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts b/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts index 5ae4eb5cf0d..e2cd53761fe 100644 --- a/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts +++ b/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.ts @@ -1,6 +1,6 @@ export type CrudOp = "read" | "create" | "update" | "delete" | "unknown"; -const DELETE_RE = /\b(delete|remove|destroy|purge|drop|clear|erase|unlink|disconnect)\b/i; +const DELETE_RE = /\b(delete|remove|destroy|purge|drop|erase|unlink)\b/i; const CREATE_RE = /\b(create|add|insert|new|post|submit|register|make|generate|write|upload)\b/i; const UPDATE_RE = /\b(update|edit|modify|change|patch|put|set|rename|move|transform)\b/i; const READ_RE = /\b(get|read|list|fetch|search|find|query|retrieve|show|view|check|describe|info)\b/i;