import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { X, Plus, Server } from "lucide-react"; import { McpServer, McpStatus } from "../../types"; import McpListItem from "./McpListItem"; import McpFormModal from "./McpFormModal"; import { ConfirmDialog } from "../ConfirmDialog"; import { extractErrorMessage } from "../../utils/errorUtils"; import { mcpPresets } from "../../config/mcpPresets"; import McpToggle from "./McpToggle"; import { cardStyles, cn } from "../../lib/styles"; interface McpPanelProps { onClose: () => void; onNotify?: ( message: string, type: "success" | "error", duration?: number, ) => void; } /** * MCP 管理面板 * 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行 */ const McpPanel: React.FC = ({ onClose, onNotify }) => { const { t } = useTranslation(); const [status, setStatus] = useState(null); const [servers, setServers] = useState>({}); const [loading, setLoading] = useState(true); const [isFormOpen, setIsFormOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [confirmDialog, setConfirmDialog] = useState<{ isOpen: boolean; title: string; message: string; onConfirm: () => void; } | null>(null); const reload = async () => { setLoading(true); try { const s = await window.api.getClaudeMcpStatus(); setStatus(s); const text = await window.api.readClaudeMcpConfig(); if (text) { try { const obj = JSON.parse(text); const list = (obj?.mcpServers || {}) as Record; setServers(list); } catch (e) { console.error("Failed to parse mcp.json", e); setServers({}); } } else { setServers({}); } } finally { setLoading(false); } }; useEffect(() => { reload(); }, []); const handleToggle = async (id: string, enabled: boolean) => { try { const server = servers[id]; let updatedServer: McpServer | null = null; if (server) { updatedServer = { ...server, enabled }; } else { const preset = mcpPresets.find((p) => p.id === id); if (!preset) return; // 既不是已安装项也不是预设,忽略 updatedServer = { ...(preset.server as McpServer), enabled }; } await window.api.upsertClaudeMcpServer(id, updatedServer as McpServer); await reload(); onNotify?.( enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"), "success", 1500, ); } catch (e: any) { const detail = extractErrorMessage(e); onNotify?.( detail || t("mcp.error.saveFailed"), "error", detail ? 6000 : 5000, ); } }; const handleEdit = (id: string) => { setEditingId(id); setIsFormOpen(true); }; const handleAdd = () => { setEditingId(null); setIsFormOpen(true); }; const handleDelete = (id: string) => { setConfirmDialog({ isOpen: true, title: t("mcp.confirm.deleteTitle"), message: t("mcp.confirm.deleteMessage", { id }), onConfirm: async () => { try { await window.api.deleteClaudeMcpServer(id); await reload(); setConfirmDialog(null); onNotify?.(t("mcp.msg.deleted"), "success", 1500); } catch (e: any) { const detail = extractErrorMessage(e); onNotify?.( detail || t("mcp.error.deleteFailed"), "error", detail ? 6000 : 5000, ); } }, }); }; const handleSave = async (id: string, server: McpServer) => { try { await window.api.upsertClaudeMcpServer(id, server); await reload(); setIsFormOpen(false); setEditingId(null); onNotify?.(t("mcp.msg.saved"), "success", 1500); } catch (e: any) { const detail = extractErrorMessage(e); onNotify?.( detail || t("mcp.error.saveFailed"), "error", detail ? 6000 : 5000, ); // 继续抛出错误,让表单层可以给到直观反馈(避免被更高层遮挡) throw e; } }; const handleCloseForm = () => { setIsFormOpen(false); setEditingId(null); }; const serverEntries = useMemo(() => Object.entries(servers), [servers]); return (
{/* Backdrop */}
{/* Panel */}
{/* Header */}

{t("mcp.title")}

{/* Info Section */}
{t("mcp.configPath")}:{" "} {status?.userConfigPath}
{t("mcp.serverCount", { count: status?.serverCount || 0 })}
{/* Content - Scrollable */}
{loading ? (
{t("mcp.loading")}
) : ( (() => { const notInstalledPresets = mcpPresets.filter( (p) => !servers[p.id], ); const hasAny = serverEntries.length > 0 || notInstalledPresets.length > 0; if (!hasAny) { return (

{t("mcp.empty")}

{t("mcp.emptyDescription")}

); } return (
{/* 已安装 */} {serverEntries.map(([id, server]) => ( ))} {/* 预设(未安装) */} {notInstalledPresets.map((p) => { const s = { ...(p.server as McpServer), enabled: false, } as McpServer; const details = [s.type, s.command, ...(s.args || [])].join( " · ", ); return (
handleToggle(p.id, en)} />

{p.id}

{details}

); })}
); })() )}
{/* Form Modal */} {isFormOpen && ( )} {/* Confirm Dialog */} {confirmDialog && ( setConfirmDialog(null)} /> )}
); }; export default McpPanel;