From ede4f91ffd590490aa5c00475e777f6e3d4ffcef Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 9 Mar 2026 19:53:35 -0700 Subject: [PATCH] fix(ui): pin suggested tools at top of tool list, fix TDZ crash, add per-section enable/disable --- .../mcp_tools/create_mcp_server.tsx | 1 + .../mcp_tools/mcp_tool_configuration.tsx | 208 ++++++++++-------- 2 files changed, 119 insertions(+), 90 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx index 501f4f7fd7c..85d3e4143f5 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx @@ -498,6 +498,7 @@ const CreateMCPServer: React.FC = ({ width={1000} onCancel={handleCancel} footer={null} + forceRender className="top-8" styles={{ body: { padding: "24px" }, 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 60aeb2a6b30..b072a4ae7dd 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 @@ -1,7 +1,7 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Card, Title, Text } from "@tremor/react"; import { ToolOutlined, CheckCircleOutlined, SearchOutlined, EditOutlined } from "@ant-design/icons"; -import { Badge, Spin, Checkbox, Input, Tooltip } from "antd"; +import { Badge, Spin, Checkbox, Input } from "antd"; import { useTestMCPConnection } from "../../hooks/useTestMCPConnection"; interface KeyTool { @@ -49,6 +49,39 @@ const MCPToolConfiguration: React.FC = ({ enabled: true, }); + // Fuzzy-match curated key tool names against actual loaded tool names + const suggestedTools = useMemo(() => { + if (!keyTools || keyTools.length === 0 || tools.length === 0) return []; + const usedNames = new Set(); + const result: typeof tools = []; + for (const keyTool of keyTools) { + const keywords = keyTool.name.split("_").map((k) => k.toLowerCase()).filter((k) => k.length > 1); + const normalize = (s: string) => s.toLowerCase().replace(/[-_/]/g, " "); + let match = tools.find((t) => { + if (usedNames.has(t.name)) return false; + const n = normalize(t.name); + return keywords.every((kw) => n.includes(kw)); + }); + if (!match) { + const mainKw = keywords.find((k) => k.length > 3) ?? keywords[keywords.length - 1]; + match = tools.find((t) => { + if (usedNames.has(t.name)) return false; + return normalize(t.name).includes(mainKw); + }); + } + if (match) { + result.push(match); + usedNames.add(match.name); + } + } + return result; + }, [keyTools, tools]); + + const suggestedToolNames = useMemo( + () => new Set(suggestedTools.map((t) => t.name)), + [suggestedTools] + ); + // Filter tools based on search term const filteredTools = tools.filter((tool) => { const searchLower = toolSearchTerm.toLowerCase(); @@ -76,6 +109,9 @@ const MCPToolConfiguration: React.FC = ({ // Edit mode: pre-select tools that match existing allowed tools const validExistingTools = existingAllowedTools.filter((toolName) => availableToolNames.includes(toolName)); onAllowedToolsChange(validExistingTools); + } else if (suggestedTools.length > 0) { + // OpenAPI preset: only enable suggested tools by default + onAllowedToolsChange(suggestedTools.map((t) => t.name).filter((name) => availableToolNames.includes(name))); } else { // Create mode: auto-select all tools onAllowedToolsChange(availableToolNames); @@ -94,7 +130,7 @@ const MCPToolConfiguration: React.FC = ({ // Update ref to track current tools previousToolsRef.current = tools; - }, [tools, allowedTools, existingAllowedTools, onAllowedToolsChange]); + }, [tools, allowedTools, existingAllowedTools, onAllowedToolsChange, suggestedTools]); const handleToolToggle = (toolName: string) => { if (allowedTools.includes(toolName)) { @@ -185,11 +221,6 @@ const MCPToolConfiguration: React.FC = ({ )} - {/* Key tools preview — shown while loading or when spec hasn't loaded yet */} - {keyTools && keyTools.length > 0 && (isLoadingTools || (!isLoadingTools && !toolsError && tools.length === 0)) && ( - - )} - {/* Error state */} {toolsError && !isLoadingTools && (
@@ -223,30 +254,12 @@ const MCPToolConfiguration: React.FC = ({ {/* Tools loaded successfully */} {!isLoadingTools && !toolsError && tools.length > 0 && (
-
-
- - - {allowedTools.length} of {tools.length} {tools.length === 1 ? "tool" : "tools"} enabled for user - access - -
-
- - -
+
+ + + {allowedTools.length} of {tools.length} {tools.length === 1 ? "tool" : "tools"} enabled for user + access +
{/* Search bar */} @@ -261,30 +274,32 @@ const MCPToolConfiguration: React.FC = ({ /> {/* Tool list with checkboxes */} -
- {filteredTools.length === 0 ? ( -
- - No tools found matching "{toolSearchTerm}" -
- ) : ( - filteredTools.map((tool, index) => { - const isEnabled = allowedTools.includes(tool.name); - const isEditExpanded = expandedTools.has(tool.name); - return ( + {(() => { + const pinnedFiltered = filteredTools.filter((t) => suggestedToolNames.has(t.name)); + const restFiltered = filteredTools.filter((t) => !suggestedToolNames.has(t.name)); + + if (filteredTools.length === 0) { + return ( +
+ + No tools found matching "{toolSearchTerm}" +
+ ); + } + + const renderRow = (tool: typeof filteredTools[0], index: number) => { + const isEnabled = allowedTools.includes(tool.name); + const isEditExpanded = expandedTools.has(tool.name); + return (
- {/* Main tool row */} -
handleToolToggle(tool.name)} - > +
handleToolToggle(tool.name)}>
handleToolToggle(tool.name)} />
@@ -314,7 +329,6 @@ const MCPToolConfiguration: React.FC = ({ {isEnabled ? "✓ Users can call this tool" : "✗ Users cannot call this tool"}
- {/* Edit toggle button */}
- - {/* Inline edit section */} {isEditExpanded && (
e.stopPropagation()} >
- - Display Name - + Display Name = ({
- - Description - + Description = ({
)}
- ); - }) - )} -
+ ); + }; + + const handleEnableSuggested = () => { + const suggestedNames = pinnedFiltered.map((t) => t.name); + const others = allowedTools.filter((n) => !suggestedToolNames.has(n)); + onAllowedToolsChange([...others, ...suggestedNames]); + }; + const handleDisableSuggested = () => { + onAllowedToolsChange(allowedTools.filter((n) => !suggestedToolNames.has(n))); + }; + const handleEnableRest = () => { + const restNames = restFiltered.map((t) => t.name); + const current = new Set(allowedTools); + onAllowedToolsChange([...allowedTools, ...restNames.filter((n) => !current.has(n))]); + }; + const handleDisableRest = () => { + const restNames = new Set(restFiltered.map((t) => t.name)); + onAllowedToolsChange(allowedTools.filter((n) => !restNames.has(n))); + }; + + return ( +
+ {pinnedFiltered.length > 0 && ( + <> +
+

+ Suggested tools +

+
+ + +
+
+ {pinnedFiltered.map((tool, i) => renderRow(tool, i))} + {restFiltered.length > 0 && ( +
+

+ All tools +

+
+ + +
+
+ )} + + )} + {restFiltered.map((tool, i) => renderRow(tool, i))} +
+ ); + })()}
)}
@@ -377,33 +433,5 @@ const MCPToolConfiguration: React.FC = ({ ); }; -const KeyToolsPreview: React.FC<{ tools: KeyTool[] }> = ({ tools }) => { - const [expanded, setExpanded] = useState(false); - const shown = expanded ? tools : tools.slice(0, 4); - - return ( -
-

Key tools from this API (preview — all tools load from the spec)

-
- {shown.map((tool) => ( - - - {tool.name} - - - ))} -
- {tools.length > 4 && ( - - )} -
- ); -}; export default MCPToolConfiguration;