From 35f5d6cc1c4fbda1efe5a688e0264d9f1ccf95de Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 7 Mar 2026 06:58:29 +0000 Subject: [PATCH] Polish MCP Servers UI for enterprise-grade look and feel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 10 improvements to the MCP Servers table and related components: 1. Remove debug console.logs from mcp_servers.tsx 2. Fix health status icons: distinct ✓/✗/? per state instead of identical dots 3. Health status badges: proper pill styling with rounded-full and borders 4. Health loading state: subtle pulsing dot instead of raw SVG spinner 5. Transport column: color-coded badges (HTTP=blue, SSE=purple, STDIO=amber, OPENAPI=teal) 6. Auth type column: color-coded badges (oauth2=indigo, bearer_token=sky, api_key=emerald) 7. Server ID chip: rounded corners, border, and transition effect 8. Filter bar: lighter border, cleaner labels, vertical divider between filters 9. Network Access: pill badges with colored dots (Public/Internal) 10. Date columns: shorter headers, dash for missing values, tooltip with full datetime Also: - Improved delete modal: cleaner layout, neutral background instead of red - Access Groups column: shows first group with +N count instead of truncated text - Empty state message includes CTA guidance - Updated test to match renamed filter label Co-authored-by: Ishaan Jaff --- .../mcp_tools/mcp_server_columns.tsx | 103 ++++++++++++------ .../components/mcp_tools/mcp_servers.test.tsx | 4 +- .../src/components/mcp_tools/mcp_servers.tsx | 93 ++++++---------- 3 files changed, 104 insertions(+), 96 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_columns.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_columns.tsx index 0db1f4c14cd..7a65422ade0 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_columns.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_columns.tsx @@ -21,7 +21,7 @@ export const mcpServerColumns = ( cell: ({ row }) => ( @@ -56,16 +56,39 @@ export const mcpServerColumns = ( cell: ({ row }) => { const transport = row.original.transport || "http"; const specPath = row.original.spec_path; - // If server has spec_path, display as "OPENAPI" instead of the raw transport type const displayTransport = specPath && transport !== "stdio" ? "OPENAPI" : transport; - return {displayTransport.toUpperCase()}; + const label = displayTransport.toUpperCase(); + const colorClass = + label === "HTTP" ? "bg-blue-50 text-blue-700 border-blue-200" : + label === "SSE" ? "bg-purple-50 text-purple-700 border-purple-200" : + label === "STDIO" ? "bg-amber-50 text-amber-700 border-amber-200" : + label === "OPENAPI" ? "bg-teal-50 text-teal-700 border-teal-200" : + "bg-gray-50 text-gray-700 border-gray-200"; + return ( + + {label} + + ); }, }, { accessorKey: "auth_type", header: "Auth Type", enableSorting: true, - cell: ({ getValue }) => {(getValue() as string) || "none"}, + cell: ({ getValue }) => { + const authType = (getValue() as string) || "none"; + const colorClass = + authType === "oauth2" ? "bg-indigo-50 text-indigo-700 border-indigo-200" : + authType === "bearer_token" ? "bg-sky-50 text-sky-700 border-sky-200" : + authType === "api_key" ? "bg-emerald-50 text-emerald-700 border-emerald-200" : + authType === "basic" ? "bg-orange-50 text-orange-700 border-orange-200" : + "bg-gray-50 text-gray-500 border-gray-200"; + return ( + + {authType} + + ); + }, }, { id: "health_status", @@ -76,38 +99,34 @@ export const mcpServerColumns = ( const lastCheck = server.last_health_check; const error = server.health_check_error; - // Show loading spinner if health check is in progress if (isLoadingHealth) { return ( -
- - - - - Loading... -
+ + + Checking + ); } const getStatusColor = (status: string) => { switch (status) { case "healthy": - return "text-green-500 bg-green-50 hover:bg-green-100"; + return "text-green-700 bg-green-50 border border-green-200"; case "unhealthy": - return "text-red-500 bg-red-50 hover:bg-red-100"; + return "text-red-700 bg-red-50 border border-red-200"; default: - return "text-gray-500 bg-gray-50 hover:bg-gray-100"; + return "text-gray-600 bg-gray-50 border border-gray-200"; } }; const getStatusIcon = (status: string) => { switch (status) { case "healthy": - return "●"; + return "✓"; case "unhealthy": - return "●"; + return "✗"; default: - return "●"; + return "?"; } }; @@ -127,12 +146,12 @@ export const mcpServerColumns = ( return ( - + ); }, @@ -143,19 +162,23 @@ export const mcpServerColumns = ( cell: ({ row }) => { const groups = row.original.mcp_access_groups; if (Array.isArray(groups) && groups.length > 0) { - // If string array if (typeof groups[0] === "string") { const joined = groups.join(", "); return ( - - {joined.length > 30 ? `${joined.slice(0, 30)}...` : joined} - +
+ + {groups[0]} + + {groups.length > 1 && ( + +{groups.length - 1} + )} +
); } } - return None; + return —; }, }, { @@ -164,33 +187,47 @@ export const mcpServerColumns = ( cell: ({ row }) => { const isPublic = row.original.available_on_public_internet; return isPublic ? ( - All networks + + + Public + ) : ( - Internal only + + + Internal + ); }, }, { - header: "Created At", + header: "Created", accessorKey: "created_at", enableSorting: true, sortingFn: "datetime", cell: ({ row }) => { const server = row.original; + if (!server.created_at) return —; + const date = new Date(server.created_at); return ( - {server.created_at ? new Date(server.created_at).toLocaleDateString() : "-"} + + {date.toLocaleDateString()} + ); }, }, { - header: "Updated At", + header: "Updated", accessorKey: "updated_at", enableSorting: true, sortingFn: "datetime", cell: ({ row }) => { const server = row.original; + if (!server.updated_at) return —; + const date = new Date(server.updated_at); return ( - {server.updated_at ? new Date(server.updated_at).toLocaleDateString() : "-"} + + {date.toLocaleDateString()} + ); }, }, diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.test.tsx index 173b623a2ff..8385fc7ac70 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.test.tsx @@ -305,8 +305,8 @@ describe("MCPServers", () => { expect(screen.getByText("Team B Server")).toBeInTheDocument(); expect(screen.getByText("Team A Server 2")).toBeInTheDocument(); - // Find the team select dropdown by looking for the "Current Team:" label - const teamLabel = screen.getByText("Current Team:"); + // Find the team select dropdown by looking for the "Team" label + const teamLabel = screen.getByText("Team"); const teamSelectContainer = teamLabel.closest("div")?.querySelector(".ant-select"); expect(teamSelectContainer).toBeTruthy(); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx index f48649d6653..f985d3d5fb5 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx @@ -48,17 +48,6 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) }); }, [mcpServers, healthStatuses]); - // Log allowed_tools from fetched servers - React.useEffect(() => { - if (mcpServers) { - console.log("MCP Servers fetched:", mcpServers); - mcpServers.forEach((server) => { - console.log(`Server: ${server.server_name || server.server_id}`); - console.log(` allowed_tools:`, server.allowed_tools); - }); - } - }, [mcpServers]); - // state const [serverIdToDelete, setServerToDelete] = useState(null); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -257,34 +246,28 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) confirmLoading={isDeletingServer} >
- Are you sure you want to delete this MCP Server? This action cannot be undone. + This action is permanent and cannot be undone. All associated configurations will be removed. {serverToDelete && ( -
- - Server Information - - +
+ {serverToDelete.server_name && ( - Server Name}> - {serverToDelete.server_name} + Name}> + {serverToDelete.server_name} )} - {serverToDelete.alias && ( - Alias}> - {serverToDelete.alias} - - )} - Server ID}> - + ID}> + {serverToDelete.server_id} - URL}> - - {serverToDelete.url} - - + {serverToDelete.url && ( + URL}> + + {serverToDelete.url} + + + )}
)} @@ -353,50 +336,38 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID })
-
-
- Current Team: - {uniqueTeams.map((team) => ( ))} - - Access Group: +
+
+
+ + Access Group - + - {uniqueMcpAccessGroups.map((group) => ( ))} @@ -411,8 +382,8 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) renderSubComponent={() =>
} getRowCanExpand={() => false} isLoading={isLoadingServers} - noDataMessage="No MCP servers configured" - loadingMessage="🚅 Loading MCP servers..." + noDataMessage="No MCP servers configured. Click '+ Add New MCP Server' to get started." + loadingMessage="Loading MCP servers..." enableSorting={true} />