UNPKG

consultant-mcp-server

Version:

MCP Server for Enterprise Architecture Consultants

388 lines (351 loc) 12.1 kB
/** * Consultant MCP Server * 标准MCP服务实现,作为顾问工具集 */ const express = require('express'); const path = require('path'); const { spawn } = require('child_process'); const fs = require('fs'); const axios = require('axios'); class ConsultantMcpServer { constructor(options = {}) { this.options = { port: options.port || 3201, agentPath: options.agentPath || path.join(__dirname, '../consultant-agent/index.js'), agentUrl: options.agentUrl || 'http://localhost:3100', ...options }; this.app = express(); this.agentProcess = null; this.tools = []; this.setupServer(); } setupServer() { // 解析JSON请求体 this.app.use(express.json()); // 设置CORS头 this.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); // 健康检查端点 this.app.get('/health', (req, res) => { res.json({ jsonrpc: '2.0', result: { status: 'ok', agent: this.agentProcess ? 'running' : 'not_running', timestamp: new Date().toISOString() }, id: null }); }); // MCP发现端点 this.app.get('/api/discovery', (req, res) => { this.loadTools(); res.json({ jsonrpc: '2.0', result: { name: 'consultant-mcp-server', version: '1.0.0', description: 'MCP Server for Enterprise Architecture Consultants', tools: this.tools }, id: null }); }); // MCP执行端点 this.app.post('/api/execute', async (req, res) => { try { const { jsonrpc, method, params, id } = req.body; // 确保请求符合JSON-RPC 2.0格式 if (jsonrpc !== '2.0' || !method) { return res.status(400).json({ jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: id || null }); } console.log(`Executing method: ${method}`); console.log(`Params: ${JSON.stringify(params, null, 2)}`); // 尝试确保Agent在运行 const agentRunning = await this.ensureAgentRunning(); // 如果Agent不可用,使用模拟响应 if (!agentRunning) { console.log(`Agent not available. Sending simulated response for method: ${method}`); // 根据方法类型提供不同的模拟响应 let simulatedResult = { message: `Simulated response for ${method}. Agent not available.`, timestamp: new Date().toISOString() }; // 针对不同方法的特定模拟响应 switch (method) { case 'browse': simulatedResult = { ...simulatedResult, url: params.url, success: true, loggedIn: false }; break; case 'analyze-page': simulatedResult = { ...simulatedResult, pageInfo: { title: 'Simulated Page', url: 'https://example.com/simulated', timestamp: new Date().toISOString() }, extractedContent: { headings: { h1: ['Simulated Heading'] }, paragraphs: ['This is a simulated paragraph for testing.'] } }; break; case 'take-screenshot': simulatedResult = { ...simulatedResult, filename: `simulated_screenshot_${Date.now()}.png`, success: true }; break; // 其他方法的模拟响应... default: return res.status(400).json({ jsonrpc: '2.0', error: { code: -32601, message: 'Method not found' }, id: id || null }); } return res.json({ jsonrpc: '2.0', result: simulatedResult, id: id || null }); } // 实际与Agent交互的逻辑(如果Agent可用) try { // 使用JSON-RPC 2.0格式调用Agent const agentResponse = await axios.post(`${this.options.agentUrl}/api/execute`, { jsonrpc: '2.0', method, params, id }); // 返回Agent的响应 return res.json(agentResponse.data); } catch (agentError) { console.error('Error calling Agent:', agentError.message); // 如果调用Agent失败,返回错误信息 return res.json({ jsonrpc: '2.0', error: { code: -32603, message: 'Agent communication error', data: agentError.message }, id: id || null }); } } catch (error) { console.error('Error executing method:', error); res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal error', data: error.message }, id: req.body.id || null }); } }); } loadTools() { try { // 从多个可能的位置加载工具定义 const possiblePaths = [ path.join(__dirname, '../consultant-agent-tools.json'), path.join(__dirname, 'consultant-agent-tools.json'), path.join(process.cwd(), 'consultant-agent-tools.json'), path.join(process.env.HOME || process.env.USERPROFILE, '.cursor/mcp-servers/consultant-agent.json') ]; let toolsData = null; let loadedPath = null; // 尝试所有可能的路径 for (const toolsPath of possiblePaths) { if (fs.existsSync(toolsPath)) { try { const content = fs.readFileSync(toolsPath, 'utf8'); toolsData = JSON.parse(content); loadedPath = toolsPath; break; } catch (e) { console.warn(`Failed to read tools from ${toolsPath}: ${e.message}`); } } } if (toolsData && toolsData.tools) { this.tools = toolsData.tools; console.log(`Loaded ${this.tools.length} tools from ${loadedPath}`); } else { // 默认工具定义,保持与MCP标准兼容的格式 this.tools = [ { "name": "browse", "description": "Navigate to a Consultant URL and handle login", "parameters": { "type": "object", "properties": { "url": { "type": "string", "description": "The URL to navigate to" }, "credentials": { "type": "object", "properties": { "username": { "type": "string", "description": "Username for login" }, "password": { "type": "string", "description": "Password for login (optional)" } }, "required": ["username"] } }, "required": ["url"] } }, { "name": "analyze-page", "description": "Analyze current page content and save structured data", "parameters": { "type": "object", "properties": {} } }, { "name": "take-screenshot", "description": "Take a screenshot of the current page", "parameters": { "type": "object", "properties": { "fullPage": { "type": "boolean", "description": "Whether to capture the full page" } } } } ]; console.log('Using default tools configuration'); } } catch (error) { console.error('Error loading tools:', error); this.tools = []; } } async ensureAgentRunning() { try { if (!this.agentProcess) { await this.startAgent(); } return !!this.agentProcess; } catch (error) { console.error('Error ensuring agent is running:', error); return false; } } async startAgent() { console.log(`Starting Consultant Agent from: ${this.options.agentPath}`); // 验证路径存在 if (!fs.existsSync(this.options.agentPath)) { console.warn(`Agent path not found: ${this.options.agentPath}`); // 尝试查找其他可能的位置 const possiblePaths = [ path.join(__dirname, '../consultant-agent/index.js'), path.join(process.cwd(), 'consultant-agent/index.js') ]; for (const agentPath of possiblePaths) { if (fs.existsSync(agentPath)) { console.log(`Found alternative agent path: ${agentPath}`); this.options.agentPath = agentPath; break; } } if (!fs.existsSync(this.options.agentPath)) { console.error('Could not find Consultant Agent path. Agent will not be started.'); // 返回空Promise,允许服务器继续运行但不启动Agent return Promise.resolve(); } } // 创建日志文件 const logFilePath = path.join(__dirname, '../logs/agent.log'); await fs.promises.mkdir(path.dirname(logFilePath), { recursive: true }); const logFile = fs.openSync(logFilePath, 'a'); // 启动Agent进程 this.agentProcess = spawn('node', [this.options.agentPath], { stdio: ['ignore', logFile, logFile], detached: true }); this.agentProcess.on('close', (code) => { console.log(`Agent process exited with code ${code}`); fs.appendFileSync(logFilePath, `\n[${new Date().toISOString()}] Process exited with code ${code}\n`); this.agentProcess = null; }); console.log(`Consultant Agent started with PID ${this.agentProcess.pid}`); fs.appendFileSync(logFilePath, `\n[${new Date().toISOString()}] Process started with PID ${this.agentProcess.pid}\n`); // 等待短暂时间确保启动 return new Promise(resolve => setTimeout(resolve, 1000)); } async start() { return new Promise((resolve, reject) => { // 启动Express服务器 this.server = this.app.listen(this.options.port, () => { // 获取实际使用的端口(如果指定了0,系统会分配一个可用端口) const port = this.server.address().port; this.actualPort = port; console.log(`Consultant MCP Server running on port ${port}`); console.log('Available endpoints:'); console.log('- GET /health: Check server status'); console.log('- GET /api/discovery: Get tools list (MCP discovery)'); console.log('- POST /api/execute: Execute a tool'); resolve(port); }); this.server.on('error', (err) => { reject(err); }); }); } async stop() { // 停止Agent进程 if (this.agentProcess) { this.agentProcess.kill(); this.agentProcess = null; } // 停止Express服务器 if (this.server) { return new Promise((resolve) => { this.server.close(() => { console.log('Consultant MCP Server stopped'); resolve(); }); }); } } } module.exports = ConsultantMcpServer;