mcp-interactive-feedback-server
Version:
一个简洁高效的MCP服务器,支持AI与用户的实时交互问答
522 lines (457 loc) • 18.7 kB
JavaScript
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();