mcp.test.js•8.84 kB
// mcp-test.js - MCP测试脚本
import { spawn } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 检查服务器文件是否存在
const SERVER_PATH = path.join(__dirname, '../server.js');
if (!fs.existsSync(SERVER_PATH)) {
throw new Error(`找不到服务器文件: ${SERVER_PATH}`);
}
// 生成请求ID
function generateId() {
return Math.random().toString(36).substring(2, 10);
}
// 发送请求并返回响应的Promise
function sendRequest(server, request) {
return new Promise((resolve, reject) => {
let responseData = '';
const onData = (data) => {
responseData += data;
try {
// 尝试解析JSON响应
const response = JSON.parse(responseData);
removeListeners();
resolve(response);
} catch (e) {
// 不是完整的JSON,继续等待更多数据
}
};
const onError = (err) => {
// 这里不需要处理,因为服务器错误日志不影响测试
// 但为了调试可以开启
// console.log('服务器日志:', err.toString().slice(0, 50) + '...');
};
const removeListeners = () => {
server.stdout.removeListener('data', onData);
server.stderr.removeListener('data', onError);
};
server.stdout.on('data', onData);
server.stderr.on('data', onError);
// 发送请求
server.stdin.write(JSON.stringify(request) + '\n');
// 设置超时,避免永久等待
setTimeout(() => {
removeListeners();
reject(new Error('请求超时'));
}, 5000);
});
}
// Jest测试用例
describe('MCP服务器测试', () => {
let server;
let docSource = 'taro'; // 根据docs目录内容设置默认源
let docId; // 存储搜索结果中的文档ID
// 在所有测试前启动服务器
beforeAll(() => {
console.log(`启动服务器: ${SERVER_PATH}`);
server = spawn('node', [SERVER_PATH]);
server.stdout.setEncoding('utf8');
server.stderr.setEncoding('utf8');
// 忽略服务器stdout和stderr输出
server.stderr.on('data', () => {});
server.stdout.on('data', () => {});
// 等待服务器启动
return new Promise(resolve => setTimeout(resolve, 3000));
}, 15000);
// 测试初始化
test('初始化MCP服务器', async () => {
// 创建初始化请求
const request = {
jsonrpc: "2.0",
id: generateId(),
method: "initialize",
params: {
protocolVersion: "2024-11-05",
capabilities: {},
clientInfo: {
name: "doc-test-client",
version: "1.0.0"
}
}
};
const response = await sendRequest(server, request);
expect(response).toBeDefined();
expect(response.jsonrpc).toBe('2.0');
expect(response.id).toBe(request.id);
expect(response.error).toBeUndefined();
expect(response.result).toBeDefined();
expect(response.result.serverInfo.name).toBe("文档 MCP 服务器");
}, 10000);
// 测试搜索功能
test('调用搜索工具', async () => {
try {
// 根据server.js创建正确的搜索请求,使用taro文档源
const request = {
jsonrpc: "2.0",
id: generateId(),
method: "tools/call",
params: {
name: "search_docs",
arguments: {
query: "组件",
source: docSource,
limit: 5
}
}
};
const response = await sendRequest(server, request);
expect(response).toBeDefined();
expect(response.jsonrpc).toBe('2.0');
expect(response.id).toBe(request.id);
// 处理可能的错误
if (response.error) {
console.log(`搜索功能返回错误: ${response.error.message}`);
// 设置一个默认的文档ID,用于继续测试
docId = `https://taro-docs.jd.com/taro/docs/components/viewContainer/view`;
return;
}
expect(response.result).toBeDefined();
// 解析结果中的文档ID
const content = response.result?.content;
expect(content).toBeDefined();
expect(Array.isArray(content)).toBe(true);
// 从结果中提取第一个文档ID
let searchResults;
for (const item of content) {
if (item.type === 'text' && item.text) {
try {
searchResults = JSON.parse(item.text);
break;
} catch (e) {
// 不是JSON格式,继续下一项
}
}
}
expect(searchResults).toBeDefined();
if (searchResults.error) {
console.log(`搜索结果包含错误: ${searchResults.error}`);
// 设置一个默认的文档ID,用于继续测试
docId = `https://taro-docs.jd.com/taro/docs/components/viewContainer/view`;
return;
}
expect(searchResults.results).toBeDefined();
expect(Array.isArray(searchResults.results)).toBe(true);
if (searchResults.results.length === 0) {
console.log(`搜索结果为空`);
// 设置一个默认的文档ID,用于继续测试
docId = `https://taro-docs.jd.com/taro/docs/components/viewContainer/view`;
return;
}
// 保存第一个结果的ID,用于后续详情测试
docId = searchResults.results[0].id;
console.log(`获取到文档ID: ${docId}`);
} catch (error) {
console.error(`搜索功能测试出错: ${error ? error.message : '未知错误'}`);
// 设置一个默认的文档ID,用于继续测试
docId = `https://taro-docs.jd.com/taro/docs/components/viewContainer/view`;
}
}, 10000);
// 测试详情功能
test('调用文档详情工具', async () => {
// 确保docId有值
if (!docId) {
docId = `https://taro-docs.jd.com/taro/docs/components/viewContainer/view`;
console.log(`使用默认文档ID: ${docId}`);
}
expect(docId).toBeDefined();
try {
// 根据server.js创建正确的文档详情请求
const request = {
jsonrpc: "2.0",
id: generateId(),
method: "tools/call",
params: {
name: "get_doc_detail",
arguments: {
id: docId,
source: docSource
}
}
};
const response = await sendRequest(server, request);
expect(response).toBeDefined();
expect(response.jsonrpc).toBe('2.0');
expect(response.id).toBe(request.id);
// 处理可能的错误
if (response.error) {
console.log(`详情功能返回错误: ${response.error.message}`);
return;
}
expect(response.result).toBeDefined();
// 解析结果,确认成功获取文档详情
const content = response.result?.content;
expect(content).toBeDefined();
expect(Array.isArray(content)).toBe(true);
let docDetail;
for (const item of content) {
if (item.type === 'text' && item.text) {
try {
docDetail = JSON.parse(item.text);
break;
} catch (e) {
// 不是JSON格式,继续下一项
}
}
}
expect(docDetail).toBeDefined();
if (docDetail.error) {
console.log(`文档详情包含错误: ${docDetail.error}`);
return;
}
expect(docDetail.success).toBe(true);
expect(docDetail.document).toBeDefined();
expect(docDetail.document.id).toBe(docId);
console.log(`成功获取文档详情: ${docDetail.document.title}`);
} catch (error) {
console.error(`详情功能测试出错: ${error ? error.message : '未知错误'}`);
}
}, 10000);
// 在所有测试后关闭服务器
afterAll(() => {
if (server) {
console.log('关闭服务器进程...');
// 直接发送SIGTERM信号强制终止进程
server.kill('SIGTERM');
// 添加确认关闭的日志
server.on('close', (code) => {
console.log(`服务器进程退出,退出码 ${code}`);
});
// 设置超时检查
return new Promise((resolve) => {
// 如果5秒后进程仍未退出,强制退出
const timeout = setTimeout(() => {
console.log('服务器进程超时未退出,强制终止');
server.kill('SIGKILL');
resolve();
}, 1000);
// 进程退出后,清除超时并resolve
server.on('close', () => {
clearTimeout(timeout);
resolve();
});
});
}
}, 5000);
});