pomdp-mcp-server
Version:
MCP服务器实现POMDP研发任务拆解
258 lines • 10.4 kB
JavaScript
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