consultant-mcp-server
Version:
MCP Server for Enterprise Architecture Consultants
388 lines (351 loc) • 12.1 kB
JavaScript
/**
* 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;