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"; 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]; if (!server) return; const updatedServer = { ...server, enabled }; await window.api.upsertClaudeMcpServer(id, updatedServer); await reload(); onNotify?.( enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"), "success", 1500, ); } catch (e: any) { onNotify?.(e?.message || t("mcp.error.saveFailed"), "error", 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) { onNotify?.(e?.message || t("mcp.error.deleteFailed"), "error", 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) { onNotify?.(e?.message || t("mcp.error.saveFailed"), "error", 6000); // 继续抛出错误,让表单层可以给到直观反馈(避免被更高层遮挡) 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")}
) : serverEntries.length === 0 ? (

{t("mcp.empty")}

{t("mcp.emptyDescription")}

) : (
{serverEntries.map(([id, server]) => ( ))}
)}
{/* Form Modal */} {isFormOpen && ( )} {/* Confirm Dialog */} {confirmDialog && ( setConfirmDialog(null)} /> )}
); }; export default McpPanel;