MCP测试套件
 
模型上下文协议(MCP)服务器的综合测试框架
](https://www.npmjs.com/package/@crashbytes/mcp-test-kit) 
📖 目录
🤔 什么是MCP?
模型上下文协议(MCP) 是由Anthropic开发的一种开放协议,它使像Claude这样的人工智能助手能够安全地与外部工具、数据源和服务进行交互。将其视为AI模型的一种标准化方式:
- 访问工具:执行搜索数据库、调用API或运行计算等功能
- 阅读资源:从文件、数据库或外部服务获取数据
- 使用提示:利用预构建的提示模板完成常见任务
现实世界MCP示例
以下是您可以构建的一些实用的MCP服务器:
- 天气服务器:提供任何城市的当前天气数据
- 数据库服务器:允许查询PostgreSQL/MySQL数据库
- 文件系统服务器:允许读取/写入磁盘上的文件
- API集成服务器:连接到第三方API(GitHub、Slack等)
- 计算器服务器:执行复杂的数学运算
MCP的工作原理
graph LR
A[Claude
Client] |MCP| B[MCP Server
Your Code]
B C[Your Data
or Service]MCP服务器充当Claude和您的数据/服务之间的桥梁,通过标准化协议公开它们。
🎯 什么是MCP测试套件?
MCP测试套件 是一个测试框架,可以轻松为MCP服务器编写自动化测试。它提供:
🔧 核心功能
- MCPTestClient:连接到MCP服务器并调用其工具/资源的测试客户端
- 定制Vitest配对器:用于验证MCP响应的专门断言
- 模拟服务器:创建伪造的MCP服务器以测试客户端代码
- TypeScript支持:完整的类型定义,以获得更好的IDE支持和类型安全
🎪 它解决的问题
没有MCP测试套件,测试MCP服务器具有挑战性:
- ❌ 您需要手动启动服务器并与之交互
- ❌ 没有标准化的方法来断言MCP特定的行为
- ❌ 集成测试的复杂设置
- ❌ 难以测试的错误条件和边缘情况
使用MCP测试套件:
- ✅ 在测试中以编程方式启动和停止服务器
- ✅ 使用直观的匹配器,如
toBeValidMCPTool()和toMatchMCPToolResponse() - ✅ 在几分钟内编写全面的集成测试
- ✅ 轻松测试错误处理和边缘情况
💡 为什么使用MCP测试套件?
面向MCP服务器开发人员
如果您正在构建MCP服务器,MCP测试套件可以帮助您:
- 验证正确性:确保您的服务器正确实现MCP协议
- 测试工具:验证您的工具是否返回预期结果
- 测试资源:确认资源可访问并返回正确的数据
- 测试错误处理:验证错误响应是否符合MCP标准
- 回归测试:在bug进入生产环境之前将其捕获
- 文档:测试是服务器行为的实时文档
示例用例
假设你构建了一个气象MCP服务器。使用MCP测试套件,您可以编写以下测试:
it('should return weather data for San Francisco', async () => {
const result = await client.callTool('get-weather', {
city: 'San Francisco'
});
expect(result).toMatchMCPToolResponse();
expect(result.content[0].text).toContain('temperature');
expect(result.content[0].text).toContain('San Francisco');
});
it('should handle invalid city names', async () => {
await expect(
client.callTool('get-weather', { city: '' })
).rejects.toMatchMCPError({
code: -32602,
message: /invalid.*city/i
});
});📦 安装
# Using npm
npm install --save-dev @crashbytes/mcp-test-kit
# Using yarn
yarn add -D @crashbytes/mcp-test-kit
# Using pnpm
pnpm add -D @crashbytes/mcp-test-kit先决条件
- Node.js 18+
- 测试框架(Vitest推荐)
- TypeScript(可选,但推荐)
🚀 快速开始
1.创建您的第一个测试
// tests/weather-server.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createMCPTestClient } from '@crashbytes/mcp-test-kit';
import type { MCPTestClient } from '@crashbytes/mcp-test-kit';
import '@crashbytes/mcp-test-kit/matchers';
describe('Weather MCP Server', () => {
let client: MCPTestClient;
beforeAll(async () => {
// Connect to your MCP server
client = await createMCPTestClient({
command: 'node',
args: ['dist/server.js'],
});
});
afterAll(async () => {
await client.disconnect();
});
it('should list available tools', async () => {
const tools = await client.listTools();
expect(tools).toHaveLength(1);
expect(tools[0]).toBeValidMCPTool();
expect(tools[0].name).toBe('get-weather');
});
it('should get weather for a city', async () => {
const result = await client.callTool('get-weather', {
city: 'London',
});
expect(result).toMatchMCPToolResponse();
expect(result.content[0].text).toContain('London');
});
});2.运行测试
npm test就是这样!您现在正在测试MCP服务器。
🧩 核心概念
MCPTestClient
这 MCPTestClient 是测试MCP服务器的主界面。它
- 将MCP服务器作为子进程生成
- 使用MCP协议建立连接
- 提供调用工具、列出资源等的方法。
- 测试完成后处理清理
关键方法:
listTools():从服务器获取所有可用工具callTool(name, args):使用参数执行工具listResources():获取所有可用资源readResource(uri):阅读特定资源listPrompts():获取所有可用提示getPrompt(name, args):获取特定提示
定制配对器
MCP测试套件为MCP特定的断言提供自定义Vitest匹配器:
toBeValidMCPTool()
验证对象是否是格式正确的MCP工具:
const tool = {
name: 'calculate',
description: 'Performs calculations',
inputSchema: {
type: 'object',
properties: {
expression: { type: 'string' }
}
}
};
expect(tool).toBeValidMCPTool();toBeValidMCPResource()
验证对象是否是格式正确的MCP资源:
const resource = {
uri: 'file:///data/users.json',
name: 'Users Database',
description: 'List of all users'
};
expect(resource).toBeValidMCPResource();toMatchMCPToolResponse()
验证工具响应的结构:
const response = await client.callTool('get-weather', { city: 'Paris' });
expect(response).toMatchMCPToolResponse();
expect(response.content[0].text).toContain('Paris');toMatchMCPError(error)
验证MCP错误响应:
await expect(
client.callTool('invalid-tool', {})
).rejects.toMatchMCPError({
code: -32601, // Method not found
message: /not found/i
});toHaveMCPProtocolVersion(version)
验证MCP协议版本:
const info = await client.getServerInfo();
expect(info).toHaveMCPProtocolVersion('2024-11-05');📚 API 参考
createMCPTestClient(配置)
创建并连接到MCP测试客户端。
参数:
interface MCPTestClientConfig {
command: string; // Command to run (e.g., 'node', 'python')
args?: string[]; // Arguments for the command
env?: Record; // Environment variables
timeout?: number; // Timeout in milliseconds (default: 5000)
transport?: 'stdio'; // Transport type (only stdio supported)
debug?: boolean; // Enable debug logging
}退货: Promise
例子:
const client = await createMCPTestClient({
command: 'node',
args: ['dist/server.js'],
env: {
NODE_ENV: 'test',
DATABASE_URL: 'sqlite::memory:'
},
timeout: 10000,
debug: true
});MCPTestClient方法
listTools(): Promise
列出MCP服务器上可用的所有工具。
const tools = await client.listTools();
console.log(tools[0].name); // 'get-weather'callTool(name: string, args?: object): Promise
使用给定的参数执行工具。
const result = await client.callTool('calculate', {
expression: '2 + 2'
});listResources(): Promise
列出MCP服务器上可用的所有资源。
const resources = await client.listResources();
console.log(resources[0].uri); // 'file:///data/users.json'readResource(uri: string): Promise
读取特定资源的内容。
const content = await client.readResource('file:///data/users.json');
console.log(content.text);listPrompts(): Promise
列出MCP服务器上可用的所有提示。
const prompts = await client.listPrompts();getPrompt(name: string, args?: Record): Promise
获取带有参数的特定提示。
const prompt = await client.getPrompt('code-review', {
language: 'typescript'
});disconnect(): Promise
断开与MCP服务器的连接并清理资源。
await client.disconnect();💻 例子
示例1:测试计算器服务器
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createMCPTestClient, MCPTestClient } from '@crashbytes/mcp-test-kit';
import '@crashbytes/mcp-test-kit/matchers';
describe('Calculator MCP Server', () => {
let client: MCPTestClient;
beforeAll(async () => {
client = await createMCPTestClient({
command: 'node',
args: ['dist/calculator-server.js'],
});
});
afterAll(async () => {
await client.disconnect();
});
describe('Basic Operations', () => {
it('should add two numbers', async () => {
const result = await client.callTool('calculate', {
operation: 'add',
a: 5,
b: 3
});
expect(result).toMatchMCPToolResponse();
expect(result.content[0].text).toBe('8');
});
it('should handle division by zero', async () => {
await expect(
client.callTool('calculate', {
operation: 'divide',
a: 10,
b: 0
})
).rejects.toMatchMCPError({
code: -32602,
message: /division by zero/i
});
});
});
describe('Tool Validation', () => {
it('should have valid tool schema', async () => {
const tools = await client.listTools();
const calcTool = tools.find(t => t.name === 'calculate');
expect(calcTool).toBeValidMCPTool();
expect(calcTool?.inputSchema.properties).toHaveProperty('operation');
expect(calcTool?.inputSchema.properties).toHaveProperty('a');
expect(calcTool?.inputSchema.properties).toHaveProperty('b');
});
});
});示例2:测试数据库服务器
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createMCPTestClient, MCPTestClient } from '@crashbytes/mcp-test-kit';
import '@crashbytes/mcp-test-kit/matchers';
describe('Database MCP Server', () => {
let client: MCPTestClient;
beforeAll(async () => {
client = await createMCPTestClient({
command: 'node',
args: ['dist/db-server.js'],
env: {
DATABASE_URL: 'sqlite::memory:',
NODE_ENV: 'test'
}
});
});
afterAll(async () => {
await client.disconnect();
});
it('should query users from database', async () => {
const result = await client.callTool('query', {
sql: 'SELECT * FROM users WHERE age > 18'
});
expect(result).toMatchMCPToolResponse();
const data = JSON.parse(result.content[0].text);
expect(Array.isArray(data)).toBe(true);
});
it('should list database resources', async () => {
const resources = await client.listResources();
expect(resources.length).toBeGreaterThan(0);
resources.forEach(resource => {
expect(resource).toBeValidMCPResource();
});
});
it('should read table schema', async () => {
const content = await client.readResource('schema://users');
expect(content.text).toContain('id');
expect(content.text).toContain('name');
expect(content.text).toContain('email');
});
});示例3:使用模拟进行测试
import { describe, it, expect } from 'vitest';
import { createMockMCPServer, mockTool } from '@crashbytes/mcp-test-kit/mocks';
describe('Mock MCP Server', () => {
it('should create a mock server with tools', async () => {
const server = createMockMCPServer({
tools: [
mockTool(
'greet',
async (args) => ({
content: [{ type: 'text', text: `Hello, ${args.name}!` }]
}),
{
description: 'Greets a person',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string' }
}
}
}
)
]
});
const tools = server.getTools();
expect(tools).toHaveLength(1);
expect(tools[0].name).toBe('greet');
const result = await server.callTool('greet', { name: 'World' });
expect(result.content[0].text).toBe('Hello, World!');
});
});🎓 最佳实践
1.使用before All/after All进行连接管理
let client: MCPTestClient;
beforeAll(async () => {
client = await createMCPTestClient({ /* config */ });
});
afterAll(async () => {
await client.disconnect();
});2.测试成功和失败案例
// Test success
it('should return data for valid input', async () => {
const result = await client.callTool('tool', { valid: true });
expect(result).toMatchMCPToolResponse();
});
// Test failure
it('should reject invalid input', async () => {
await expect(
client.callTool('tool', { invalid: true })
).rejects.toMatchMCPError({ code: -32602 });
});3.验证工具模式
it('should have properly defined tools', async () => {
const tools = await client.listTools();
tools.forEach(tool => {
expect(tool).toBeValidMCPTool();
expect(tool.description).toBeTruthy();
expect(tool.inputSchema.properties).toBeDefined();
});
});4.使用描述性测试名称
// ❌ Bad
it('test 1', async () => { /* ... */ });
// ✅ Good
it('should return weather data for valid city names', async () => { /* ... */ });5.隔离测试
每个测试都应该是独立的,不依赖于其他测试的状态。
// ❌ Bad - Tests depend on order
it('creates a user', async () => { /* ... */ });
it('updates the user', async () => { /* assumes user exists */ });
// ✅ Good - Each test is independent
beforeEach(async () => {
await client.callTool('reset-database', {});
});
it('creates a user', async () => { /* ... */ });
it('updates a user', async () => {
await client.callTool('create-user', { name: 'Test' });
await client.callTool('update-user', { name: 'Updated' });
});🤝 贡献
欢迎投稿!请随时提交拉取请求。
开发设置
# Clone the repository
git clone https://github.com/crashbytes/mcp-test-kit.git
cd mcp-test-kit
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Run linter
npm run lint📄 许可证
MIT© 黑洞软件有限责任公司
🔗 链接
- NPM包: https://www.npmjs.com/package/@crashbytes/mcp测试套件
- GitHub: https://github.com/crashbytes/mcp-test-kit
- MCP规范: https://spec.modelcontextprotocol.io/
- CrashBytes博客: https://crashbytes.com
🙏 致谢
______________________________________________________________________
建于❤️ 通过 CrashBytes
