import { useEffect, useMemo, useState } from 'react' import { Alert, Button, Card, Col, Descriptions, Empty, Input, List, Row, Space, Spin, Statistic, Tag, Typography, message, } from 'antd' import { mcpApi } from '@/lib/api' import type { JsonSchema, McpContent, McpManagedStatus, McpServerConfig, McpToolDescriptor } from '@/types/system' const { TextArea } = Input function statusColor(state?: string) { switch (state) { case 'running': return 'green' case 'starting': return 'orange' case 'external': return 'blue' default: return 'default' } } function asSchemaRecord(value: unknown): Record { if (!value || typeof value !== 'object' || Array.isArray(value)) { return {} } return value as Record } function buildExampleValue(schema?: JsonSchema | Record | unknown): unknown { const current = asSchemaRecord(schema) if ('default' in current) { return current.default } const enumValues = Array.isArray(current.enum) ? current.enum : [] if (enumValues.length > 0) { return enumValues[0] } const schemaType = typeof current.type === 'string' ? current.type : '' switch (schemaType) { case 'string': return '' case 'number': case 'integer': return 0 case 'boolean': return false case 'array': return current.items ? [buildExampleValue(current.items)] : [] case 'object': break default: if (!current.properties) { return {} } } const properties = asSchemaRecord(current.properties) const result: Record = {} for (const [key, value] of Object.entries(properties)) { result[key] = buildExampleValue(value) } return result } function buildExampleArguments(tool?: McpToolDescriptor | null) { if (!tool?.inputSchema) { return '{}' } return JSON.stringify(buildExampleValue(tool.inputSchema), null, 2) } function renderContent(content: McpContent, index: number) { if (content.type === 'image' && content.data) { const mimeType = content.mimeType || 'image/png' return ( {`mcp-result-${index ) } return (
        {content.text || JSON.stringify(content, null, 2)}
      
) } export function McpTestPage() { const [status, setStatus] = useState(null) const [serverConfig, setServerConfig] = useState(null) const [tools, setTools] = useState([]) const [selectedToolName, setSelectedToolName] = useState('') const [argumentsText, setArgumentsText] = useState('{}') const [result, setResult] = useState([]) const [loading, setLoading] = useState(true) const [actionLoading, setActionLoading] = useState(false) const [calling, setCalling] = useState(false) const selectedTool = useMemo( () => tools.find((item) => item.name === selectedToolName) || null, [selectedToolName, tools], ) const reload = async () => { setLoading(true) try { const statusRes = await mcpApi.getStatus() setStatus(statusRes.data.status) setServerConfig(statusRes.data.mcpServerConfig) if (!statusRes.data.status.reachable) { setTools([]) return } const listRes = await mcpApi.getList() const nextTools = listRes.data.list?.tools || [] setStatus(listRes.data.status) setServerConfig(listRes.data.mcpServerConfig) setTools(nextTools) if (nextTools.length > 0) { setSelectedToolName((current) => nextTools.some((item) => item.name === current) ? current : nextTools[0].name, ) } } finally { setLoading(false) } } useEffect(() => { reload() }, []) useEffect(() => { setArgumentsText(buildExampleArguments(selectedTool)) }, [selectedTool]) const runAction = async (action: 'start' | 'stop') => { setActionLoading(true) try { if (action === 'start') { await mcpApi.start() message.success('MCP 独立服务已启动') } else { await mcpApi.stop() message.success('MCP 独立服务已停用') setResult([]) } await reload() } finally { setActionLoading(false) } } const callTool = async () => { if (!selectedToolName) { message.warning('请先选择一个工具') return } let parsedArguments: Record = {} try { parsedArguments = JSON.parse(argumentsText || '{}') as Record } catch { message.error('参数 JSON 解析失败,请先修正格式') return } setCalling(true) try { const response = await mcpApi.test({ name: selectedToolName, arguments: parsedArguments, }) setResult(response.data) message.success('工具调用成功') } finally { setCalling(false) } } const activeServer = useMemo(() => { if (!serverConfig?.mcpServers) { return null } const [serverName, serverValue] = Object.entries(serverConfig.mcpServers)[0] || [] if (!serverName || !serverValue) { return null } return { serverName, ...serverValue, } }, [serverConfig]) return (
MCP Tools 管理 当前页直接管理 MCP 独立服务,并可在登录态下调用工具验证新项目接入是否正常。
{status?.lastError ? ( ) : null}
{status?.state || 'stopped'} {status?.message || '未获取状态信息'} ), }, { key: 'managed', label: '托管状态', children: status?.managed ? '页面托管' : '外部或未启动' }, { key: 'reachable', label: '健康检查', children: status?.reachable ? '可访问' : '不可访问' }, { key: 'baseURL', label: 'MCP 地址', children: status?.baseURL || '-' }, { key: 'healthURL', label: '健康检查地址', children: status?.healthURL || '-' }, { key: 'logPath', label: '日志文件', children: status?.logPath || '-' }, { key: 'config', label: '客户端配置', children: activeServer ? JSON.stringify(activeServer, null, 2) : '-' }, ]} /> {loading ? (
) : tools.length === 0 ? ( ) : ( ( setSelectedToolName(tool.name)} > {tool.name} {tool.name === selectedToolName ? 当前 : null} } description={tool.description || '未提供工具描述'} /> )} /> )}
调用工具 } > {selectedTool ? (
输入 Schema
                    {JSON.stringify(selectedTool.inputSchema || {}, null, 2)}
                  
调用参数 JSON