UNPKG

dpml-prompt

Version:

DPML-powered AI prompt framework - Revolutionary AI-First CLI system based on Deepractice Prompt Markup Language. Build sophisticated AI agents with structured prompts, memory systems, and execution frameworks.

614 lines (534 loc) 18.3 kB
const express = require('express'); const { randomUUID } = require('node:crypto'); const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { SSEServerTransport } = require('@modelcontextprotocol/sdk/server/sse.js'); const { isInitializeRequest } = require('@modelcontextprotocol/sdk/types.js'); const { cli } = require('../core/pouch'); const { MCPOutputAdapter } = require('../mcp/MCPOutputAdapter'); const { getToolDefinitions, getToolDefinition, getToolCliConverter } = require('../mcp/toolDefinitions'); const ProjectManager = require('../utils/ProjectManager'); const { getGlobalProjectManager } = require('../utils/ProjectManager'); const { getGlobalServerEnvironment } = require('../utils/ServerEnvironment'); const logger = require('../utils/logger'); /** * MCP HTTP Server Command * 实现基于 HTTP 协议的 MCP 服务器 * 支持 Streamable HTTP 和 SSE 两种传输方式 */ class MCPServerHttpCommand { constructor() { this.name = 'promptx-mcp-streamable-http-server'; this.version = '1.0.0'; this.transport = 'http'; this.port = 3000; this.host = 'localhost'; this.transports = {}; // 存储会话传输 this.outputAdapter = new MCPOutputAdapter(); this.debug = process.env.MCP_DEBUG === 'true'; } /** * 执行命令 */ async execute(options = {}) { const { transport = 'http', port = 3000, host = 'localhost' } = options; // 🚀 初始化ServerEnvironment - 在所有逻辑之前装配服务环境 const serverEnv = getGlobalServerEnvironment(); serverEnv.initialize({ transport, host, port }); // 验证传输类型 if (!['http', 'sse'].includes(transport)) { throw new Error(`Unsupported transport: ${transport}`); } // 验证配置 this.validatePort(port); this.validateHost(host); if (transport === 'http') { return this.startStreamableHttpServer(port, host); } else if (transport === 'sse') { return this.startSSEServer(port, host); } } /** * 启动 Streamable HTTP 服务器 */ async startStreamableHttpServer(port, host) { this.log(`🚀 启动 Streamable HTTP MCP Server...`); const app = express(); // 中间件设置 app.use(express.json()); app.use(this.corsMiddleware.bind(this)); // 健康检查端点 app.get('/health', (req, res) => { res.json({ status: 'ok', name: this.name, version: this.version, transport: 'http' }); }); // OAuth 支持端点 (简化实现) app.get('/.well-known/oauth-authorization-server', this.handleOAuthMetadata.bind(this)); app.get('/.well-known/openid-configuration', this.handleOAuthMetadata.bind(this)); app.post('/register', this.handleDynamicRegistration.bind(this)); app.get('/authorize', this.handleAuthorize.bind(this)); app.post('/token', this.handleToken.bind(this)); // MCP 端点 app.post('/mcp', this.handleMCPPostRequest.bind(this)); app.get('/mcp', this.handleMCPGetRequest.bind(this)); app.delete('/mcp', this.handleMCPDeleteRequest.bind(this)); // 错误处理中间件 app.use(this.errorHandler.bind(this)); return new Promise((resolve, reject) => { const server = app.listen(port, host, () => { this.log(`✅ Streamable HTTP MCP Server 运行在 http://${host}:${port}`); this.server = server; resolve(server); }); server.on('error', reject); }); } /** * CORS 中间件 */ corsMiddleware(req, res, next) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, mcp-session-id'); if (req.method === 'OPTIONS') { res.sendStatus(200); return; } next(); } /** * 错误处理中间件 */ errorHandler(error, req, res, next) { this.log('Express 错误处理:', error); if (!res.headersSent) { // 检查是否是JSON解析错误 if (error.type === 'entity.parse.failed' || error.message?.includes('JSON')) { res.status(400).json({ jsonrpc: '2.0', error: { code: -32700, message: 'Parse error: Invalid JSON' }, id: null }); } else { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null }); } } } /** * 启动 SSE 服务器 */ async startSSEServer(port, host) { const app = express(); app.use(express.json()); this.log(`🚀 启动 SSE MCP Server...`); // 健康检查端点 app.get('/health', (req, res) => { res.json({ status: 'ok', name: this.name, version: this.version, transport: 'sse' }); }); // SSE 端点 - 建立事件流 app.get('/mcp', async (req, res) => { await this.handleSSEConnection(req, res); }); // 消息端点 - 接收客户端 JSON-RPC 消息 app.post('/messages', async (req, res) => { await this.handleSSEMessage(req, res); }); return new Promise((resolve, reject) => { const server = app.listen(port, host, () => { this.log(`✅ SSE MCP Server 运行在 http://${host}:${port}`); resolve(server); }); server.on('error', reject); this.server = server; }); } /** * 处理 SSE 连接建立 */ async handleSSEConnection(req, res) { this.log('建立 SSE 连接'); try { // 创建 SSE 传输 const transport = new SSEServerTransport('/messages', res); const sessionId = transport.sessionId; // 存储传输 this.transports[sessionId] = transport; // 设置关闭处理程序 transport.onclose = () => { this.log(`SSE 传输关闭: ${sessionId}`); delete this.transports[sessionId]; }; // 连接到 MCP 服务器 const server = this.setupMCPServer(); await server.connect(transport); this.log(`SSE 流已建立,会话ID: ${sessionId}`); } catch (error) { this.log('建立 SSE 连接错误:', error); if (!res.headersSent) { res.status(500).send('Error establishing SSE connection'); } } } /** * 处理 SSE 消息 */ async handleSSEMessage(req, res) { this.log('收到 SSE 消息:', req.body); try { // 从查询参数获取会话ID const sessionId = req.query.sessionId; if (!sessionId) { res.status(400).send('Missing sessionId parameter'); return; } const transport = this.transports[sessionId]; if (!transport) { res.status(404).send('Session not found'); return; } // 处理消息 await transport.handlePostMessage(req, res, req.body); } catch (error) { this.log('处理 SSE 消息错误:', error); if (!res.headersSent) { res.status(500).send('Error handling request'); } } } /** * 设置 MCP 服务器 - 使用与 stdio 模式完全相同的低级 API */ setupMCPServer() { const server = new Server({ name: this.name, version: this.version }, { capabilities: { tools: {} } }); // ✨ 使用与 stdio 模式相同的低级 API 注册处理器 this.setupMCPHandlers(server); return server; } /** * 设置 MCP 处理器 - 与 stdio 模式完全一致的实现 */ setupMCPHandlers(server) { const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js'); // 注册工具列表处理程序 - 与 stdio 模式完全相同 server.setRequestHandler(ListToolsRequestSchema, async () => { this.log('📋 收到工具列表请求'); return { tools: this.getToolDefinitions() // ✅ 直接返回完整工具定义 }; }); // 注册工具调用处理程序 - 与 stdio 模式完全相同 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; this.log(`🔧 调用工具: ${name} 参数: ${JSON.stringify(args)}`); console.log(`🔧 [强制调试] 工具: ${name} 正确参数: ${JSON.stringify(args)}`); return await this.callTool(name, args || {}); }); } /** * 获取工具定义 */ getToolDefinitions() { return getToolDefinitions(); } /** * 处理 MCP POST 请求 */ async handleMCPPostRequest(req, res) { this.log('收到 MCP 请求:', req.body); try { // 检查现有会话 ID const sessionId = req.headers['mcp-session-id']; let transport; if (sessionId && this.transports[sessionId]) { // 复用现有传输 transport = this.transports[sessionId]; } else if (!sessionId && isInitializeRequest(req.body)) { // 新的初始化请求 transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), enableJsonResponse: true, onsessioninitialized: (sessionId) => { this.log(`会话初始化: ${sessionId}`); this.transports[sessionId] = transport; } }); // 设置关闭处理程序 transport.onclose = () => { const sid = transport.sessionId; if (sid && this.transports[sid]) { this.log(`传输关闭: ${sid}`); delete this.transports[sid]; } }; // 连接到 MCP 服务器 const server = this.setupMCPServer(); await server.connect(transport); await transport.handleRequest(req, res, req.body); return; } else if (!sessionId && this.isStatelessRequest(req.body)) { // 无状态请求(如 tools/list, prompts/list 等)- 使用官方推荐方式 console.log(`🎯 [官方模式] 无状态请求: ${req.body.method}`); try { const server = this.setupMCPServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // 无状态模式 enableJsonResponse: true }); // 请求结束时清理资源 res.on('close', () => { console.log('🧹 清理无状态请求资源'); transport.close && transport.close(); server.close && server.close(); }); await server.connect(transport); await transport.handleRequest(req, res, req.body); return; } catch (error) { console.error('🔥 无状态请求处理错误:', error); throw error; } } else if (sessionId && !this.transports[sessionId] && this.isStatelessRequest(req.body)) { // 🔧 修复:sessionId已失效但是无状态请求,可以处理 console.log(`🔄 [修复模式] Session已失效,转为无状态处理: ${req.body.method}`); try { const server = this.setupMCPServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // 无状态模式 enableJsonResponse: true }); await server.connect(transport); await transport.handleRequest(req, res, req.body); return; } catch (error) { console.error('🔥 Session修复模式处理错误:', error); throw error; } } else { // 无效请求 - 只有真正无法处理的情况才报错 return res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: `Bad Request: ${sessionId ? 'Invalid session ID' : 'No valid session ID provided'}. Method: ${req.body?.method || 'unknown'}` }, id: req.body?.id || null }); } // 处理现有传输的请求 await transport.handleRequest(req, res, req.body); } catch (error) { this.log('处理 MCP 请求错误:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: req.body?.id || null }); } } } /** * 处理 MCP GET 请求(SSE) */ async handleMCPGetRequest(req, res) { const sessionId = req.headers['mcp-session-id']; if (!sessionId || !this.transports[sessionId]) { return res.status(400).json({ error: 'Invalid or missing session ID' }); } this.log(`建立 SSE 流: ${sessionId}`); const transport = this.transports[sessionId]; await transport.handleRequest(req, res); } /** * 处理 MCP DELETE 请求(会话终止) */ async handleMCPDeleteRequest(req, res) { const sessionId = req.headers['mcp-session-id']; if (!sessionId || !this.transports[sessionId]) { return res.status(400).json({ error: 'Invalid or missing session ID' }); } this.log(`终止会话: ${sessionId}`); try { const transport = this.transports[sessionId]; await transport.handleRequest(req, res); } catch (error) { this.log('处理会话终止错误:', error); if (!res.headersSent) { res.status(500).json({ error: 'Error processing session termination' }); } } } /** * 调用工具 */ async callTool(toolName, args) { try { // 将 MCP 参数转换为 CLI 函数调用参数 console.log(`🎯 [强制调试] 收到MCP参数: ${JSON.stringify(args)}`); const cliArgs = this.convertMCPToCliParams(toolName, args); console.log(`🎯 [强制调试] 转换后CLI参数: ${JSON.stringify(cliArgs)}`); this.log(`🎯 CLI调用: ${toolName} -> ${JSON.stringify(cliArgs)}`); // 直接调用 PromptX CLI 函数 this.log(`🎯 传递给CLI的参数: ${JSON.stringify(cliArgs)}`); const result = await cli.execute(toolName.replace('promptx_', ''), cliArgs, true); this.log(`✅ CLI执行完成: ${toolName}`); // 使用输出适配器转换为MCP响应格式(与stdio模式保持一致) return this.outputAdapter.convertToMCPFormat(result); } catch (error) { this.log(`❌ 工具调用失败: ${toolName} - ${error.message}`); return this.outputAdapter.handleError(error); } } /** * 转换 MCP 参数为 CLI 函数调用参数 - 使用统一转换逻辑 */ convertMCPToCliParams(toolName, mcpArgs) { const converter = getToolCliConverter(toolName); if (!converter) { throw new Error(`未知工具: ${toolName}`); } return converter(mcpArgs || {}); } /** * 调试日志 */ log(message, ...args) { if (this.debug) { logger.debug(`[MCP DEBUG] ${message}`, ...args); } } /** * 验证端口号 */ validatePort(port) { if (typeof port !== 'number') { throw new Error('Port must be a number'); } if (port < 1 || port > 65535) { throw new Error('Port must be between 1 and 65535'); } } /** * 验证主机地址 */ validateHost(host) { if (!host || typeof host !== 'string' || host.trim() === '') { throw new Error('Host cannot be empty'); } } /** * 判断是否为无状态请求(不需要会话ID) */ isStatelessRequest(requestBody) { if (!requestBody || !requestBody.method) { return false; } // 这些方法可以无状态处理 - 按照官方标准扩展支持所有工具调用 const statelessMethods = [ 'tools/list', 'prompts/list', 'resources/list', 'tools/call' // ✨ 添加工具调用支持无状态模式 ]; return statelessMethods.includes(requestBody.method); } /** * OAuth 元数据端点 - 简化实现 */ handleOAuthMetadata(req, res) { const baseUrl = `http://${req.get('host')}`; res.json({ issuer: baseUrl, authorization_endpoint: `${baseUrl}/authorize`, token_endpoint: `${baseUrl}/token`, registration_endpoint: `${baseUrl}/register`, response_types_supported: ["code"], grant_types_supported: ["authorization_code"], code_challenge_methods_supported: ["S256"], client_registration_types_supported: ["dynamic"] }); } /** * 动态客户端注册 - 简化实现 */ handleDynamicRegistration(req, res) { // 简化实现:直接返回一个客户端ID const clientId = `promptx-client-${Date.now()}`; const baseUrl = `http://${req.get('host')}`; res.json({ client_id: clientId, client_secret: "not-required", // 简化实现 registration_access_token: `reg-token-${Date.now()}`, registration_client_uri: `${baseUrl}/register/${clientId}`, client_id_issued_at: Math.floor(Date.now() / 1000), client_secret_expires_at: 0, // 永不过期 redirect_uris: [ `${baseUrl}/callback`, "urn:ietf:wg:oauth:2.0:oob" ], response_types: ["code"], grant_types: ["authorization_code"], token_endpoint_auth_method: "none" }); } /** * OAuth 授权端点 - 简化实现 */ handleAuthorize(req, res) { // 简化实现:直接返回授权码 const code = `auth-code-${Date.now()}`; const baseUrl = `http://${req.get('host')}`; const redirectUri = req.query.redirect_uri || `${baseUrl}/callback`; res.redirect(`${redirectUri}?code=${code}&state=${req.query.state || ''}`); } /** * OAuth 令牌端点 - 简化实现 */ handleToken(req, res) { // 简化实现:直接返回访问令牌 res.json({ access_token: `access-token-${Date.now()}`, token_type: "Bearer", expires_in: 3600, scope: "mcp" }); } } module.exports = { MCPServerHttpCommand };