import { useState, useRef } from 'react'; import { MCPIcon } from '@librechat/client'; import { PermissionBits, hasPermissions } from 'librechat-data-provider'; import type { MCPServerStatusIconProps } from '~/components/MCP/MCPServerStatusIcon'; import type { MCPServerDefinition } from '~/hooks'; import MCPServerDialog from './MCPServerDialog'; import { getStatusDotColor } from './MCPStatusBadge'; import MCPCardActions from './MCPCardActions'; import { useMCPServerManager, useLocalize } from '~/hooks'; import { cn } from '~/utils'; interface MCPServerCardProps { server: MCPServerDefinition; getServerStatusIconProps: (serverName: string) => MCPServerStatusIconProps; canCreateEditMCPs: boolean; } /** * Compact card component for displaying an MCP server with status and actions. * * Visual design: * - Status shown via colored dot on icon (no separate badge - avoids redundancy) * - Action buttons clearly indicate available operations * - Consistent with MCPServerMenuItem in chat dropdown */ export default function MCPServerCard({ server, getServerStatusIconProps, canCreateEditMCPs, }: MCPServerCardProps) { const localize = useLocalize(); const triggerRef = useRef(null); const { initializeServer, revokeOAuthForServer } = useMCPServerManager(); const [dialogOpen, setDialogOpen] = useState(false); const statusIconProps = getServerStatusIconProps(server.serverName); const { serverStatus, onConfigClick, isInitializing, canCancel, onCancel, hasCustomUserVars = false, } = statusIconProps; const canEditThisServer = hasPermissions(server.effectivePermissions, PermissionBits.EDIT); const displayName = server.config?.title || server.serverName; const description = server.config?.description; const statusDotColor = getStatusDotColor(serverStatus, isInitializing); const canEdit = canCreateEditMCPs && canEditThisServer; const handleInitialize = () => { /** If server has custom user vars and is not already connected, show config dialog first * This ensures users can enter credentials before initialization attempts */ if (hasCustomUserVars && serverStatus?.connectionState !== 'connected') { onConfigClick({ stopPropagation: () => {}, preventDefault: () => {} } as React.MouseEvent); return; } initializeServer(server.serverName); }; const handleRevoke = () => { revokeOAuthForServer(server.serverName); }; const handleEditClick = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); setDialogOpen(true); }; // Determine status text for accessibility const getStatusText = () => { if (isInitializing) return localize('com_nav_mcp_status_initializing'); if (!serverStatus) return localize('com_nav_mcp_status_unknown'); const { connectionState, requiresOAuth } = serverStatus; if (connectionState === 'connected') return localize('com_nav_mcp_status_connected'); if (connectionState === 'connecting') return localize('com_nav_mcp_status_connecting'); if (connectionState === 'error') return localize('com_nav_mcp_status_error'); if (connectionState === 'disconnected') { return requiresOAuth ? localize('com_nav_mcp_status_needs_auth') : localize('com_nav_mcp_status_disconnected'); } return localize('com_nav_mcp_status_unknown'); }; return ( <>
{/* Server Icon with Status Dot */}
{server.config?.iconPath ? ( ) : (
)} {/* Status dot - color indicates connection state */} {/* Server Info */}
{displayName} {description &&

{description}

}
{/* Actions */}
{/* Edit Dialog - separate from card */} {canEdit && ( )} ); }