mcp开发工具包
](https://www.npmjs.com/package/mcp-dev-kit)  
模型上下文协议(MCP)服务器的完整测试和调试工具包
使用全面的测试工具、智能快照测试和stdio安全调试日志构建可靠的MCP服务器。
为什么选择mcp开发工具包?
开发MCP服务器面临着独特的挑战:
- ❌ 测试很难 -MCP服务器没有内置测试实用程序
- ❌ 快照中断 -每次运行时,时间戳和ID都会发生变化
- ❌ 日志记录中断stdio -
console.log()破坏JSON-RPC通信 - ❌ 手动断言 -用于常见检查的重复样板
mcp-dev-kit解决了所有这些问题:
- ✅ MCPTestClient -MCP服务器的全功能测试客户端
- ✅ 智能快照 -自动排除动态字段(时间戳、ID)
- ✅ 定制火柴 -工具、资源、提示的可读断言
- ✅ 安全记录 -调试时不破坏JSON-RPC协议
快速开始
安装
npm install --save-dev mcp-dev-kit vitest基本测试设置
1.创建 vitest.setup.ts:
import { installMCPMatchers } from 'mcp-dev-kit/matchers';
installMCPMatchers();2.配置 vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./vitest.setup.ts'],
},
});3.编写测试(server.test.ts):
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { MCPTestClient } from 'mcp-dev-kit/client';
describe('My MCP Server', () => {
let client: MCPTestClient;
beforeAll(async () => {
client = new MCPTestClient({
command: 'node',
args: ['./my-server.js'],
});
await client.connect();
});
afterAll(async () => {
await client.disconnect();
});
it('should list available tools', async () => {
const tools = await client.listTools();
expect(tools).toHaveLength(2);
expect(tools[0]).toHaveToolProperty('name', 'echo');
});
it('should execute tools successfully', async () => {
const result = await client.callTool('echo', { message: 'hello' });
await expect(result).toReturnToolResult('hello');
});
it('should have stable response structure', async () => {
const result = await client.callTool('list_files', { path: '/' });
expect(result).toMatchToolResponseSnapshot();
});
});4.将调试日志添加到服务器:
// At the top of your MCP server file
import 'mcp-dev-kit/logger';
// Now console.log works without breaking JSON-RPC!
console.log('Server started');
console.error('Connection error', error);5.运行测试:
npx vitest特性
MCP测试客户端
通过stdio生成和测试MCP服务器的综合测试客户端。
import { MCPTestClient } from 'mcp-dev-kit/client';
const client = new MCPTestClient({
command: 'node',
args: ['./my-server.js'],
env: { DEBUG: 'true' },
timeout: 30000,
});
await client.connect();
// Test server capabilities
const serverInfo = client.getServerInfo();
const capabilities = client.getServerCapabilities();
// List and call tools
const tools = await client.listTools();
const result = await client.callTool('my-tool', { param: 'value' });
// List and read resources
const resources = await client.listResources();
const content = await client.readResource('file://config.json');
// List and get prompts
const prompts = await client.listPrompts();
const prompt = await client.getPrompt('greeting', { name: 'Alice' });
// Helper methods for common patterns
const toolResult = await client.expectToolCallSuccess('my-tool', { input: 'test' });
const error = await client.expectToolCallError('bad-tool', {});
await client.disconnect();主要特点:
- 自动流程生命周期管理
- 请求/响应与超时匹配
- 服务器通知处理
- 全面的错误处理
- TypeScript优先,具有完全类型安全性
定制Vitest配对器
用于MCP特定测试的可读、富有表现力的断言。
import { installMCPMatchers } from 'mcp-dev-kit/matchers';
installMCPMatchers();可用匹配器:
// Tool assertions
await expect(client).toHaveTool('echo');
const tools = await client.listTools();
expect(tools[0]).toHaveToolProperty('description', 'Echoes back the message');
expect(tools[0]).toMatchToolSchema({
type: 'object',
required: ['message']
});
// Resource assertions
await expect(client).toHaveResource('config://app.json');
const resources = await client.listResources();
expect(resources[0]).toHaveProperty('uri', 'config://app.json');
// Prompt assertions
await expect(client).toHavePrompt('greeting');
const prompts = await client.listPrompts();
expect(prompts[0]).toHaveProperty('name', 'greeting');
// Tool result assertions
const result = await client.callTool('echo', { message: 'test' });
await expect(result).toReturnToolResult('test');
await expect(client.callTool('unknown', {})).toThrowToolError();优点:
- 清晰、自文档化的测试代码
- 测试失败时更好的错误消息
- 减少测试文件中的样板
- 使用TypeScript进行类型安全
快照测试
MCP感知快照测试,具有智能现场排除功能。
为什么要对MCP进行快照测试?
MCP服务器响应通常包含每次运行时都会发生变化的动态数据:
- 时间戳(
2024-11-03T10:30:00.000Z) - 请求ID(
abc123) - 执行时间(
42.5ms) - 自动递增ID、文件索引节点、git SHA
每次运行时,常规快照测试都会失败。 mcp-devkit会自动排除这些字段 同时捕获稳定的响应结构。
快速示例
import { installMCPMatchers } from 'mcp-dev-kit/matchers';
installMCPMatchers();
describe('File System Server', () => {
it('should return consistent file listing structure', async () => {
const result = await client.callTool('list_files', { path: '/project' });
// Timestamps, IDs, and dynamic fields automatically excluded!
expect(result).toMatchToolResponseSnapshot();
});
it('should have stable tool definitions', async () => {
const tools = await client.listTools();
// Captures tool schemas for regression detection
expect(tools).toMatchToolListSnapshot();
});
it('should snapshot custom data structures', async () => {
const data = {
users: [...],
timestamp: new Date().toISOString(), // Auto-excluded
requestId: 'abc123', // Auto-excluded
};
expect(data).toMatchMCPSnapshot();
});
});可用的快照匹配器
toMatchMCPSnapshot(options?) -任何MCP数据的通用快照匹配器
expect(serverResponse).toMatchMCPSnapshot();
expect(data).toMatchMCPSnapshot({ exclude: ['user.id', 'files.*.size'] });toMatchToolResponseSnapshot(options?) -工具调用结果
const result = await client.callTool('query_database', { query: 'SELECT * FROM orders' });
expect(result).toMatchToolResponseSnapshot();toMatchToolListSnapshot(options?) -关于工具定义
const tools = await client.listTools();
expect(tools).toMatchToolListSnapshot();toMatchResourceListSnapshot(options?) -关于资源列表
const resources = await client.listResources();
expect(resources).toMatchResourceListSnapshot();toMatchPromptListSnapshot(options?) -用于快速定义
const prompts = await client.listPrompts();
expect(prompts).toMatchPromptListSnapshot();智能默认值
这些字段是 自动排除 从所有快照中:
timestamprequestIdexecutionTimecacheKey_meta.timestampserverInfo.startedAtserverInfo.uptime
例子:
// Original response
{
"users": [...],
"timestamp": "2024-11-03T10:30:00.000Z", // ❌ Excluded
"requestId": "abc123", // ❌ Excluded
"executionTime": 42.5 // ❌ Excluded
}
// Snapshot (only stable data)
{
"users": [...] // ✅ Captured
}自定义排除
使用glob模式排除其他字段:
// Exclude file system-specific fields
expect(result).toMatchToolResponseSnapshot({
exclude: ['files.*.size', 'files.*.inode', 'files.*.modified']
});
// Exclude all auto-increment IDs
expect(data).toMatchMCPSnapshot({
exclude: ['*.id', '*.userId', 'rows.*.orderId']
});
// Exclude nested timestamps with custom names
expect(response).toMatchMCPSnapshot({
exclude: ['data.users.*.createdAt', 'metadata.generatedAt']
});模式语法:
field-不包括顶级字段nested.field-排除嵌套字段array.*.field-从所有数组项中排除字段data.users.*.createdAt-不包括createdAt来自所有用户data.users
演出
带有属性排除的快照测试增加的开销可以忽略不计:
| 数据大小 | 标准化开销 | 备注 |
|---|---|---|
| 10-100个项目 | \ { |
const result = await client.callTool('list_users', {}); const parsed = JSON.parse(result.content[0]?.text || '{}');
// Explicit assertions for critical properties expect(parsed.users).toHaveLength(50); expect(parsed.users[0]).toHaveProperty('name'); expect(parsed.users[0]).toHaveProperty('email');
// Snapshot for structure regression detection expect(result).toMatchToolResponseSnapshot(); });
看 [示例/快照示例/](./examples/snapshot-example/) 查看带有基准的完整工作示例。
### 调试日志记录
不会中断JSON-RPC stdio通信的安全调试日志记录。
#### 问题
MCP服务器通过stdio上的JSON-RPC进行通信。每条消息必须是stdout上的一行JSON:
{"jsonrpc":"2.0","method":"tools/list","params":{...}}\n
如果你在stdout中写任何其他东西(比如 `console.log()`),它腐蚀了溪流:
Server starting... ← Breaks protocol! {"jsonrpc":"2.0","method":"tools/list","params":{...}}\n
结果: `SyntaxError: Unexpected token 'S'`
#### 解决方案
**mcp-devkit将所有控制台输出重定向到stderr**,保持stdout干净:
- **标准输出** =纯JSON-RPC(协议)
- **标准错误** =所有日志(调试)
#### 自动修补程序(推荐)
// At the top of your MCP server file import 'mcp-dev-kit/logger';
// Now console.log works without breaking JSON-RPC! console.log('Server started', { port: 3000 }); console.info('Configuration loaded'); console.warn('Deprecated feature used'); console.error('Connection failed', error);
**选择退出:**
MCP_DEV_KIT_NO_AUTO_PATCH=true node server.js
#### 手动记录仪
为了获得更多控制,请创建一个自定义记录器实例:
import { createLogger } from 'mcp-dev-kit';
const logger = createLogger({ timestamps: true, colors: true, level: 'info', // Only show info and above logFile: './server.log', // Optional file output });
logger.info('Server starting...'); logger.warn('Configuration may need updating'); logger.error('Connection failed', { reason: 'timeout' });
// Cleanup when done await logger.close();
#### 记录器功能
- **自动修补** -只需导入并整合.log即可
- **彩色输出** -颜色编码日志级别(自动检测TTY)
- **时间戳** -所有日志上的ISO8601时间戳
- **对象格式化** -漂亮的打印对象 `util.inspect()`
- **文件日志记录** -可选异步文件输出
- **清理** -对原始控制台的优雅修复
- **零开销** -轻量级,使用微微色(7 KB)
#### 配置
**日志级别:**
const logger = createLogger({ level: 'warn' });
logger.debug('Not shown'); logger.info('Not shown'); logger.warn('Shown'); // ✓ logger.error('Shown'); // ✓
**颜色:**
createLogger({ colors: false }); // Force disable createLogger({ colors: true }); // Force enable // Auto-detected by default based on process.stderr.isTTY
**时间戳:**
createLogger({ timestamps: false }); // Disable // ISO8601 format: 2024-11-03T12:34:56.789Z
**文件日志记录:**
const logger = createLogger({ logFile: './server.log', });
logger.info('This goes to both stderr and server.log');
// Flush pending writes await logger.close();
## 测试指南
### 测试结构
按MCP功能组织测试:
describe('My MCP Server', () => { let client: MCPTestClient;
beforeAll(async () => { client = new MCPTestClient({ command: 'node', args: ['./server.js'] }); await client.connect(); });
afterAll(async () => { await client.disconnect(); });
describe('Server Initialization', () => { it('should expose correct server info', () => { const info = client.getServerInfo(); expect(info.name).toBe('my-server'); expect(info.version).toBe('1.0.0'); });
it('should declare required capabilities', () => { const caps = client.getServerCapabilities(); expect(caps.tools).toBeDefined(); }); });
describe('Tools', () => { it('should list all available tools', async () => { const tools = await client.listTools(); expect(tools).toHaveLength(3); expect(tools.map(t => t.name)).toEqual(['echo', 'calculate', 'search']); });
it('should execute tools successfully', async () => { const result = await client.callTool('echo', { message: 'test' }); expect(result.content[0]?.text).toBe('test'); });
it('should handle tool errors gracefully', async () => { const error = await client.expectToolCallError('calculate', { invalid: 'params' }); expect(error.message).toContain('Invalid parameters'); });
it('should have stable tool schemas', async () => { const tools = await client.listTools(); expect(tools).toMatchToolListSnapshot(); }); });
describe('Resources', () => { it('should list available resources', async () => { await expect(client).toHaveResource('config://app.json'); });
it('should read resource content', async () => { const content = await client.readResource('config://app.json'); expect(content.contents[0]?.text).toContain('version'); }); });
describe('Prompts', () => { it('should provide defined prompts', async () => { await expect(client).toHavePrompt('greeting'); });
it('should render prompts with arguments', async () => { const prompt = await client.getPrompt('greeting', { name: 'Alice' }); expect(prompt.messages[0]?.content.text).toContain('Alice'); }); }); });
### 测试最佳实践
**✅ 做:**
1. **测试所有MCP功能** -工具、资源、提示
1. **使用描述性测试名称** -明确说明正在测试的内容
1. **结合匹配和快照** -显式断言+结构验证
1. **测试错误案例** -不要只测试快乐的道路
1. **清理资源** -始终断开客户端连接 `afterAll`
1. **适当使用超时** -为慢速操作设置合理的超时
1. **测试服务器生命周期** -测试初始化和关闭
**❌ 不要:**
1. **不共享客户端状态** -每个测试套件都应该有自己的客户端
1. **不要跳过错误测试** -错误处理至关重要
1. **不测试实现细节** -仅测试公共API
1. **不要创建不稳定的测试** -避免依赖时间的断言
1. **不要忽略快照** -仔细查看快照更改
1. **不要硬编码系统特定的路径** -使用相对路径或环境变量
### 错误测试
始终测试错误条件:
it('should validate tool parameters', async () => { const error = await client.expectToolCallError('calculate', { // Missing required parameter }); expect(error.code).toBe(-32602); // Invalid params expect(error.message).toContain('Required parameter'); });
it('should handle resource not found', async () => { await expect( client.readResource('nonexistent://resource') ).rejects.toThrow('Resource not found'); });
it('should reject unknown tools', async () => { await expect( client.callTool('unknown-tool', {}) ).rejects.toThrow(); });
### 性能测试
关键操作的测试响应时间:
it('should respond quickly to tool calls', async () => { const start = Date.now(); await client.callTool('quick-operation', {}); const duration = Date.now() - start;
expect(duration).toBeLessThan(1000); // { // 1. List available tools const tools = await client.listTools(); expect(tools.length).toBeGreaterThan(0);
// 2. Get resource for context const config = await client.readResource('config://app.json'); const settings = JSON.parse(config.contents[0]?.text || '{}');
// 3. Execute tool with context const result = await client.callTool('process', { mode: settings.defaultMode, }); expect(result.content[0]?.text).toBeTruthy();
// 4. Verify result structure expect(result).toMatchToolResponseSnapshot(); });
## 例子
看 [示例/](./examples/) 完整示例目录:
- **[快照示例/](./examples/snapshot-example/)** -使用基准完成快照测试
- **[记录器/基本用法.ts](./examples/logger/basic-usage.ts)** -自动补丁控制台
- **[记录器/手动设置.ts](./examples/logger/manual-setup.ts)** -自定义记录器实例
- **[logger/file-loging.ts](./examples/logger/file-logging.ts)** -记录到文件
- **[logger/mcp-server-example.ts](./examples/logger/mcp-server-example.ts)** -真正的MCP服务器
运行示例:
npm install -g tsx tsx examples/logger/basic-usage.ts
## API 参考
### MCPTestClient
class MCPTestClient { constructor(options: { command: string; args?: string[]; env?: Record; timeout?: number; });
// Connection management connect(): Promise; disconnect(): Promise;
// Server info getServerInfo(): ServerInfo; getServerCapabilities(): ServerCapabilities;
// Tools listTools(): Promise; callTool(name: string, args: unknown): Promise; expectToolCallSuccess(name: string, args: unknown): Promise; expectToolCallError(name: string, args: unknown): Promise;
// Resources listResources(): Promise; readResource(uri: string): Promise;
// Prompts listPrompts(): Promise ; getPrompt(name: string, args?: unknown): Promise; }
### 日志记录器
interface LoggerOptions { enabled?: boolean; // Enable/disable logger (default: true) timestamps?: boolean; // Show timestamps (default: true) colors?: boolean; // Force colors on/off (default: auto-detect) level?: 'debug'|'info'|'warn'|'error'; // Min level (default: 'debug') stream?: WritableStream; // Custom output (default: process.stderr) logFile?: string; // Optional file output }
function createLogger(options?: LoggerOptions): DebugLogger; function patchConsole(options?: LoggerOptions): void; function unpatchConsole(): void;
### 匹配器
// Installation function installMCPMatchers(): void;
// Tool matchers expect(client).toHaveTool(name: string); expect(tool).toHaveToolProperty(property: string, value?: any); expect(tool).toMatchToolSchema(schema: object); expect(result).toReturnToolResult(expected: any); expect(promise).toThrowToolError();
// Resource matchers expect(client).toHaveResource(uri: string);
// Prompt matchers expect(client).toHavePrompt(name: string);
// Snapshot matchers expect(data).toMatchMCPSnapshot(options?: { exclude?: string[] }); expect(result).toMatchToolResponseSnapshot(options?: { exclude?: string[] }); expect(tools).toMatchToolListSnapshot(options?: { exclude?: string[] }); expect(resources).toMatchResourceListSnapshot(options?: { exclude?: string[] }); expect(prompts).toMatchPromptListSnapshot(options?: { exclude?: string[] });
## 故障排除
### 测试挂起还是超时?
增加超时时间:
const client = new MCPTestClient({ command: 'node', args: ['./server.js'], timeout: 60000, // 60 seconds });
### 快照意外失败?
检查是否排除了足够的动态字段:
expect(result).toMatchToolResponseSnapshot({ exclude: [ 'timestamp', 'requestId', 'files.*.modified', 'data.*.generatedAt', ] });
### 日志未显示?
检查您的日志级别:
createLogger({ level: 'debug' }); // Show everything
### 颜色不起作用?
颜色仅在stderr为TTY时有效。强制启用/禁用:
createLogger({ colors: true }); // Always color createLogger({ colors: false }); // Never color
### 客户端未连接?
验证您的服务器是否正在使用stdio传输并响应初始化:
// Server must respond to initialize request server.setRequestHandler(InitializeRequestSchema, async (request) => { return { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'my-server', version: '1.0.0' }, }; });
## 需求
- Node.js>=18.0.0
- TypeScript>=5.0.0(如果使用TypeScript)
- Vitest>=1.0.0(用于测试功能)
## 贡献
看 [贡献.md](./CONTRIBUTING.md) 用于开发设置和指南。
## 许可证
MIT© [格纳997](https://github.com/gnana997)
## 相关项目
- [@模型上下文协议/sdk](https://modelcontextprotocol.io) -官方MCP SDK
- [模型上下文协议](https://modelcontextprotocol.io) -协议规范
- [Nochen jsonrpc](https://www.npmjs.com/package/node-stdio-jsonrpc) -基于stdio的JSON-RPC 2.0
______________________________________________________________________
**内置❤️ 对于MCP社区**
觉得这个有用吗? ⭐