UNPKG

mcp-interactive-feedback-server

Version:

一个简洁高效的MCP服务器,支持AI与用户的实时交互问答

522 lines (457 loc) 18.7 kB
#!/usr/bin/env node const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { z } = require('zod'); const express = require('express'); const WebSocket = require('ws'); const path = require('path'); const fs = require('fs'); // 解析命令行参数 function parseCommandLineArgs() { const args = process.argv.slice(2); // 添加调试信息 console.log('=== 命令行参数调试信息 ==='); console.log('process.argv:', process.argv); console.log('args (slice(2)):', args); // 帮助信息 if (args.includes('--help') || args.includes('-h')) { console.log('使用方法: node index.js [端口号] [选项]'); console.log('选项:'); console.log(' --debug 启用详细调试日志'); console.log('示例: node index.js 9000 --debug'); process.exit(0); } // 检查debug模式 const debugMode = args.includes('--debug'); console.log('debugMode:', debugMode); // 第一个非选项参数作为端口号 const portArg = args.find(arg => !arg.startsWith('--')); console.log('找到的端口参数:', portArg); const port = parseInt(portArg) || 8888; console.log('解析后的端口:', port); console.log('=== 调试信息结束 ==='); return { port, debugMode }; } const cmdArgs = parseCommandLineArgs(); // 配置 const CONFIG = { PORT: cmdArgs.port, DEBUG: cmdArgs.debugMode, LOG_FILE: path.resolve(__dirname, `interactive-feedback-mpc-server-${cmdArgs.port}.log`), HISTORY_FILE: path.resolve(__dirname, `qa_history_${cmdArgs.port}.json`) }; // 统一日志函数 function logMessage(message, level = 'INFO', context = '') { const timestamp = new Date().toISOString(); const contextStr = context ? ` [${context}]` : ''; const logMessage = `[${timestamp}]${contextStr} ${level}: ${message}\n`; // 统一写入到一个日志文件 fs.appendFileSync(CONFIG.LOG_FILE, logMessage); } // 简化的日志函数 function log(message, context = '') { logMessage(message, 'INFO', context); } function debugLog(message, context = '') { if (!CONFIG.DEBUG) return; logMessage(message, 'DEBUG', context); } function errorLog(message, context = '') { logMessage(message, 'ERROR', context); } // 全局状态 const state = { webServer: null, wsServer: null, pendingMessage: null, // 简化为单个等待消息 qaHistory: [] }; // 历史记录管理 const historyManager = { loadHistory() { debugLog('开始加载历史记录', 'History'); try { if (fs.existsSync(CONFIG.HISTORY_FILE)) { debugLog(`历史文件存在: ${CONFIG.HISTORY_FILE}`, 'History'); const data = fs.readFileSync(CONFIG.HISTORY_FILE, 'utf8'); debugLog(`读取到数据长度: ${data.length}`, 'History'); state.qaHistory = JSON.parse(data); debugLog(`解析后的历史记录条数: ${state.qaHistory.length}`, 'History'); } else { debugLog(`历史文件不存在: ${CONFIG.HISTORY_FILE}`, 'History'); state.qaHistory = []; } } catch (error) { errorLog(`加载历史记录失败: ${error.message}`, 'History'); state.qaHistory = []; } debugLog('历史记录加载完成', 'History'); }, saveHistory() { debugLog('开始保存历史记录', 'History'); try { const dataToSave = JSON.stringify(state.qaHistory, null, 2); debugLog(`准备保存的数据长度: ${dataToSave.length}`, 'History'); fs.writeFileSync(CONFIG.HISTORY_FILE, dataToSave); debugLog('历史记录保存成功', 'History'); } catch (error) { errorLog(`保存历史记录失败: ${error.message}`, 'History'); } }, addMessage(type, message) { debugLog(`添加消息到历史记录,类型: ${type},内容: ${message}`, 'History'); const historyItem = { type, // 'ai' 或 'user' message, timestamp: new Date().toISOString() }; debugLog(`创建的历史项: ${JSON.stringify(historyItem)}`, 'History'); state.qaHistory.push(historyItem); debugLog(`历史记录总数: ${state.qaHistory.length}`, 'History'); this.saveHistory(); debugLog('消息已添加到历史记录', 'History'); }, clearHistory() { debugLog('清空历史记录', 'History'); const oldCount = state.qaHistory.length; state.qaHistory = []; debugLog(`已清空${oldCount}条历史记录`, 'History'); this.saveHistory(); debugLog('历史记录清空完成', 'History'); }, getAllHistory() { debugLog(`获取所有历史记录,当前条数: ${state.qaHistory.length}`, 'History'); return state.qaHistory; } }; // Web服务器管理 const webServer = { async start(port) { debugLog(`开始启动Web服务器,端口: ${port}`, 'WebServer'); return new Promise((resolve, reject) => { debugLog(`创建Express应用`, 'WebServer'); const app = express(); debugLog(`配置静态文件目录`, 'WebServer'); app.use(express.static(path.join(__dirname, 'public'))); state.webServer = app.listen(port, (error) => { if (error) { debugLog(`启动失败: ${error.message}`, 'WebServer'); reject(error); return; } log(`Web服务器启动: http://localhost:${port}`, 'WebServer'); // 启动WebSocket state.wsServer = new WebSocket.Server({ server: state.webServer }); debugLog(`WebSocket服务器启动`, 'WebServer'); state.wsServer.on('connection', this.handleConnection); resolve(); }); state.webServer.on('error', reject); }); }, handleConnection(ws) { log('客户端已连接', 'WebSocket'); debugLog('新的WebSocket连接建立', 'WebSocket'); debugLog(`当前连接数: ${state.wsServer.clients.size}`, 'WebSocket'); // 发送历史记录 setTimeout(() => { if (ws.readyState === WebSocket.OPEN) { debugLog('准备发送历史记录给客户端', 'WebSocket'); const history = historyManager.getAllHistory(); debugLog(`历史记录条数: ${history.length}`, 'WebSocket'); ws.send(JSON.stringify({ type: 'history', history: history })); debugLog('历史记录已发送', 'WebSocket'); } else { debugLog('WebSocket连接已关闭,无法发送历史记录', 'WebSocket'); } }, 100); ws.on('message', (message) => { try { debugLog(`收到WebSocket消息: ${message}`, 'WebSocket'); const data = JSON.parse(message); debugLog(`解析后的消息类型: ${data.type}`, 'WebSocket'); if (data.type === 'user_message' && data.message) { // 用户发送消息,记录并通过MCP传递 debugLog(`处理用户消息: ${data.message}`, 'UserMessage'); historyManager.addMessage('user', data.message); log(`收到用户消息: ${data.message}`, 'UserMessage'); debugLog(`当前pendingMessage状态: ${state.pendingMessage ? '存在' : '不存在'}`, 'UserMessage'); // 如果有等待中的MCP请求,直接resolve if (state.pendingMessage) { debugLog(`找到等待中的MCP请求,准备resolve`, 'UserMessage'); debugLog(`正在resolve pendingMessage,消息内容: ${data.message}`, 'UserMessage'); state.pendingMessage.resolve(data.message); debugLog(`pendingMessage.resolve()调用成功`, 'UserMessage'); state.pendingMessage = null; debugLog(`pendingMessage已清空`, 'UserMessage'); } else { debugLog(`警告:收到用户消息但没有等待中的MCP请求`, 'UserMessage'); } // 广播更新历史 debugLog(`准备广播历史更新给所有客户端`, 'UserMessage'); if (state.wsServer) { const clientCount = state.wsServer.clients.size; debugLog(`向${clientCount}个客户端广播历史更新`, 'UserMessage'); state.wsServer.clients.forEach((client, index) => { if (client.readyState === WebSocket.OPEN) { debugLog(`向客户端${index + 1}发送历史更新`, 'UserMessage'); client.send(JSON.stringify({ type: 'history_updated', history: historyManager.getAllHistory() })); } else { debugLog(`客户端${index + 1}连接已关闭,跳过`, 'UserMessage'); } }); } else { debugLog(`警告:wsServer不存在,无法广播`, 'UserMessage'); } } else if (data.type === 'clear_history') { debugLog('处理清空历史记录请求', 'WebSocket'); historyManager.clearHistory(); if (state.wsServer) { state.wsServer.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'history_cleared' })); } }); } debugLog('历史记录已清空并通知所有客户端', 'WebSocket'); } else if (data.type === 'stop_server') { debugLog('收到停止服务器请求', 'WebSocket'); log('收到停止服务器请求', 'WebSocket'); setTimeout(async () => { log('用户请求停止服务器,开始优雅关闭...', 'Main'); try { await gracefulShutdown('USER_REQUEST'); } catch (error) { errorLog(`优雅关闭失败: ${error.message}`, 'Main'); process.exit(1); } }, 1000); } else { debugLog(`未知的消息类型: ${data.type}`, 'WebSocket'); } } catch (error) { errorLog(`处理WebSocket消息时发生错误: ${error.message}`, 'WebSocket'); debugLog(`错误堆栈: ${error.stack}`, 'WebSocket'); } }); ws.on('close', () => { debugLog('WebSocket客户端连接关闭', 'WebSocket'); debugLog(`剩余连接数: ${state.wsServer.clients.size - 1}`, 'WebSocket'); log('客户端已断开', 'WebSocket'); }); ws.on('error', (error) => { errorLog(`WebSocket连接错误: ${error.message}`, 'WebSocket'); }); }, async stop() { return new Promise(resolve => { if (state.wsServer) { state.wsServer.clients.forEach(client => client.close()); state.wsServer.close(() => { if (state.webServer) { state.webServer.close(() => resolve()); } else { resolve(); } }); } else if (state.webServer) { state.webServer.close(() => resolve()); } else { resolve(); } }); } }; // MCP服务器封装 const mcpInstance = { mcpServer: null, transport: null, async start() { this.transport = new StdioServerTransport(); this.mcpServer = new McpServer({ name: "interactive-feedback-server", version: "1.0.3" }, { capabilities: { tools: {} } }); // 使用新的API方式注册工具 this.mcpServer.tool("ask_user_question", { question: z.string().describe("要向用户提出的问题") }, async ({ question }) => { // 记录AI的问题 debugLog(`=== MCP ask_user_question 开始 ===`, 'MCP'); debugLog(`收到问题: ${question}`, 'MCP'); log(`MCP收到ask_user_question请求,问题: ${question}`); debugLog('添加AI消息到历史记录', 'MCP'); historyManager.addMessage('ai', question); debugLog('AI消息已添加到历史记录', 'MCP'); if (state.wsServer) { const clientCount = state.wsServer.clients.size; debugLog(`WebSocket服务器存在,客户端数量: ${clientCount}`, 'MCP'); log(`向${clientCount}个WebSocket客户端发送AI问题`); let sentCount = 0; state.wsServer.clients.forEach((client, index) => { debugLog(`检查客户端${index + 1}状态: ${client.readyState}`, 'MCP'); if (client.readyState === WebSocket.OPEN) { debugLog(`向客户端${index + 1}发送AI问题`, 'MCP'); const messageData = { type: 'ai_message', message: question, history: historyManager.getAllHistory() }; debugLog(`发送的消息数据: ${JSON.stringify(messageData)}`, 'MCP'); client.send(JSON.stringify(messageData)); sentCount++; debugLog(`客户端${index + 1}发送成功`, 'MCP'); } else { debugLog(`客户端${index + 1}连接已关闭,跳过发送`, 'MCP'); } }); debugLog(`实际发送给${sentCount}个客户端`, 'MCP'); } else { debugLog(`警告:WebSocket服务器不存在`, 'MCP'); log(`警告:没有WebSocket服务器实例`); } // 等待用户回复 debugLog(`准备创建Promise等待用户回复`, 'MCP'); log(`创建pendingMessage,等待用户回复...`); const answer = await new Promise((resolve, reject) => { debugLog(`创建pendingMessage对象`, 'MCP'); state.pendingMessage = { resolve, reject }; debugLog(`pendingMessage已设置,resolve函数: ${typeof resolve}, reject函数: ${typeof reject}`, 'MCP'); log(`pendingMessage已创建,开始等待用户回复`); debugLog(`无超时限制,将等待用户回复`, 'MCP'); }); debugLog(`Promise resolved,收到用户回复: ${answer}`, 'MCP'); log(`收到用户回复: ${answer}`); // 用户的回答已经在WebSocket处理时添加到历史记录了,这里不需要重复添加 const result = { content: [ { type: "text", text: answer } ] }; debugLog(`准备返回结果: ${JSON.stringify(result)}`, 'MCP'); debugLog(`=== MCP ask_user_question 结束 ===`, 'MCP'); return result; }); // 连接传输层 await this.mcpServer.connect(this.transport); log('MCP服务器已通过Stdio启动', 'MCP'); }, async stop() { if (this.mcpServer) { log('正在关闭MCP服务器...', 'MCP'); await this.mcpServer.close(); this.mcpServer = null; log('MCP服务器已关闭', 'MCP'); } if (this.transport) { // StdioTransport 可能不需要显式关闭,或者其关闭已集成在mcpServer.close()中 this.transport = null; } } }; async function main() { try { debugLog('=== 应用启动开始 ===', 'Main'); debugLog(`端口: ${CONFIG.PORT}`, 'Main'); debugLog(`调试模式: ${CONFIG.DEBUG ? '启用' : '禁用'}`, 'Main'); debugLog(`日志文件路径: ${CONFIG.LOG_FILE}`, 'Main'); debugLog(`历史文件路径: ${CONFIG.HISTORY_FILE}`, 'Main'); debugLog('开始加载历史记录', 'Main'); historyManager.loadHistory(); debugLog('历史记录加载完成', 'Main'); debugLog('开始启动Web服务器', 'Main'); await webServer.start(CONFIG.PORT); debugLog('Web服务器启动完成', 'Main'); debugLog('开始启动MCP实例', 'Main'); await mcpInstance.start(); debugLog('MCP实例启动完成', 'Main'); log(`应用 "${require('./package.json').name}" 版本 "${require('./package.json').version}" 已成功启动。`, 'Main'); debugLog('设置关闭处理程序', 'Main'); setupShutdownHandlers(); debugLog('=== 应用启动完成 ===', 'Main'); } catch (error) { errorLog(`启动失败: ${error.message}`, 'Main'); debugLog(`错误堆栈: ${error.stack}`, 'Main'); console.error('启动失败:', error); process.exit(1); } } // 优雅关闭处理 function setupShutdownHandlers() { const shutdown = async (signal) => { await gracefulShutdown(signal); }; // 监听退出信号 process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); log('已设置优雅关闭处理程序。', 'Main'); } // 优雅关闭函数(可被信号处理和用户请求共用) async function gracefulShutdown(source) { log(`接收到信号 ${source}。开始关闭服务...`); let exitCode = 0; try { // 1. 关闭MCP服务器 (如果它需要先于WebSocket关闭) await mcpInstance.stop(); // 2. 关闭WebSocket服务器 if (state.wsServer) { log('正在关闭WebSocket服务器...'); await new Promise((resolve, reject) => { state.wsServer.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.terminate(); // 强制关闭客户端连接 } }); state.wsServer.close((err) => { if (err) { log(`关闭WebSocket服务器错误: ${err.message}`); // 不立即将exitCode设为1,继续尝试关闭HTTP服务器 // exitCode = 1; reject(err); // 但promise应该被reject return; } log('WebSocket服务器已关闭。'); state.wsServer = null; resolve(); }); }); } // 3. 关闭HTTP服务器 if (state.webServer) { log('正在关闭HTTP服务器...'); await new Promise((resolve, reject) => { state.webServer.close((err) => { if (err) { log(`关闭HTTP服务器错误: ${err.message}`); // exitCode = 1; // 不立即将exitCode设为1 reject(err); // 但promise应该被reject return; } log('HTTP服务器已关闭。'); state.webServer = null; resolve(); }); }); } } catch (error) { log(`关闭过程中发生错误: ${error.message}`); exitCode = 1; } finally { log(`服务关闭完成,退出码: ${exitCode}`); process.exit(exitCode); } } main();