import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { Check, ExternalLink, Loader2, RotateCcw, Search, Server, SlidersHorizontal, Trash2, X, } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { SegmentedControl } from "@/components/ui/segmented-control"; import type { McpPresetInfo } from "@/lib/types"; import { cn } from "@/lib/utils"; export type McpManagementTab = "overview" | "tools" | "connection"; type McpAction = "enable" | "disable" | "remove" | "test" | "reconnect"; interface McpManagementDialogProps { preset: McpPresetInfo; values: Record; actionKey: string | null; statusLabel: string; statusTone: "success" | "warning" | "neutral"; tab: McpManagementTab; icon: ReactNode; onTabChange: (tab: McpManagementTab) => void; onOpenChange: (open: boolean) => void; onFieldChange: (presetName: string, fieldName: string, value: string) => void; onAction: (action: McpAction, name: string, values?: Record) => void; onOAuthConnect: (name: string, reset?: boolean) => void; onToolsChange: (name: string, enabledTools: string[]) => void; } export function McpManagementDialog({ preset, values, actionKey, statusLabel, statusTone, tab, icon, onTabChange, onOpenChange, onFieldChange, onAction, onOAuthConnect, onToolsChange, }: McpManagementDialogProps) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const inventoryRef = useRef(preset.tool_names ?? []); if (preset.tool_names?.length) inventoryRef.current = preset.tool_names; const toolNames = inventoryRef.current; const [toolQuery, setToolQuery] = useState(""); const [draftEnabledTools, setDraftEnabledTools] = useState( preset.enabled_tools ?? ["*"], ); const testBusy = actionKey === `test:${preset.name}`; const reconnectBusy = actionKey === `reconnect:${preset.name}`; const removeBusy = actionKey === `remove:${preset.name}`; const enableBusy = actionKey === `enable:${preset.name}`; const toolsBusy = actionKey === `tools:${preset.name}`; const oauthBusy = actionKey === `oauth:${preset.name}`; const busy = testBusy || reconnectBusy || removeBusy || enableBusy || toolsBusy || oauthBusy; const configuredInstalled = preset.installed && preset.configured; const isOAuth = preset.auth === "oauth"; const inspectionRequestedRef = useRef(false); const allowAllTools = draftEnabledTools.includes("*"); const selectedTools = new Set(allowAllTools ? toolNames : draftEnabledTools); const normalizedQuery = toolQuery.trim().toLowerCase(); const filteredTools = useMemo( () => toolNames.filter((name) => name.toLowerCase().includes(normalizedQuery)), [normalizedQuery, toolNames], ); const knownToolCount = preset.tool_count ?? toolNames.length; const selectedToolCount = allowAllTools ? knownToolCount : selectedTools.size; const requiredFieldsComplete = preset.required_fields .filter((field) => field.required && !field.configured) .every((field) => Boolean(values[field.name]?.trim())); const initialTools = normalizeTools(preset.enabled_tools ?? ["*"]); const draftTools = normalizeTools(draftEnabledTools); const description = tx( `settings.mcp.presetDescriptions.${preset.name}`, preset.description || preset.note || preset.name, ); const toolsDirty = initialTools.join("\n") !== draftTools.join("\n"); const tabs: Array<{ value: McpManagementTab; label: ReactNode }> = [ { value: "overview", label: tx("settings.mcp.overviewTab", "Overview") }, { value: "tools", label: tx("settings.mcp.toolScope", "Tools"), }, { value: "connection", label: tx("settings.mcp.connectionTab", "Connection") }, ]; const activePanelLabel = tab === "overview" ? tx("settings.mcp.overviewTab", "Overview") : tab === "tools" ? tx("settings.mcp.toolScope", "Tools") : tx("settings.mcp.connectionTab", "Connection"); const toggleTool = (toolName: string) => { const next = new Set(allowAllTools ? toolNames : draftEnabledTools); if (next.has(toolName)) next.delete(toolName); else next.add(toolName); setDraftEnabledTools(next.size === toolNames.length ? ["*"] : Array.from(next)); }; const connect = () => { onOpenChange(false); if (isOAuth) { onOAuthConnect(preset.name, configuredInstalled); return; } onAction(configuredInstalled ? "reconnect" : "enable", preset.name, values); }; const inspectTools = () => { inspectionRequestedRef.current = true; onAction("test", preset.name); }; useEffect(() => { if ( tab !== "tools" || !configuredInstalled || toolNames.length || busy || preset.error || inspectionRequestedRef.current ) { return; } inspectionRequestedRef.current = true; onAction("test", preset.name); }, [busy, configuredInstalled, onAction, preset.error, preset.name, tab, toolNames.length]); return (
{icon}
{preset.display_name} {description}
{statusLabel}
{tab === "overview" ? ( ) : tab === "tools" ? ( setDraftEnabledTools(["*"])} onClear={() => setDraftEnabledTools([])} onTest={inspectTools} onOpenConnection={() => onTabChange("connection")} /> ) : ( { onOpenChange(false); onAction("remove", preset.name); }} /> )}
{tab === "tools" && toolsDirty ? (
) : null}
); } function OverviewPanel({ preset, description, statusLabel, knownToolCount, selectedToolCount, }: { preset: McpPresetInfo; description: string; statusLabel: string; knownToolCount: number; selectedToolCount: number; }) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const previewTools = (preset.tool_names ?? []).slice(0, 4); return (

{tx("settings.mcp.about", "About this MCP")}

{description}

{preset.docs_url ? ( {tx("settings.mcp.openDocs", "Open docs")} ) : null}
{knownToolCount ? ( ) : null}
{previewTools.length ? (

{tx("settings.mcp.toolPreview", "Tools")}

{previewTools.map((tool) => ( {displayToolName(tool, preset.name)} ))} {knownToolCount > previewTools.length ? ( +{knownToolCount - previewTools.length} ) : null}
) : null}
); } function ToolsPanel({ preset, toolNames, filteredTools, query, selectedTools, selectedToolCount, toolsBusy, testBusy, configuredInstalled, onQueryChange, onToggleTool, onSelectAll, onClear, onTest, onOpenConnection, }: { preset: McpPresetInfo; toolNames: string[]; filteredTools: string[]; query: string; selectedTools: Set; selectedToolCount: number; toolsBusy: boolean; testBusy: boolean; configuredInstalled: boolean; onQueryChange: (value: string) => void; onToggleTool: (name: string) => void; onSelectAll: () => void; onClear: () => void; onTest: () => void; onOpenConnection: () => void; }) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); if (!toolNames.length) { return (
{testBusy ? ( ) : ( )}

{testBusy ? tx("common.loading", "Loading…") : preset.error || tx("settings.mcp.noToolsAvailable", "No tools available")}

{!testBusy ? ( ) : null}
); } return (
{tx("settings.mcp.selectedCount", "{{count}} selected").replace("{{count}}", String(selectedToolCount))}
{filteredTools.length ? filteredTools.map((toolName) => { const selected = selectedTools.has(toolName); return ( ); }) : (
{tx("settings.mcp.noMatchingTools", "No tools match this search.")}
)}
); } function ConnectionPanel({ preset, values, busy, connectBusy, removeBusy, requiredFieldsComplete, onFieldChange, onConnect, onRemove, }: { preset: McpPresetInfo; values: Record; busy: boolean; connectBusy: boolean; removeBusy: boolean; requiredFieldsComplete: boolean; onFieldChange: (presetName: string, fieldName: string, value: string) => void; onConnect: () => void; onRemove: () => void; }) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const configuredInstalled = preset.installed && preset.configured; const hasFields = preset.required_fields.length > 0; const authentication = preset.auth === "oauth" ? "OAuth" : hasFields ? tx("settings.mcp.credentials", "Credentials") : tx("settings.mcp.none", "None"); const connectionLabel = preset.transport === "stdio" ? tx("settings.mcp.command", "Command") : tx("settings.mcp.endpointLabel", "Endpoint"); const connectLabel = configuredInstalled ? tx("settings.mcp.reconnect", "Reconnect") : tx("settings.mcp.setup", "Connect"); return (

{tx("settings.mcp.connectionDetails", "Connection details")}

{preset.connection_summary ? (

{connectionLabel}

{preset.connection_summary}
) : null}
{formatTransport(preset.transport)} · {authentication}
{preset.docs_url ? ( {tx("settings.mcp.openDocs", "Open docs")} ) : null} {configuredInstalled && preset.install_supported ? ( ) : null}
{preset.error ? (
{preset.error}
) : null} {hasFields ? (

{tx("settings.mcp.credentials", "Credentials")}

{preset.required_fields.map((field) => { const inputId = `mcp-manage-${preset.name}-${field.name}`; return ( ); })}
) : null} {!configuredInstalled && preset.install_supported ? (
) : null} {preset.installed && preset.enabled === undefined ? (
) : null}
); } function MetricCard({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function StatusPill({ children, tone }: { children: ReactNode; tone: "success" | "warning" | "neutral" }) { return ( {children} ); } function normalizeTools(tools: string[]): string[] { return tools.includes("*") ? ["*"] : [...tools].sort((left, right) => left.localeCompare(right)); } function displayToolName(toolName: string, serverName: string): string { const wrappedPrefix = `mcp_${serverName}_`; return toolName.startsWith(wrappedPrefix) ? toolName.slice(wrappedPrefix.length) : toolName; } function formatTransport(transport: string): string { if (transport === "streamableHttp") return "Streamable HTTP"; if (transport === "stdio") return "STDIO"; if (transport === "sse") return "SSE"; return transport || "MCP"; }