From 6b42172e997a810cdd416010c4dc0a4b4e0b23f2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 10:57:48 +0000 Subject: [PATCH] feat(ui): MCPServerPermissions to shadcn (expandable list) - @tremor Text/Badge + @heroicons Server/Chevron + antd Tooltip \u2192 shadcn Badge/Tooltip + lucide Server/ChevronDown/ChevronRight. - Preserves the categorical blue/emerald/purple palette for servers/access-groups/toolsets respectively. File added to .eslintrc.json overrides. - Expand/collapse logic for tool lists under each server/toolset retained; chevron direction tied to expansion state. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 1 + .../permissions/MCPServerPermissions.tsx | 253 ++++++++++-------- 2 files changed, 144 insertions(+), 110 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index a7526b0748d..738372ab4a2 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -52,6 +52,7 @@ "src/components/agents/agent_card.tsx", "src/components/permissions/VectorStorePermissions.tsx", "src/components/permissions/AgentPermissions.tsx", + "src/components/permissions/MCPServerPermissions.tsx", "src/components/DeletedKeysPage/DeletedKeysPage.tsx", "src/components/DeletedTeamsPage/DeletedTeamsPage.tsx" ], diff --git a/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx b/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx index 649e8efdf4a..6353899dc07 100644 --- a/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx +++ b/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx @@ -1,7 +1,12 @@ import React, { useState, useEffect } from "react"; -import { Text, Badge } from "@tremor/react"; -import { ServerIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline"; -import { Tooltip } from "antd"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { ChevronDown, ChevronRight, Server } from "lucide-react"; import { fetchMCPServers, fetchMCPToolsets } from "../networking"; import { MCPServer, MCPToolset } from "../mcp_tools/types"; @@ -18,38 +23,29 @@ export function MCPServerPermissions({ mcpAccessGroups = [], mcpToolPermissions = {}, mcpToolsets = [], - accessToken + accessToken, }: MCPServerPermissionsProps) { const [mcpServerDetails, setMCPServerDetails] = useState([]); const [toolsetDetails, setToolsetDetails] = useState([]); - const [expandedServers, setExpandedServers] = useState>(new Set()); - const [expandedToolsets, setExpandedToolsets] = useState>(new Set()); + const [expandedServers, setExpandedServers] = useState>( + new Set(), + ); + const [expandedToolsets, setExpandedToolsets] = useState>( + new Set(), + ); - const toggleServerExpansion = (serverId: string) => { - setExpandedServers((prev) => { - const newSet = new Set(prev); - if (newSet.has(serverId)) { - newSet.delete(serverId); - } else { - newSet.add(serverId); - } - return newSet; + const toggle = ( + setter: React.Dispatch>>, + id: string, + ) => { + setter((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; }); }; - const toggleToolsetExpansion = (toolsetId: string) => { - setExpandedToolsets((prev) => { - const newSet = new Set(prev); - if (newSet.has(toolsetId)) { - newSet.delete(toolsetId); - } else { - newSet.add(toolsetId); - } - return newSet; - }); - }; - - // Fetch MCP server details when component mounts useEffect(() => { const fetchMCPServerDetails = async () => { if (accessToken && mcpServers.length > 0) { @@ -68,7 +64,6 @@ export function MCPServerPermissions({ fetchMCPServerDetails(); }, [accessToken, mcpServers.length]); - // Fetch toolset details useEffect(() => { const fetchToolsets = async () => { if (accessToken && mcpToolsets.length > 0) { @@ -86,17 +81,20 @@ export function MCPServerPermissions({ fetchToolsets(); }, [accessToken, mcpToolsets.length]); - // Function to get display name for MCP server const getMCPServerDisplayName = (serverId: string) => { - const serverDetail = mcpServerDetails.find((server) => server.server_id === serverId); + const serverDetail = mcpServerDetails.find( + (s) => s.server_id === serverId, + ); if (serverDetail) { - const truncatedId = serverId.length > 7 ? `${serverId.slice(0, 3)}...${serverId.slice(-4)}` : serverId; + const truncatedId = + serverId.length > 7 + ? `${serverId.slice(0, 3)}...${serverId.slice(-4)}` + : serverId; return `${serverDetail.alias} (${truncatedId})`; } return serverId; }; - // Merge servers and access groups into one list const mergedItems = [ ...mcpServers.map((server) => ({ type: "server", value: server })), ...mcpAccessGroups.map((group) => ({ type: "accessGroup", value: group })), @@ -106,9 +104,9 @@ export function MCPServerPermissions({ return (
- - MCP Servers - + + MCP Servers + {totalCount}
@@ -116,33 +114,51 @@ export function MCPServerPermissions({ {totalCount > 0 ? (
{mergedItems.map((item, index) => { - const toolsForServer = item.type === "server" ? mcpToolPermissions[item.value] : undefined; - const hasToolRestrictions = toolsForServer && toolsForServer.length > 0; + const toolsForServer = + item.type === "server" + ? mcpToolPermissions[item.value] + : undefined; + const hasToolRestrictions = + toolsForServer && toolsForServer.length > 0; const isExpanded = expandedServers.has(item.value); return (
hasToolRestrictions && toggleServerExpansion(item.value)} - className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-gray-200 transition-all ${ + onClick={() => + hasToolRestrictions && + toggle(setExpandedServers, item.value) + } + className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-border transition-all ${ hasToolRestrictions - ? 'cursor-pointer hover:bg-gray-50 hover:border-gray-300' - : 'bg-white' + ? "cursor-pointer hover:bg-muted hover:border-foreground/30" + : "bg-background" }`} >
{item.type === "server" ? ( - -
- - {getMCPServerDisplayName(item.value)} -
-
+ + + +
+ + + {getMCPServerDisplayName(item.value)} + +
+
+ + Full ID: {item.value} + +
+
) : (
- - {item.value} - + + + {item.value} + + Group
@@ -151,25 +167,28 @@ export function MCPServerPermissions({ {hasToolRestrictions && (
- {toolsForServer.length} - {toolsForServer.length === 1 ? "tool" : "tools"} + + {toolsForServer.length} + + + {toolsForServer.length === 1 ? "tool" : "tools"} + {isExpanded ? ( - + ) : ( - + )}
)}
- {/* Show tool permissions if expanded */} {hasToolRestrictions && isExpanded && ( -
+
{toolsForServer.map((tool, toolIndex) => ( {tool} @@ -181,65 +200,79 @@ export function MCPServerPermissions({ ); })} - {/* Toolsets section */} - {mcpToolsets.length > 0 && mcpToolsets.map((toolsetId, index) => { - const detail = toolsetDetails.find((t) => t.toolset_id === toolsetId); - const isExpanded = expandedToolsets.has(toolsetId); - const toolCount = detail?.tools.length ?? 0; + {mcpToolsets.length > 0 && + mcpToolsets.map((toolsetId, index) => { + const detail = toolsetDetails.find( + (t) => t.toolset_id === toolsetId, + ); + const isExpanded = expandedToolsets.has(toolsetId); + const toolCount = detail?.tools.length ?? 0; - return ( -
-
toolCount > 0 && toggleToolsetExpansion(toolsetId)} - className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-purple-200 transition-all ${ - toolCount > 0 ? 'cursor-pointer hover:bg-purple-50 hover:border-purple-300' : 'bg-white' - }`} - > -
- - - {detail?.toolset_name ?? toolsetId} - - - Toolset - + return ( +
+
+ toolCount > 0 && toggle(setExpandedToolsets, toolsetId) + } + className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-purple-200 dark:border-purple-900 transition-all ${ + toolCount > 0 + ? "cursor-pointer hover:bg-purple-50 dark:hover:bg-purple-950/30 hover:border-purple-300 dark:hover:border-purple-800" + : "bg-background" + }`} + > +
+ + + {detail?.toolset_name ?? toolsetId} + + + Toolset + +
+ {toolCount > 0 && ( +
+ + {toolCount} + + + {toolCount === 1 ? "tool" : "tools"} + + {isExpanded ? ( + + ) : ( + + )} +
+ )}
- {toolCount > 0 && ( -
- {toolCount} - {toolCount === 1 ? "tool" : "tools"} - {isExpanded ? ( - - ) : ( - - )} + + {toolCount > 0 && isExpanded && detail && ( +
+
+ {detail.tools.map((tool, toolIndex) => ( + + + {tool.server_id.slice(0, 6)}… + + {tool.tool_name} + + ))} +
)}
- - {toolCount > 0 && isExpanded && detail && ( -
-
- {detail.tools.map((tool, toolIndex) => ( - - {tool.server_id.slice(0, 6)}… - {tool.tool_name} - - ))} -
-
- )} -
- ); - })} + ); + })}
) : ( -
- - No MCP servers, access groups, or toolsets configured +
+ + + No MCP servers, access groups, or toolsets configured +
)}