import ClientForm from "@/app/workspace/mcp-registry/views/mcpClientForm"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alertDialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { useToast } from "@/hooks/use-toast"; import { MCP_STATUS_COLORS } from "@/lib/constants/config"; import { getErrorMessage, useDeleteMCPClientMutation, useReconnectMCPClientMutation } from "@/lib/store"; import { MCPClient } from "@/lib/types/mcp"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { ChevronLeft, ChevronRight, Loader2, Plus, RefreshCcw, Search, Trash2 } from "lucide-react"; import { useState } from "react"; import { MCPServersEmptyState } from "./mcpServersEmptyState"; import MCPClientSheet from "./mcpClientSheet"; interface MCPClientsTableProps { mcpClients: MCPClient[]; totalCount: number; refetch?: () => void; search: string; debouncedSearch: string; onSearchChange: (value: string) => void; offset: number; limit: number; onOffsetChange: (offset: number) => void; } export default function MCPClientsTable({ mcpClients, totalCount, refetch, search, debouncedSearch, onSearchChange, offset, limit, onOffsetChange, }: MCPClientsTableProps) { const [formOpen, setFormOpen] = useState(false); const hasCreateMCPClientAccess = useRbac(RbacResource.MCPGateway, RbacOperation.Create); const hasUpdateMCPClientAccess = useRbac(RbacResource.MCPGateway, RbacOperation.Update); const hasDeleteMCPClientAccess = useRbac(RbacResource.MCPGateway, RbacOperation.Delete); const [selectedMCPClient, setSelectedMCPClient] = useState(null); const [showDetailSheet, setShowDetailSheet] = useState(false); const { toast } = useToast(); const [reconnectingClients, setReconnectingClients] = useState([]); // RTK Query mutations const [reconnectMCPClient] = useReconnectMCPClientMutation(); const [deleteMCPClient] = useDeleteMCPClientMutation(); const handleCreate = () => { setFormOpen(true); }; const handleReconnect = async (client: MCPClient) => { try { setReconnectingClients((prev) => [...prev, client.config.client_id]); await reconnectMCPClient(client.config.client_id).unwrap(); setReconnectingClients((prev) => prev.filter((id) => id !== client.config.client_id)); toast({ title: "Reconnected", description: `Client ${client.config.name} reconnected successfully.` }); if (refetch) { await refetch(); } } catch (error) { setReconnectingClients((prev) => prev.filter((id) => id !== client.config.client_id)); toast({ title: "Error", description: getErrorMessage(error), variant: "destructive" }); } }; const handleDelete = async (client: MCPClient) => { try { await deleteMCPClient(client.config.client_id).unwrap(); toast({ title: "Deleted", description: `Client ${client.config.name} removed successfully.` }); if (refetch) { await refetch(); } } catch (error) { toast({ title: "Error", description: getErrorMessage(error), variant: "destructive" }); } }; const handleSaved = async () => { setFormOpen(false); if (refetch) { await refetch(); } }; const getConnectionDisplay = (client: MCPClient) => { if (client.config.connection_type === "stdio") { return `${client.config.stdio_config?.command} ${client.config.stdio_config?.args.join(" ")}` || "STDIO"; } // connection_string is now an EnvVar, display the value or env_var reference const connStr = client.config.connection_string; if (connStr) { return connStr.from_env ? connStr.env_var : connStr.value || `${client.config.connection_type.toUpperCase()}`; } return `${client.config.connection_type.toUpperCase()}`; }; const getConnectionTypeDisplay = (type: string) => { switch (type) { case "http": return "HTTP"; case "sse": return "SSE"; case "stdio": return "STDIO"; default: return type.toUpperCase(); } }; const handleRowClick = (mcpClient: MCPClient) => { setSelectedMCPClient(mcpClient); setShowDetailSheet(true); }; const handleDetailSheetClose = () => { setShowDetailSheet(false); setSelectedMCPClient(null); }; const handleEditTools = async () => { setShowDetailSheet(false); setSelectedMCPClient(null); if (refetch) { await refetch(); } }; const hasActiveFilters = debouncedSearch; // True empty state: no servers at all (not just filtered to zero) if (totalCount === 0 && !hasActiveFilters) { return ( <> {formOpen && setFormOpen(false)} onSaved={handleSaved} />} ); } return (
{showDetailSheet && selectedMCPClient && ( )}

MCP Server Catalog

Manage servers that can connect to the MCP Tools endpoint.

{/* Toolbar: Search */}
onSearchChange(e.target.value)} className="pl-9" data-testid="mcp-clients-search-input" />
Name Connection Type Code Mode Connection Info Enabled Tools Auto-execute Tools State {mcpClients.length === 0 ? ( No matching MCP servers found. ) : ( mcpClients.map((c: MCPClient) => { const enabledToolsCount = c.state == "connected" ? c.config.tools_to_execute?.includes("*") ? c.tools?.length : (c.config.tools_to_execute?.length ?? 0) : 0; const autoExecuteToolsCount = c.state == "connected" ? c.config.tools_to_auto_execute?.includes("*") ? c.tools?.length : (c.config.tools_to_auto_execute?.length ?? 0) : 0; return ( handleRowClick(c)} > {c.config.name} {getConnectionTypeDisplay(c.config.connection_type)} {c.state == "connected" ? <>{c.config.is_code_mode_client ? "Enabled" : "Disabled"} : "-"} {getConnectionDisplay(c)} {c.state == "connected" ? ( <> {enabledToolsCount}/{c.tools?.length} ) : ( "-" )} {c.state == "connected" ? ( <> {autoExecuteToolsCount}/{c.tools?.length} ) : ( "-" )} {c.state} e.stopPropagation()}> Remove MCP Server Are you sure you want to remove MCP server {c.config.name}? You will need to reconnect the server to continue using it. Cancel handleDelete(c)} className="bg-destructive hover:bg-destructive/90"> Delete ); }) )}
{/* Pagination */} {totalCount > 0 && (

Showing {offset + 1}-{Math.min(offset + limit, totalCount)} of {totalCount}

)} {formOpen && setFormOpen(false)} onSaved={handleSaved} />}
); }