UNPKG

skilled-feishu-mcp

Version:

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

282 lines (236 loc) 7.19 kB
#!/usr/bin/env node /** * MCP协议直接通信测试脚本 * 完全遵循MCP协议规范进行通信 */ import { spawn } from 'child_process'; 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 = 15000; // 日志文件 const logFile = fs.createWriteStream('mcp_protocol_test.log'); // MCP请求ID计数器 let requestIdCounter = 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 = {}) { const jsonContent = JSON.stringify({ jsonrpc: "2.0", id: requestIdCounter++, method, params }); // MCP协议格式: Content-Length头 + 两个CRLF + JSON内容 const contentLength = Buffer.byteLength(jsonContent, 'utf8'); return `Content-Length: ${contentLength}\r\n\r\n${jsonContent}`; } /** * 协议消息解析器 */ class MessageParser { constructor() { this.buffer = ''; this.contentLength = -1; this.messages = []; } append(data) { this.buffer += data; this.parse(); } parse() { while (true) { // 还没有读取到Content-Length if (this.contentLength === -1) { const headerEnd = this.buffer.indexOf('\r\n\r\n'); if (headerEnd === -1) { return; // 头部不完整,等待更多数据 } const header = this.buffer.substring(0, headerEnd); const match = /Content-Length: (\d+)/i.exec(header); if (!match) { log('无法解析Content-Length', true); this.buffer = this.buffer.substring(headerEnd + 4); continue; } this.contentLength = parseInt(match[1], 10); this.buffer = this.buffer.substring(headerEnd + 4); } // 检查消息体是否完整 if (this.buffer.length >= this.contentLength) { const content = this.buffer.substring(0, this.contentLength); this.buffer = this.buffer.substring(this.contentLength); this.contentLength = -1; try { const message = JSON.parse(content); this.messages.push(message); } catch (e) { log(`解析JSON失败: ${e.message}`, true); } } else { return; // 消息体不完整,等待更多数据 } } } hasMessages() { return this.messages.length > 0; } getNextMessage() { if (this.messages.length === 0) { return null; } return this.messages.shift(); } } /** * 主函数 */ async function main() { log('======================================'); log('MCP协议通信测试'); log('======================================'); log(`时间: ${new Date().toLocaleString()}`); log(`开发模式: ${IS_DEV_MODE ? '是' : '否'}`); log(`服务器路径: ${SERVER_PATH}`); log('\n1. 准备启动参数...'); const args = [ '--stdio', `--feishu-app-id=${APP_ID}`, `--feishu-app-secret=${APP_SECRET}`, '--verbose' ]; if (IS_DEV_MODE) { args.push('--development'); log(' 添加开发模式参数'); } const fullCommand = `${SERVER_PATH} ${args.join(' ')}`; log(` 完整命令: ${fullCommand}`); log('\n2. 启动MCP服务器进程...'); const mcpProcess = spawn(SERVER_PATH, args, { stdio: ['pipe', 'pipe', 'pipe'], env: process.env }); // 设置编码 mcpProcess.stdin.setDefaultEncoding('utf8'); mcpProcess.stdout.setEncoding('utf8'); mcpProcess.stderr.setEncoding('utf8'); // 创建消息解析器 const parser = new MessageParser(); // 监听stdout(JSON-RPC消息) mcpProcess.stdout.on('data', (data) => { log(` [收到原始数据] 长度: ${data.length}字节`); parser.append(data); // 处理所有完整的消息 while (parser.hasMessages()) { const message = parser.getNextMessage(); log(` [收到消息] ${JSON.stringify(message)}`); } }); // 监听stderr(日志输出) mcpProcess.stderr.on('data', (data) => { const lines = data.toString().trim().split('\n'); lines.forEach(line => { if (line.trim()) { log(` [stderr] ${line}`); } }); }); // 监听进程退出 mcpProcess.on('exit', (code, signal) => { log(` 进程退出 - 代码: ${code}, 信号: ${signal}`, code !== 0); }); // 监听进程错误 mcpProcess.on('error', (err) => { log(` 进程错误: ${err.message}`, true); }); log('\n3. 等待服务器初始化...'); // 等待服务器准备就绪 await new Promise(resolve => setTimeout(resolve, 2000)); // 设置总超时 const timeout = setTimeout(() => { log('操作超时', true); cleanup(); process.exit(1); }, TIMEOUT_MS); try { log('\n4. 发送initialize请求...'); const initRequest = createMcpMessage('initialize', { client: { name: 'MCP Protocol Test', version: '1.0.0' }, capabilities: { tools: true, resources: true, prompts: true } }); log(` 请求消息结构: ${initRequest.replace(/\r\n/g, '\\r\\n')}`); mcpProcess.stdin.write(initRequest); // 等待响应 await new Promise(resolve => setTimeout(resolve, 2000)); log('\n5. 发送listTools请求...'); const toolsRequest = createMcpMessage('listTools', {}); log(` 请求消息结构: ${toolsRequest.replace(/\r\n/g, '\\r\\n')}`); mcpProcess.stdin.write(toolsRequest); // 等待响应 await new Promise(resolve => setTimeout(resolve, 2000)); log('\n6. 发送shutdown请求...'); const shutdownRequest = createMcpMessage('shutdown'); log(` 请求消息结构: ${shutdownRequest.replace(/\r\n/g, '\\r\\n')}`); mcpProcess.stdin.write(shutdownRequest); // 等待响应处理 await new Promise(resolve => setTimeout(resolve, 2000)); log('\n✅ 测试完成'); clearTimeout(timeout); } catch (error) { clearTimeout(timeout); log(`\n❌ 测试失败: ${error.message}`, true); } finally { cleanup(); } /** * 清理资源 */ function cleanup() { log('\n7. 清理资源...'); try { // 尝试优雅关闭 if (!mcpProcess.killed) { mcpProcess.stdin.end(); setTimeout(() => { if (!mcpProcess.killed) { mcpProcess.kill(); } }, 500); } } catch (err) { log(` 清理错误: ${err.message}`, true); } // 关闭日志文件 logFile.end(); } } // 执行主函数 main().catch(err => { log(`未捕获错误: ${err.message}`, true); if (err.stack) { log(err.stack, true); } process.exit(1); });