UNPKG

pomdp-mcp-server

Version:

MCP服务器实现POMDP研发任务拆解

258 lines 10.4 kB
import express from 'express'; import path from 'path'; import fs from 'fs'; import { randomUUID } from 'crypto'; import { pomdpTaskDecomposer } from './pomdp-decomposer.js'; import { z } from 'zod'; import dotenv from 'dotenv'; // 采用标准ESM导入语法 // 导入MCP SDK import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; // 导入自定义logger,在stdio模式下禁用普通日志输出 import { logger } from '../bin/logger.js'; // 加载环境变量 dotenv.config(); // 配置日志文件 const logDir = path.resolve(process.cwd(), '..'); const logFile = path.join(logDir, 'mcp-server.log'); // 创建自定义日志函数 const logToFileAndConsole = (message) => { const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] ${message}\n`; // 输出到控制台,但在stdio模式下不输出 logger.log(message); // 输出到文件(始终执行,与运行模式无关) fs.appendFileSync(logFile, logMessage); }; // 清空之前的日志文件 fs.writeFileSync(logFile, `[${new Date().toISOString()}] === MCP服务器日志开始 ===\n`); logToFileAndConsole('日志文件已创建: ' + logFile); // 创建Express应用 const app = express(); app.use(express.json()); // 添加JSON解析中间件 app.use(express.urlencoded({ extended: true })); // 创建MCP服务器实例 const mcpServer = new McpServer({ name: "pomdp-task-decomposer", version: "1.0.0", // 身份验证函数,可以使用环境变量中的密钥 authenticate: async (auth) => { const secretKey = process.env.MCP_SECRET_KEY || 'default-secret-key'; logToFileAndConsole(`收到身份验证请求: ${JSON.stringify(auth)}`); if (auth?.bearer === secretKey) { logToFileAndConsole('身份验证成功: authenticated-user'); return { userId: 'authenticated-user' }; } logToFileAndConsole('使用匿名身份: anonymous-user'); return { userId: 'anonymous-user' }; } }); // 注册POMDP任务分解资源 - 使用字符串URI模板 mcpServer.registerResource("task-decompose", "task:///decompose/{task}", // URI模板格式 // 资源元数据 { title: "POMDP任务分解器", description: "使用POMDP方法分解研发任务描述" }, // 资源处理函数 async (uri, extra) => { try { // 从查询参数或URI路径中获取任务描述 const taskDescription = extra.question || uri.pathname.slice(1) || ""; if (!taskDescription) { throw new Error("请提供任务描述"); } logToFileAndConsole(`处理任务分解请求: ${taskDescription.substring(0, 100)}...`); // 使用POMDP分解任务 const decompositionResult = await pomdpTaskDecomposer(taskDescription); // 返回结果 return { contents: [ { uri: uri.href, text: decompositionResult, contentType: 'text/markdown' } ] }; } catch (error) { logToFileAndConsole(`处理资源请求时出错: ${error instanceof Error ? error.message : String(error)}`); throw error; } }); // 注册POMDP任务分解工具 // 使用与Sdk示例相同的格式注册工具 mcpServer.tool('decompose-task', '使用POMDP方法将研发任务分解为详细计划', { taskDescription: z.string().describe('需要分解的研发任务描述'), }, // 工具处理函数 async (params) => { const { taskDescription } = params; try { // 已从参数中获取任务描述 logToFileAndConsole(`处理工具调用: decompose-task, 描述: ${taskDescription.substring(0, 100)}...`); // 使用POMDP分解任务 const result = await pomdpTaskDecomposer(taskDescription); return { content: [ { type: "text", text: result, contentType: "text/markdown" } ] }; } catch (error) { console.error('处理工具请求时出错:', error); return { content: [ { type: "text", text: `错误: ${error instanceof Error ? error.message : String(error)}` } ] }; } }); // 存储活跃的会话信息 const activeSessions = new Map(); // 使用构造函数创建传输层实例 const transport = new StreamableHTTPServerTransport({ // 生成唯一会话ID sessionIdGenerator: () => { const sessionId = randomUUID(); logToFileAndConsole(`生成新会话ID: ${sessionId}`); return sessionId; }, enableJsonResponse: true, // 使用JSON响应而不是SSE流 // 会话初始化回调 onsessioninitialized: (sessionId) => { logToFileAndConsole(`新会话已初始化: ${sessionId}`); // 记录新会话 activeSessions.set(sessionId, { id: sessionId, createdAt: new Date(), lastAccessedAt: new Date(), requestCount: 0 }); logToFileAndConsole(`当前活跃会话数: ${activeSessions.size}`); } // 移除不支持的perstrategy参数 }); // 添加健康检查端点 app.get('/health', (req, res) => { res.status(200).send('服务运行正常'); }); // 添加调试端点来查看当前活跃会话 app.get('/debug/sessions', (req, res) => { const sessions = Array.from(activeSessions.entries()).map(([id, session]) => ({ id, createdAt: session.createdAt, lastAccessedAt: session.lastAccessedAt, requestCount: session.requestCount })); res.json({ totalSessions: activeSessions.size, sessions }); logToFileAndConsole(`已请求会话调试信息,当前有 ${activeSessions.size} 个活跃会话`); }); // 定义MCP请求处理函数 const mcpHandler = (req, res) => { const sessionId = req.headers['mcp-session-id']; const requestBody = req.body; const method = requestBody?.method || '未知方法'; const id = requestBody?.id || '无ID'; // 记录详细请求信息 logToFileAndConsole(`收到MCP请求: ${req.method} ${req.path} Method=${method} ID=${id}`); logToFileAndConsole(`请求头: ${JSON.stringify(req.headers)}`); logToFileAndConsole(`请求体: ${JSON.stringify(requestBody, null, 2)}`); // 跟踪会话状态 if (sessionId && activeSessions.has(sessionId)) { const session = activeSessions.get(sessionId); if (session) { session.lastAccessedAt = new Date(); session.requestCount += 1; logToFileAndConsole(`更新了会话 ${sessionId} 信息,请求次数: ${session.requestCount}`); } } else if (method === 'mcp/initialize') { logToFileAndConsole(`收到初始化请求,将创建新会话`); } else if (!sessionId) { logToFileAndConsole(`警告: 收到没有会话ID的非初始化请求`); } else { logToFileAndConsole(`错误: 收到未知会话ID: ${sessionId}`); } // 添加响应结束监听器来记录响应情况 const oldSend = res.send; res.send = function (body) { // 记录返回数据 if (body) { try { const responseStr = typeof body === 'object' ? JSON.stringify(body) : String(body); logToFileAndConsole(`返回数据: ${responseStr}`); // 特别检测和记录初始化错误 if (responseStr.includes('Server already initialized')) { logToFileAndConsole(`检测到 'Server already initialized' 错误,请求ID: ${id}`); logToFileAndConsole(`错误上下文 - 请求方法: ${method}, 会话ID: ${sessionId || '无'}`); logToFileAndConsole(`错误请求头: ${JSON.stringify(req.headers, null, 2)}`); } } catch (e) { logToFileAndConsole(`无法解析响应数据: ${e}`); } } logToFileAndConsole(`请求完成: ${req.method} ${req.path}`); return oldSend.apply(res, [body]); }; // 捕获错误 const oldStatus = res.status; res.status = function (code) { logToFileAndConsole(`响应状态码: ${code}`); return oldStatus.call(res, code); }; // 将请求传递给transport处理 transport.handleRequest(req, res, requestBody); }; // 根据运行模式选择不同的传输层 const transportMode = process.env.MCP_TRANSPORT_MODE || 'http'; logToFileAndConsole(`启动MCP服务器,传输模式: ${transportMode}`); if (transportMode === 'stdio') { // 使用官方 StdioServerTransport 创建 stdio 传输层实例 // 按照官方文档实现,无需提供自定义处理函数 const stdioTransport = new StdioServerTransport(); // 连接MCP服务器到stdio传输层 // 这会自动处理 init 和 tool list 等标准 MCP 请求 mcpServer.connect(stdioTransport).then(() => { logToFileAndConsole('MCP服务器已连接到stdio传输层,使用官方 StdioServerTransport,等待请求...'); logToFileAndConsole('stdio 模式已准备好处理 init 和 tool list 等 MCP 请求'); }).catch((err) => { logToFileAndConsole(`连接MCP服务器到stdio传输层失败: ${err.message}`); process.exit(1); }); } else { // HTTP模式 - 使用原有的HTTP传输层 mcpServer.connect(transport).then(() => { // 注册MCP路由处理器 app.post('/mcp', mcpHandler); // 处理POST请求 app.get('/mcp', mcpHandler); // 处理GET请求的SSE流 // 启动HTTP服务器 const PORT = process.env.PORT || 3000; app.listen(PORT, () => { logToFileAndConsole(`MCP服务器运行在端口 ${PORT}`); logToFileAndConsole(`MCP端点: http://localhost:${PORT}/mcp`); }); }).catch((err) => { logToFileAndConsole(`启动MCP服务器失败: ${err.message}`); process.exit(1); }); } //# sourceMappingURL=mcp-server.js.map