UNPKG

skilled-feishu-mcp

Version:

A Model Context Protocol (MCP) server that integrates with Feishu's Open Platform APIs

387 lines (318 loc) 11.7 kB
#!/usr/bin/env node /** * 原始stdio模式测试skilled-feishu-mcp * 不使用MCP SDK,而是直接通过子进程通信 */ import { spawn } from 'child_process'; import { performance } from 'perf_hooks'; import fs from 'fs'; // 配置 const IS_DEV_MODE = process.env.NODE_ENV === 'development'; const SERVER_PATH = '/opt/homebrew/bin/skilled-feishu-mcp'; const APP_ID = process.env.FEISHU_APP_ID || 'cli_test_9e11c52b0e1c500e'; const APP_SECRET = process.env.FEISHU_APP_SECRET || 'test_app_secret_for_development'; const TIMEOUT_MS = 30000; // 日志文件 const logFile = fs.createWriteStream('test_raw_mcp.log'); // 性能指标 const perfMetrics = { serverStartup: 0, initializeTime: 0, listToolsTime: 0, totalTime: 0 }; // 请求ID计数器 let requestId = 1; /** * 记录日志 */ function log(message, isError = false) { const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] ${message}`; // 控制台输出 console[isError ? 'error' : 'log'](message); // 写入日志文件 logFile.write(logMessage + '\n'); } /** * 创建MCP协议消息 */ function createMcpMessage(method, params = {}) { // 创建JSON-RPC 2.0格式的消息 const jsonContent = JSON.stringify({ jsonrpc: "2.0", id: requestId++, method, params }); // MCP协议格式: Content-Length头 + 两个CRLF + JSON内容 const contentLength = Buffer.byteLength(jsonContent, 'utf8'); return `Content-Length: ${contentLength}\r\n\r\n${jsonContent}`; } /** * 从带有头部的消息中提取JSON内容 */ function parseJsonFromMessage(message) { // 尝试查找头部和内容分隔符 const parts = message.split('\r\n\r\n'); if (parts.length >= 2) { // 提取内容部分 const content = parts[parts.length - 1]; try { return JSON.parse(content); } catch (e) { log(` 无法解析JSON: ${e.message}`, true); } } // 如果没有找到头部,尝试直接解析整个消息 try { const match = message.match(/\{.*\}/s); if (match) { return JSON.parse(match[0]); } } catch (e) { log(` 无法直接解析JSON: ${e.message}`, true); } return null; } /** * 主测试函数 */ async function main() { log('======================================'); log('原始stdio模式MCP测试'); log('======================================'); log(`测试时间: ${new Date().toLocaleString()}`); log(`环境: ${IS_DEV_MODE ? '开发模式' : '生产模式'}`); log(`服务器路径: ${SERVER_PATH}`); let serverProcess = null; let dataBuffer = ''; try { const startTime = performance.now(); // 1. 启动服务器 log('\n1. 启动服务器进程...'); const serverStartTime = performance.now(); const serverArgs = [ '--stdio', `--feishu-app-id=${APP_ID}`, `--feishu-app-secret=${APP_SECRET}`, '--verbose' ]; if (IS_DEV_MODE) { serverArgs.push('--development'); log(' 添加开发模式参数'); } log(` 完整命令: ${SERVER_PATH} ${serverArgs.join(' ')}`); serverProcess = spawn(SERVER_PATH, serverArgs, { env: process.env, stdio: ['pipe', 'pipe', 'pipe'] }); // 设置编码 serverProcess.stdout.setEncoding('utf8'); serverProcess.stderr.setEncoding('utf8'); // 监听stderr输出 serverProcess.stderr.on('data', (data) => { log(` [stderr] ${data.trim()}`); }); // 监听stdout输出 const startupPromise = new Promise((resolve, reject) => { // 超时处理 const timeout = setTimeout(() => { reject(new Error(`服务器启动超时 (${TIMEOUT_MS}ms)`)); }, TIMEOUT_MS); // 监听输出数据 serverProcess.stdout.on('data', (chunk) => { const chunkStr = chunk.toString(); log(` [接收到原始数据] ${chunk.length} 字节`); log(` [服务器输出] ${chunkStr}`); dataBuffer += chunkStr; // 尝试查找"MCP server started"消息 if (chunkStr.includes('MCP server started')) { clearTimeout(timeout); // 记录服务器启动时间 perfMetrics.serverStartup = performance.now() - serverStartTime; log(` 服务器启动完成 (${perfMetrics.serverStartup.toFixed(2)} ms)`); resolve(); } }); // 监听错误 serverProcess.on('error', (err) => { clearTimeout(timeout); reject(new Error(`服务器进程错误: ${err.message}`)); }); // 监听进程退出 serverProcess.on('exit', (code, signal) => { clearTimeout(timeout); if (code !== 0) { reject(new Error(`服务器进程异常退出,退出码: ${code}, 信号: ${signal}`)); } }); }); // 等待服务器启动 log(' 等待服务器启动...'); await startupPromise; // 额外等待一段时间,确保服务器完全准备好 log(' 额外等待2秒钟,确保服务器完全准备好...'); await new Promise(resolve => setTimeout(resolve, 2000)); // 2. 发送initialize请求 log('\n2. 发送initialize请求...'); const initializeStartTime = performance.now(); const initializeMessage = createMcpMessage('initialize', { client: { name: "MCP Raw Test", version: "1.0.0" }, capabilities: { tools: true, resources: true, prompts: true } }); log(` [发送] initialize请求: ${initializeMessage.length} 字节`); log(` [发送内容] ${initializeMessage}`); serverProcess.stdin.write(initializeMessage); // 等待initialize响应 const initializeResponsePromise = new Promise((resolve, reject) => { // 创建超时处理 const timeout = setTimeout(() => { reject(new Error(`initialize响应超时 (${TIMEOUT_MS}ms)`)); }, TIMEOUT_MS); // 重置数据缓冲区 dataBuffer = ''; // 继续监听stdout const dataListener = (chunk) => { const chunkStr = chunk.toString(); dataBuffer += chunkStr; log(` [接收] 数据: ${chunkStr.length} 字节`); log(` [接收内容] ${chunkStr}`); // 尝试解析响应 if (dataBuffer.includes('"result"') || dataBuffer.includes('"error"')) { clearTimeout(timeout); serverProcess.stdout.removeListener('data', dataListener); // 记录initialize时间 perfMetrics.initializeTime = performance.now() - initializeStartTime; log(` initialize完成 (${perfMetrics.initializeTime.toFixed(2)} ms)`); // 尝试解析JSON响应 const response = parseJsonFromMessage(dataBuffer); if (response) { resolve(response); } else { log(` 无法解析响应,收到的数据: ${dataBuffer}`, true); resolve({ status: 'parse_error', rawData: dataBuffer }); } } }; serverProcess.stdout.on('data', dataListener); }); // 等待initialize响应 try { const initializeResponse = await initializeResponsePromise; log(` initialize响应: ${JSON.stringify(initializeResponse)}`); } catch (error) { log(` initialize失败: ${error.message}`, true); throw error; } // 3. 发送listTools请求 log('\n3. 发送listTools请求...'); const listToolsStartTime = performance.now(); const listToolsMessage = createMcpMessage('listTools', {}); log(` [发送] listTools请求: ${listToolsMessage.length} 字节`); log(` [发送内容] ${listToolsMessage}`); serverProcess.stdin.write(listToolsMessage); // 等待listTools响应 const listToolsResponsePromise = new Promise((resolve, reject) => { // 创建超时处理 const timeout = setTimeout(() => { reject(new Error(`listTools响应超时 (${TIMEOUT_MS}ms)`)); }, TIMEOUT_MS); // 重置数据缓冲区 dataBuffer = ''; // 监听stdout const dataListener = (chunk) => { const chunkStr = chunk.toString(); dataBuffer += chunkStr; log(` [接收] 数据: ${chunkStr.length} 字节`); log(` [接收内容] ${chunkStr}`); // 尝试解析响应 if (dataBuffer.includes('"result"') || dataBuffer.includes('"error"')) { clearTimeout(timeout); serverProcess.stdout.removeListener('data', dataListener); // 记录listTools时间 perfMetrics.listToolsTime = performance.now() - listToolsStartTime; log(` listTools完成 (${perfMetrics.listToolsTime.toFixed(2)} ms)`); // 尝试解析JSON响应 const response = parseJsonFromMessage(dataBuffer); if (response) { resolve(response); } else { log(` 无法解析响应,收到的数据: ${dataBuffer}`, true); resolve({ status: 'parse_error', rawData: dataBuffer }); } } }; serverProcess.stdout.on('data', dataListener); }); // 等待listTools响应 try { const listToolsResponse = await listToolsResponsePromise; // 简要展示工具列表 if (listToolsResponse.result && listToolsResponse.result.tools) { const tools = listToolsResponse.result.tools; log(` 发现 ${tools.length} 个工具`); if (tools.length > 0) { log(' 工具列表:'); tools.slice(0, 5).forEach(tool => { log(` - ${tool.name}: ${tool.description || '无描述'}`); }); if (tools.length > 5) { log(` - ... 另外 ${tools.length - 5} 个工具`); } } } else { log(` listTools响应: ${JSON.stringify(listToolsResponse)}`); } } catch (error) { log(` listTools失败: ${error.message}`, true); throw error; } // 4. 发送shutdown请求 log('\n4. 发送shutdown请求...'); const shutdownMessage = createMcpMessage('shutdown', {}); log(` [发送] shutdown请求: ${shutdownMessage.length} 字节`); log(` [发送内容] ${shutdownMessage}`); serverProcess.stdin.write(shutdownMessage); // 总计时间 perfMetrics.totalTime = performance.now() - startTime; // 性能报告 log('\n======================================'); log('性能报告'); log('======================================'); log(`服务器启动时间: ${perfMetrics.serverStartup.toFixed(2)} ms`); log(`initialize请求时间: ${perfMetrics.initializeTime.toFixed(2)} ms`); log(`listTools请求时间: ${perfMetrics.listToolsTime.toFixed(2)} ms`); log(`总计时间: ${perfMetrics.totalTime.toFixed(2)} ms`); log('\n✅ 测试成功完成!'); } catch (error) { log(`\n❌ 测试失败: ${error.message}`, true); if (error.stack) { log(`\n堆栈跟踪:\n${error.stack}`, true); } process.exit(1); } finally { // 终止服务器进程 if (serverProcess && !serverProcess.killed) { log('\n清理: 终止服务器进程...'); serverProcess.kill('SIGTERM'); } // 关闭日志文件 logFile.end(); } } // 执行主测试函数 main().catch(error => { log(`未捕获错误: ${error.message}`, true); if (error.stack) { log(`堆栈跟踪:\n${error.stack}`, true); } process.exit(1); });