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