进阶:MCP 服务的端到端测试
用 MCP Client 直连本地 Server,对 Tools/Resources/Sampling/Prompts/Notify 五类能力做端到端验证——每类给出初始化连接、发起请求、处理响应、断言校验的完整代码。
本文全部样例代码均已提交到:github.com/Tecvan-fe/mcp-demo。
MCP Server 开发完成后,如何验证各项能力符合预期?最直接的方式是启动一个 MCP Client,像真实消费方那样连上 Server,依次调用 Tools/Resources/Sampling/Prompts/Notify,再对返回结果做断言。下面按这五类能力分别给出端到端测试的写法。
1. 测试 Tools
本节完整代码:number.test.ts。
初始化连接。创建 Client 实例,通过 StdioClientTransport 拉起本地 Server 进程并建立连接:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
const transport = new StdioClientTransport({
command: 'npx',
args: ['tsx', 'packages/1_stdio/index.ts'],
});
const client = new Client({ name: 'test-client', version: '1.0.0' });
await client.connect(transport);发起请求。先 listTools 获取工具清单,再用 callTool 触发具体工具:
const tools = await client.listTools();
expect(tools.tools.map((t) => t.name)).toContain('add');
const result = await client.callTool({
name: 'add',
arguments: { a: 1, b: 2 },
});验证响应。断言返回内容结构与数值符合预期:
expect(result.content).toEqual([{ type: 'text', text: '3' }]);2. 测试 Resources
本节完整代码:resources.test.ts。
Resource 的测试模式与 Tools 类似,只是调用的接口换成 listResources 与 readResource:
const resources = await client.listResources();
expect(resources.resources.length).toBeGreaterThan(0);
const content = await client.readResource({
uri: 'mcp://root-docs/README.md',
});
expect(content.contents[0].text).toContain('示例文档');要点在于:readResource 传入的 uri 必须与 Server 端 ListResourcesRequestSchema 声明的资源 URI 一致,否则 Server 会抛出"资源不存在"。
3. 测试 Sampling
本节完整代码:sampling.test.ts。
Sampling 是反向调用——Server 通过 Client 请求 LLM 完成任务。测试时需要在 Client 端注册 sampling/createMessage 的处理回调,模拟 LLM 返回:
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
return {
model: 'test-model',
role: 'assistant',
content: {
type: 'text',
text: '这是模拟的 LLM 返回内容',
},
};
});注册回调后,再触发 Server 端会用到 Sampling 的请求(如某个 Prompt),验证最终返回中包含 Client 注入的模拟内容。这一步的关键是理解 Sampling 的方向:Client 既是请求发起方,又是 LLM 能力的提供方。
4. 测试 Prompts
本节完整代码:prompt.test.ts。
Prompt 测试同样分获取列表与获取详情两步:
const prompts = await client.listPrompts();
expect(prompts.prompts.map((p) => p.name)).toContain('电子邮件模板');
const prompt = await client.getPrompt({
name: '电子邮件模板',
arguments: { recipient: '张三', topic: '会议邀请' },
});
expect(prompt.messages[0].content.text).toContain('张三');验证重点是:传入的 arguments 是否被正确填充进模板占位符,以及返回的 messages 结构是否符合 MCP 规范。
5. 测试 Notify
本节完整代码:notify.test.ts。
Notification 是 Server 向 Client 主动推送的事件,测试时需要在 Client 端监听对应通知:
const notifications: unknown[] = [];
client.setNotificationHandler(
ToolListChangedNotificationSchema,
async (notification) => {
notifications.push(notification);
}
);
// 触发 Server 端发送通知的操作...
await new Promise((resolve) => setTimeout(resolve, 100));
expect(notifications.length).toBeGreaterThan(0);由于通知是异步推送,测试中通常需要一个短暂等待,确保通知在断言前抵达。这也是 Notify 测试与前面几类同步请求测试最大的区别。
