import { useState, useEffect, useMemo } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { IconX, IconPlus, IconTrash, IconServer, IconKey, IconRefresh, IconPlugConnected, IconLoader2, IconWorld, IconTerminal2, IconBrandGithub, IconChevronDown, } from "@tabler/icons-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useSettingsStore } from "@/stores"; import { bridge } from "@/services"; import { RECOMMENDED_MCP_SERVERS, recommendedToConfig, type RecommendedMCPServer } from "@/services/recommended-mcp"; import { cn } from "@/lib/utils"; import { MCP_SECRET_MASK, type MCPServerConfig } from "shared/legacy-sdk"; const MCP_SERVERS_KEY = ["mcpServers"] as const; const NO_SERVERS: MCPServerConfig[] = []; type TransportType = "stdio" | "http"; interface KeyValueField { key: string; value: string; } interface FormData { name: string; transport: TransportType; url: string; command: string; args: string[]; envVars: KeyValueField[]; headerVars: KeyValueField[]; bearerTokenEnvVar: string; requiresAuth: boolean; } function emptyForm(): FormData { return { name: "", transport: "stdio", url: "", command: "", args: [""], envVars: [], headerVars: [], bearerTokenEnvVar: "", requiresAuth: false, }; } function serverToForm(s?: MCPServerConfig): FormData { if (!s) return emptyForm(); const isHttp = s.transport === "http"; return { name: s.name, transport: isHttp ? "http" : "stdio", url: s.url ?? "", command: s.command ?? "", args: s.args ? [...s.args] : [], envVars: s.env ? Object.entries(s.env).map(([key, value]) => ({ key, value })) : [], headerVars: s.headers ? Object.entries(s.headers).map(([key, value]) => ({ key, value })) : [], bearerTokenEnvVar: s.bearerTokenEnvVar ?? "", requiresAuth: s.auth === "oauth", }; } function formToConfig(f: FormData): MCPServerConfig { const env = f.envVars.reduce((acc, { key, value }) => (key.trim() ? { ...acc, [key.trim()]: value } : acc), {} as Record); const headers = f.headerVars.reduce((acc, { key, value }) => (key.trim() ? { ...acc, [key.trim()]: value } : acc), {} as Record); if (f.transport === "http") { const bearerTokenEnvVar = f.bearerTokenEnvVar.trim(); return { name: f.name.trim(), transport: "http", url: f.url.trim(), headers: Object.keys(headers).length > 0 ? headers : undefined, bearerTokenEnvVar: bearerTokenEnvVar || undefined, auth: f.requiresAuth ? "oauth" : undefined, }; } const args = f.args.filter((arg) => arg.length > 0); return { name: f.name.trim(), transport: "stdio", command: f.command.trim(), args: args.length > 0 ? args : undefined, env: Object.keys(env).length > 0 ? env : undefined, }; } function validateForm(f: FormData): string | null { if (!f.name.trim()) return "Name required"; if (f.transport === "http" && !f.url.trim()) return "URL required"; if (f.transport === "stdio" && !f.command.trim()) return "Command required"; return null; } function KeyValueFields({ label, fields, onChange, }: { label: string; fields: KeyValueField[]; onChange: (fields: KeyValueField[]) => void; }) { return (
{fields.map((field, index) => (
onChange(fields.map((item, itemIndex) => ( itemIndex === index ? { ...item, key: event.target.value } : item )))} placeholder="KEY" className="h-6 text-xs font-mono flex-1" /> = onChange(fields.map((item, itemIndex) => ( itemIndex === index ? { ...item, value: event.target.value } : item )))} placeholder="value" className="h-6 text-xs font-mono flex-1" />
))}
); } function ServerForm({ data, onChange, onSubmit, onCancel, submitLabel, }: { data: FormData; onChange: (d: FormData) => void; onSubmit: () => void; onCancel: () => void; submitLabel: string; }) { const [error, setError] = useState(null); const set = (k: K, v: FormData[K]) => { onChange({ ...data, [k]: v }); setError(null); }; const handleSubmit = () => { const err = validateForm(data); if (err) { setError(err); return; } onSubmit(); }; return (
set("name", e.target.value)} className="h-7 text-xs" />
{(["stdio", "http"] as const).map((t) => ( ))}
{data.transport === "http" ? ( <>
set("url", e.target.value)} placeholder="https://…" className="h-7 text-xs font-mono" />
set("headerVars", headerVars)} />
set("bearerTokenEnvVar", e.target.value)} placeholder="MCP_TOKEN" className="h-7 text-xs font-mono" />
) : (
set("command", e.target.value)} placeholder="npx" className="h-7 text-xs font-mono" />
{data.args.map((arg, index) => (
set("args", data.args.map((item, itemIndex) => ( itemIndex === index ? event.target.value : item )))} placeholder={index === 0 ? "-y" : "@pkg/name"} className="h-7 text-xs font-mono flex-1" />
))}
)} {data.transport === "stdio" && ( set("envVars", envVars)} /> )} {error &&

{error}

}
); } function ServerItem({ server, onDelete }: { server: MCPServerConfig; onDelete: () => void }) { const [expanded, setExpanded] = useState(false); const [form, setForm] = useState(() => serverToForm(server)); const [testOutput, setTestOutput] = useState(null); const [isLoading, setIsLoading] = useState(false); const queryClient = useQueryClient(); const isHttp = server.transport === "http"; const handleUpdate = async () => { try { const servers = await bridge.updateMCPServer(server.name, formToConfig(form)); queryClient.setQueryData(MCP_SERVERS_KEY, servers); setExpanded(false); } catch (error) { setTestOutput(`Update failed: ${error instanceof Error ? error.message : String(error)}`); } }; const handleAction = async (action: () => Promise) => { setIsLoading(true); setTestOutput(null); try { await action(); } catch (error) { setExpanded(true); setTestOutput(`Error: ${error instanceof Error ? error.message : String(error)}`); } finally { setIsLoading(false); } }; const handleTest = () => handleAction(async () => { setExpanded(true); const result = await bridge.testMCP(server.name); setTestOutput(result.output); }); const handleAuth = () => handleAction(() => bridge.authMCP(server.name)); const handleResetAuth = () => handleAction(() => bridge.resetAuthMCP(server.name)); return (
setExpanded(!expanded)}>
{isHttp ? : }
{server.name} {server.auth === "oauth" && OAuth}

{isHttp ? server.url : ( <> {server.command} {(server.args ?? []).map((arg, index) => {arg})} )}

e.stopPropagation()}> {server.auth === "oauth" && ( <> )} {server.mutable !== false && ( )}
{expanded && (
{testOutput && (
{testOutput.split("\n").map((line, i) => (
{line}
))}
)} {server.mutable === false ? (

{server.source === "plugin" ? `Contributed by plugin "${server.origin ?? ""}" — update the plugin manifest instead` : `Defined in ${server.origin ?? "a project config file"} — edit that file instead`}

) : ( { void handleUpdate(); }} onCancel={() => setExpanded(false)} submitLabel="Update" /> )}
)}
); } function RecommendedItem({ server, onInstall, isInstalling }: { server: RecommendedMCPServer; onInstall: () => void; isInstalling: boolean }) { return (
{server.name} {server.github && ( )}

{server.description}

); } export function MCPServersModal() { const { mcpModalOpen, setMCPModalOpen } = useSettingsStore(); const queryClient = useQueryClient(); const [showAdd, setShowAdd] = useState(false); const [addForm, setAddForm] = useState(() => emptyForm()); const [installingRecommended, setInstallingRecommended] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [actionError, setActionError] = useState(null); const serversQuery = useQuery({ queryKey: MCP_SERVERS_KEY, queryFn: () => bridge.getMCPServers(), enabled: mcpModalOpen, }); const mcpServers = serversQuery.data ?? NO_SERVERS; const loadError = serversQuery.isError ? (serversQuery.error instanceof Error ? serversQuery.error.message : String(serversQuery.error)) : null; useEffect(() => { if (!showAdd) setAddForm(emptyForm()); }, [showAdd]); const installedNames = useMemo(() => new Set(mcpServers.map((s) => s.name)), [mcpServers]); const handleAdd = async () => { setActionError(null); try { const servers = await bridge.addMCPServer(formToConfig(addForm)); queryClient.setQueryData(MCP_SERVERS_KEY, servers); setShowAdd(false); } catch (error) { setActionError(error instanceof Error ? error.message : String(error)); } }; const handleDelete = async () => { if (!deleteTarget) return; setIsDeleting(true); setActionError(null); try { const servers = await bridge.removeMCPServer(deleteTarget); queryClient.setQueryData(MCP_SERVERS_KEY, servers); } catch (error) { setActionError(error instanceof Error ? error.message : String(error)); } setIsDeleting(false); setDeleteTarget(null); }; const handleInstallRecommended = async (server: RecommendedMCPServer) => { setInstallingRecommended(server.id); setActionError(null); try { const config = recommendedToConfig(server); const servers = await bridge.addMCPServer(config); queryClient.setQueryData(MCP_SERVERS_KEY, servers); } catch (error) { setActionError(error instanceof Error ? error.message : String(error)); } setInstallingRecommended(null); }; if (!mcpModalOpen) return null; return ( <>

MCP Servers

{(actionError ?? loadError) && (
{actionError ?? loadError}
)} {showAdd && (
Add MCP Server
{ void handleAdd(); }} onCancel={() => setShowAdd(false)} submitLabel="Add Server" />
)} {mcpServers.length > 0 && (
{mcpServers.map((server) => ( setDeleteTarget(server.name)} /> ))}
)} {mcpServers.length === 0 && !showAdd && (

No MCP servers configured

)}

Recommended

{RECOMMENDED_MCP_SERVERS.filter((s) => !installedNames.has(s.id)).map((server) => ( { void handleInstallRecommended(server); }} isInstalling={installingRecommended === server.id} /> ))} {RECOMMENDED_MCP_SERVERS.every((s) => installedNames.has(s.id)) && (

All recommended servers installed

)}
!open && setDeleteTarget(null)}> Delete MCP Server? This will remove "{deleteTarget}" from your configuration. This action cannot be undone. Cancel { void handleDelete(); }} disabled={isDeleting} className="bg-destructive text-destructive-foreground hover:bg-destructive/90"> {isDeleting ? "Deleting…" : "Delete"} ); }