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
JavaScript
/**
* 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);
});