MCP Jest Testing Framework: MCP 服务器的专用测试框架

MCP Jest Testing Framework

GitHub 仓库: josharsh/mcp-jest
Stars: 7⭐
编程语言: TypeScript
许可证: MIT
推荐度: 4.2/5.0

概述

MCP Jest Testing Framework 是首个(可能是唯一)专为 Model Context Protocol (MCP) 服务器设计的测试框架。类似 Jest 但专注于 MCP,提供自动化、声明式的 MCP 服务器测试,支持多种传输协议、快照测试和 CI/CD 集成,极大简化 MCP 服务器质量保证。

为什么需要 MCP 专用测试框架?

MCP 服务器的测试有其特殊性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MCP 测试挑战:

1. 协议复杂性
- MCP 协议握手流程
- JSON-RPC 消息格式
- 多种传输层 (stdio, SSE, HTTP streaming)

2. 进程管理
- 服务器进程启动/停止
- 进程状态监控
- 资源清理

3. 响应验证
- 结构化响应验证
- 工具调用验证
- 资源变更追踪

传统 HTTP 测试工具无法满足这些需求,因此需要专用的 MCP 测试框架。

功能特性详解

核心特性

特性 描述 价值
专为 MCP 设计 首个 MCP 专用测试框架 无需手动处理协议细节
极简 API 一个函数调用完成测试 降低学习成本
声明式测试 清晰、简洁的测试定义 提高代码可读性
多传输支持 stdio、HTTP streaming、SSE 适配不同部署场景
快照测试 自动捕获和对比快照 防止回归
CI/CD 就绪 原生 GitHub Actions 支持 自动化质量门禁
进程管理 自动管理服务器进程 无需手动处理
协议握手 自动处理 MCP 协议握手 减少样板代码
结构化验证 深度验证 MCP 响应 确保合规性

架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
┌─────────────────────────────────────────────────────────┐
│ MCP Jest Framework Architecture │
│ │
│ 测试定义层 (Test Definition) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ describeMCP() │ ← MCP 专用的 describe 块 │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ MCP 测试原语 │ │
│ │ • 工具调用测试 │ │
│ │ • 资源读取测试 │ │
│ │ • 提示模板测试 │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 传输抽象层 │ │
│ │ • StdioTransport│ │
│ │ • SSETransport │ │
│ │ • HTTPStreamTransport│ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 协议处理层 │ │
│ │ • 握手管理 │ │
│ │ • 消息序列化 │ │
│ │ • 响应解析 │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ MCP 服务器进程 │
└─────────────────────────────────────────────────────────┘

安装配置

安装步骤

1
2
3
4
5
6
7
8
9
# 方式 1: 项目依赖(推荐)
npm install --save-dev mcp-jest

# 方式 2: 全局安装
npm install -g mcp-jest

# 方式 3: 其他包管理器
pnpm add -D mcp-jest
yarn add -D mcp-jest

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// jest.config.js
module.exports = {
preset: 'mcp-jest',
testEnvironment: 'mcp-jest/environment',
setupFilesAfterEnv: ['mcp-jest/setup'],

// MCP 服务器配置
mcp: {
serverCommand: 'node',
serverArgs: ['./dist/server.js'],
transport: 'stdio', // 或 'sse', 'http-stream'
timeout: 30000, // 超时时间(毫秒)
},

// 测试文件匹配
testMatch: ['**/*.mcp.test.ts', '**/*.mcp.test.js'],
};

使用指南

基础测试示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { describeMCP, it, expect } from 'mcp-jest';

describeMCP('My MCP Server', () => {
// 测试工具调用
it('should list available tools', async () => {
const tools = await mcp.listTools();

expect(tools).toBeDefined();
expect(tools.tools).toBeInstanceOf(Array);
expect(tools.tools.length).toBeGreaterThan(0);
});

// 测试工具执行
it('should execute calculator tool', async () => {
const result = await mcp.callTool('calculator', {
operation: 'add',
a: 5,
b: 3,
});

expect(result).toBeDefined();
expect(result.content).toContain('8');
});

// 测试资源读取
it('should read resource', async () => {
const resource = await mcp.readResource('file://example.txt');

expect(resource).toBeDefined();
expect(resource.contents).toBeDefined();
});

// 测试提示模板
it('should render prompt template', async () => {
const prompt = await mcp.getPrompt('greeting', {
name: 'World',
});

expect(prompt).toBeDefined();
expect(prompt.messages).toBeDefined();
expect(prompt.messages[0].content).toContain('Hello, World!');
});
});

快照测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { describeMCP, it, expect } from 'mcp-jest';

describeMCP('Snapshot Tests', () => {
it('should match tool list snapshot', async () => {
const tools = await mcp.listTools();

// 自动创建快照
expect(tools).toMatchSnapshot();
});

it('should match prompt snapshot', async () => {
const prompt = await mcp.getPrompt('code-review', {
language: 'typescript',
});

expect(prompt).toMatchSnapshot();
});
});

// 更新快照:npm test -- -u

参数化测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { describeMCP, it, expect, testCases } from 'mcp-jest';

describeMCP('Parameterized Tests', () => {
testCases([
{ operation: 'add', a: 2, b: 3, expected: 5 },
{ operation: 'subtract', a: 10, b: 4, expected: 6 },
{ operation: 'multiply', a: 3, b: 7, expected: 21 },
{ operation: 'divide', a: 20, b: 4, expected: 5 },
]).it('should perform $operation correctly',
async ({ operation, a, b, expected }) => {
const result = await mcp.callTool('calculator', {
operation,
a,
b,
});

expect(result.content).toContain(String(expected));
}
);
});

错误处理测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { describeMCP, it, expect } from 'mcp-jest';

describeMCP('Error Handling', () => {
it('should throw error for invalid tool', async () => {
await expect(
mcp.callTool('nonexistent-tool', {})
).rejects.toThrow('Tool not found');
});

it('should throw MCP error with code', async () => {
try {
await mcp.callTool('validator', { invalid: true });
} catch (error) {
expect(error).toBeInstanceOf(MCPError);
expect(error.code).toBe(-32602); // Invalid params
}
});

it('should handle timeout', async () => {
await expect(
mcp.callTool('slow-tool', { delay: 60000 })
).rejects.toThrow('Timeout');
}, 30000); // 30 秒超时
});

传输协议测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { describeMCP, it, expect, withTransport } from 'mcp-jest';

// 测试 stdio 传输
withTransport('stdio', () => {
describeMCP('Stdio Transport', () => {
it('should connect via stdio', async () => {
const tools = await mcp.listTools();
expect(tools).toBeDefined();
});
});
});

// 测试 SSE 传输
withTransport('sse', () => {
describeMCP('SSE Transport', () => {
it('should connect via SSE', async () => {
const tools = await mcp.listTools();
expect(tools).toBeDefined();
});
});
});

// 测试 HTTP Stream 传输
withTransport('http-stream', () => {
describeMCP('HTTP Stream Transport', () => {
it('should connect via HTTP stream', async () => {
const tools = await mcp.listTools();
expect(tools).toBeDefined();
});
});
});

CI/CD 集成

GitHub Actions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# .github/workflows/mcp-test.yml
name: MCP Server Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build server
run: npm run build

- name: Run MCP tests
run: npm test
env:
MCP_TEST_TIMEOUT: 30000

- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json

GitLab CI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .gitlab-ci.yml
stages:
- test

mcp-test:
stage: test
image: node:20
script:
- npm ci
- npm run build
- npm test
artifacts:
reports:
junit: junit.xml
paths:
- coverage/

实战示例:测试完整的 MCP 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// tests/weather-server.mcp.test.ts
import {
describeMCP,
it,
expect,
beforeAll,
afterAll,
} from 'mcp-jest';

describeMCP('Weather MCP Server', () => {
beforeAll(async () => {
// 服务器已在 jest.config.js 中配置自动启动
});

afterAll(async () => {
// 服务器会自动关闭
});

describe('Tools', () => {
it('should list weather tools', async () => {
const tools = await mcp.listTools();

expect(tools.tools).toMatchObject([
{
name: 'get-current-weather',
description: expect.stringContaining('current weather'),
},
{
name: 'get-forecast',
description: expect.stringContaining('forecast'),
},
]);
});

it('should get current weather for location', async () => {
const result = await mcp.callTool('get-current-weather', {
location: 'San Francisco, CA',
});

expect(result.content).toMatch(/San Francisco/);
expect(result.content).toMatch(/\d+°/);
});

it('should get weather forecast', async () => {
const result = await mcp.callTool('get-forecast', {
location: 'New York, NY',
days: 3,
});

expect(result.content).toContain('New York');
expect(result.content.split('\n').length).toBeGreaterThan(2);
});
});

describe('Resources', () => {
it('should list available resources', async () => {
const resources = await mcp.listResources();

expect(resources.resources).toBeInstanceOf(Array);
expect(resources.resources.length).toBeGreaterThan(0);
});

it('should read weather-data resource', async () => {
const resource = await mcp.readResource(
'weather-data://current'
);

expect(resource.contents).toBeDefined();
expect(resource.contents[0].text).toBeDefined();
});
});

describe('Prompts', () => {
it('should list available prompts', async () => {
const prompts = await mcp.listPrompts();

expect(prompts.prompts).toBeInstanceOf(Array);
});

it('should render weather report prompt', async () => {
const prompt = await mcp.getPrompt('weather-report', {
location: 'Boston, MA',
});

expect(prompt.messages).toBeDefined();
expect(prompt.messages.length).toBeGreaterThan(0);
expect(prompt.messages[0].content).toContain('Boston');
});
});
});

最佳实践

测试组织

1
2
3
4
5
6
7
8
9
10
11
12
// 良好的测试组织
tests/
├── unit/
│ ├── tools.mcp.test.ts # 工具测试
│ ├── resources.mcp.test.ts # 资源测试
│ └── prompts.mcp.test.ts # 提示测试
├── integration/
│ ├── stdio-transport.mcp.test.ts
│ ├── sse-transport.mcp.test.ts
│ └── http-stream.mcp.test.ts
└── e2e/
└── full-workflow.mcp.test.ts

测试命名规范

1
2
3
4
5
6
7
8
9
10
11
12
13
// 清晰的测试命名
it('should [action] when [condition]', async () => {
// ...
});

// 示例
it('should return weather data when location is valid', async () => {
// ...
});

it('should throw error when location is invalid', async () => {
// ...
});

性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 复用连接
describeMCP('Performance Tests', () => {
let connection: MCPConnection;

beforeAll(async () => {
connection = await createConnection();
});

// 2. 并行测试
it.concurrent('should handle concurrent requests', async () => {
const promises = Array(10).fill(null).map(() =>
mcp.listTools()
);
const results = await Promise.all(promises);
expect(results).toHaveLength(10);
});

// 3. 合理设置超时
it('should complete within timeout', async () => {
// 快速测试设置较短超时
}, 5000);
});

技术亮点

  • scope:MCP 服务器的专用测试框架,填补了 MCP 测试工具的空白
  • architecture:TypeScript 实现,支持多种传输协议,架构清晰
  • innovation:首个专为 MCP 设计的测试框架,超越简单的 HTTP 测试
  • unique:管理进程、传输、协议握手和结构化验证,一体化解决方案

项目信息

适用场景

场景 推荐度 说明
MCP 服务器开发 ⭐⭐⭐⭐⭐ 持续测试服务器功能
CI/CD 集成 ⭐⭐⭐⭐⭐ 自动化质量门禁
工具验证 ⭐⭐⭐⭐⭐ 验证 MCP 工具可用性
协议测试 ⭐⭐⭐⭐⭐ 确保 MCP 协议合规
回归测试 ⭐⭐⭐⭐⭐ 防止功能退化
性能测试 ⭐⭐⭐⭐ 监控服务器性能
兼容性测试 ⭐⭐⭐⭐ 跨环境测试

局限性

  1. 生态较新: 项目处于早期阶段,社区资源有限
  2. 文档待完善: 部分高级功能缺少文档
  3. Stars 较少: 目前仅 7 个 Stars,需要更多社区验证

总结

MCP Jest Testing Framework 是 MCP 服务器开发的重要工具,它:

  • 简化测试流程: 自动处理进程管理、协议握手等繁琐细节
  • 提高测试质量: 结构化验证确保 MCP 响应合规
  • 降低维护成本: 声明式测试更易理解和维护

对于正在开发 MCP 服务器的团队,这是值得采用的测试工具。


相关资源:

© 2026 Generative AI Discovery All Rights Reserved.
Theme by hiero