/** * McpServerCard — grid card for a single MCP server in the catalog. * Shows server icon from registry when available. */ import { useState } from 'react'; import { api } from '@renderer/api'; import { Badge } from '@renderer/components/ui/badge'; import { Button } from '@renderer/components/ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { useStore } from '@renderer/store'; import { formatCompactNumber, formatRelativeTime } from '@renderer/utils/formatters'; import { getMcpInstallationSummaryLabel, getMcpOperationKey, sanitizeMcpServerName, } from '@shared/utils/extensionNormalizers'; import { Clock, Cloud, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react'; import { Github as GithubIcon } from 'lucide-react'; import { InstallButton } from '../common/InstallButton'; import { SourceBadge } from '../common/SourceBadge'; import type { InstalledMcpEntry, McpCatalogItem, McpServerDiagnostic, } from '@shared/types/extensions'; interface McpServerCardProps { server: McpCatalogItem; isInstalled: boolean; installedEntry?: InstalledMcpEntry | null; installedEntries?: InstalledMcpEntry[]; diagnostic?: McpServerDiagnostic | null; diagnosticsLoading?: boolean; onClick: (serverId: string) => void; } export const McpServerCard = ({ server, isInstalled, installedEntry, installedEntries = [], diagnostic, diagnosticsLoading, onClick, }: McpServerCardProps): React.JSX.Element => { const operationKey = getMcpOperationKey(server.id, 'user'); const installProgress = useStore((s) => s.mcpInstallProgress[operationKey] ?? 'idle'); const installMcpServer = useStore((s) => s.installMcpServer); const uninstallMcpServer = useStore((s) => s.uninstallMcpServer); const installError = useStore((s) => s.installErrors[operationKey]); const stars = useStore((s) => server.repositoryUrl ? s.mcpGitHubStars[server.repositoryUrl] : undefined ); const canAutoInstall = !!server.installSpec; const normalizedInstalledEntries = installedEntries.length ? installedEntries : installedEntry ? [installedEntry] : []; const requiresConfiguration = server.installSpec?.type === 'http' || server.envVars.length > 0 || server.requiresAuth || (server.authHeaders?.length ?? 0) > 0; const defaultServerName = sanitizeMcpServerName(server.name); const userInstallEntry = normalizedInstalledEntries.find((entry) => entry.scope === 'user') ?? null; const installSummaryLabel = getMcpInstallationSummaryLabel(normalizedInstalledEntries); const supportsDirectInstalledAction = isInstalled && normalizedInstalledEntries.length === 1 && userInstallEntry?.name === defaultServerName && !requiresConfiguration; const shouldShowDirectInstallButton = canAutoInstall && (!isInstalled ? !requiresConfiguration : supportsDirectInstalledAction); const [imgError, setImgError] = useState(false); const hasIcon = !!server.iconUrl && !imgError; const diagnosticBadgeClass = diagnostic?.status === 'connected' ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400' : diagnostic?.status === 'needs-authentication' ? 'border-amber-500/30 bg-amber-500/10 text-amber-400' : diagnostic?.status === 'failed' ? 'border-red-500/30 bg-red-500/10 text-red-400' : 'border-border bg-surface-raised text-text-muted'; return (
{server.description}
{diagnostic?.target && ({diagnostic.target}
)} {/* Footer indicators + install button */}