import React, { useState, useEffect } from 'react'; import { Box, Typography, Button, Card, CardContent, Grid, IconButton, Menu, MenuItem, Checkbox, Pagination, CircularProgress, Snackbar, Alert, TextField, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; import { Add as AddIcon, MoreVert as MoreVertIcon, Delete as DeleteIcon, FileDownload as ExportIcon, FileUpload as ImportIcon, Search as SearchIcon, Edit as EditIcon } from '@mui/icons-material'; import { styled } from '@mui/material/styles'; import { useMcpSetting } from '@/hooks/setting-hooks'; import McpDialog from '@/pages/setting/components/McpDialog'; import type { IMcpServer } from '@/interfaces/database/mcp'; import type { IImportMcpServersRequestBody, ICreateMcpServerRequestBody, ITestMcpRequestBody } from '@/interfaces/request/mcp'; import { useMessage } from '@/hooks/useSnackbar'; import dayjs from 'dayjs'; const PageContainer = styled(Box)(({ theme }) => ({ padding: theme.spacing(3), maxWidth: '1200px', margin: '0 auto', backgroundColor: '#f5f5f5', minHeight: '100vh' })); const PageHeader = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: theme.spacing(2), backgroundColor: theme.palette.background.paper, borderRadius: theme.shape.borderRadius, boxShadow: theme.shadows[1], })); const SearchContainer = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: theme.spacing(2), backgroundColor: theme.palette.background.paper, borderRadius: theme.shape.borderRadius, boxShadow: theme.shadows[1], marginTop: theme.spacing(2), marginBottom: theme.spacing(2), })); export default function McpSettingPage() { const { mcpServers, loading, total, fetchMcpServers, importMcpServers, exportMcpServers, testMcpServer, getMcpServerDetail, createMcpServer, updateMcpServer, deleteMcpServer, deleteMcpServers, } = useMcpSetting(); // 本地状态 const [searchString, setSearchString] = useState(''); const [selectedServers, setSelectedServers] = useState([]); const [anchorEl, setAnchorEl] = useState(null); const [selectedServerId, setSelectedServerId] = useState(null); const [mcpDialogOpen, setMcpDialogOpen] = useState(false); const [editingServer, setEditingServer] = useState | undefined | null>(null); const [importDialogOpen, setImportDialogOpen] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [pageSize] = useState(6); const [importJson, setImportJson] = useState(''); const showMessage = useMessage() // 初始化加载数据 useEffect(() => { fetchMcpServers(); }, [fetchMcpServers]); // 过滤和分页逻辑 const filteredServers = mcpServers.filter(server => server.name.toLowerCase().includes(searchString.toLowerCase()) || server.url.toLowerCase().includes(searchString.toLowerCase()) ); const totalPages = Math.ceil(filteredServers.length / pageSize); const paginatedServers = filteredServers.slice( (currentPage - 1) * pageSize, currentPage * pageSize ); const handleSearchChange = (event: React.ChangeEvent) => { setSearchString(event.target.value); setCurrentPage(1); }; const handleSelectServer = (serverId: string) => { setSelectedServers(prev => prev.includes(serverId) ? prev.filter(id => id !== serverId) : [...prev, serverId] ); }; const handleSelectAll = () => { if (selectedServers.length === paginatedServers.length) { setSelectedServers([]); } else { setSelectedServers(paginatedServers.map(server => server.id)); } }; const handleMenuClick = (event: React.MouseEvent, serverId: string) => { setAnchorEl(event.currentTarget); setSelectedServerId(serverId); }; const handleMenuClose = () => { setAnchorEl(null); setSelectedServerId(null); }; const handleEdit = async () => { // const server = mcpServers.find(s => s.id === selectedServerId); const result = await getMcpServerDetail(selectedServerId || ''); if (result.success) { const server = result.data; if (server != null && server != undefined) { const headers = server?.headers || {}; const authorization_token = headers['authorization_token'] || ''; if (authorization_token && authorization_token.length > 0) { server!.variables = { ...server?.variables, authorization_token, } } } setEditingServer(server); setMcpDialogOpen(true); } handleMenuClose(); }; const handleAdd = () => { setEditingServer(null); setMcpDialogOpen(true); }; const handleDelete = async () => { if (selectedServerId) { const result = await deleteMcpServer(selectedServerId); if (result.success) { showMessage.success('删除成功'); } else { showMessage.error(result.error || '删除失败'); } } handleMenuClose(); }; const handleBulkDelete = async () => { const result = await deleteMcpServers(selectedServers); if (result.success) { showMessage.success('批量删除成功'); setSelectedServers([]); } else { showMessage.error(result.error || '批量删除失败'); } }; const handleExport = async () => { const result = await exportMcpServers(selectedServers); if (result.success) { showMessage.success('导出成功'); } else { showMessage.error(result.error || '导出失败'); } }; const handleMcpSave = async (data: ICreateMcpServerRequestBody) => { try { if (editingServer) { if (!editingServer.id) { showMessage.error('server id 不能为空'); return { success: false, error: 'server id 不能为空' }; } const result = await updateMcpServer({ ...data, mcp_id: editingServer.id ?? '' }); if (result.success) { showMessage.success('MCP 服务器更新成功'); setMcpDialogOpen(false); setEditingServer(null); return result; } else { return result; } } else { const result = await createMcpServer(data); if (result.success) { showMessage.success('MCP 服务器创建成功'); setMcpDialogOpen(false); setEditingServer(null); return result; } else { return result; } } } catch (error: any) { const errorMessage = error.message || '操作失败'; return { success: false, error: errorMessage }; } }; const handleTestMcpServer = async (data: ITestMcpRequestBody) => { try { const result = await testMcpServer(data); if (result.success) { showMessage.success('测试成功'); return result; } else { return result; } } catch (error: any) { const errorMessage = error.message || '操作失败'; return { success: false, error: errorMessage }; } }; const handleImport = async () => { try { const importData = JSON.parse(importJson); if (importData.mcpServers) { const requestData: IImportMcpServersRequestBody = { mcpServers: Object.entries(importData.mcpServers).reduce((acc, [key, value]: [string, any]) => { acc[key] = { type: value.type, url: value.url, authorization_token: value.authorization_token || '' }; return acc; }, {} as any) }; const result = await importMcpServers(requestData); if (result.success) { setImportDialogOpen(false); setImportJson(''); } } else { showMessage.error('JSON 格式错误'); } } catch (error) { showMessage.error('JSON 格式错误'); } }; return ( MCP Servers Customize the list of MCP servers }} sx={{ width: 300 }} /> {selectedServers.length > 0 && ( )} {loading ? ( ) : ( <> {paginatedServers.map((server) => ( handleSelectServer(server.id)} size="small" /> handleMenuClick(e, server.id)} > {server.name} 类型: {server.server_type} 更新时间: {dayjs(server.update_date).format('YYYY-MM-DD HH:mm:ss')} ))} {/* 分页 */} setCurrentPage(page)} color="primary" /> 共 {total} 条 )} {/* 操作菜单 */} Edit Delete {/* MCP 对话框 */} { setMcpDialogOpen(false); setEditingServer(null); }} onSave={handleMcpSave} onTest={handleTestMcpServer} initialData={editingServer || {}} mode={editingServer ? 'edit' : 'create'} /> {/* 导入对话框 */} setImportDialogOpen(false)} maxWidth="md" fullWidth> Import MCP Servers Paste your MCP servers JSON configuration below. The format should match the Mock.json structure. setImportJson(e.target.value)} placeholder={`{ "mcpServers": { "Time": { "type": "sse", "url": "http://localhost:8080", "name": "Time Server", "authorization_token": "", "tool_configuration": {} } } }`} /> ); }