关于我
MCP 全解:从理解到深度开发从零开发一款 MCP Server

进阶: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 类似,只是调用的接口换成 listResourcesreadResource:

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 测试与前面几类同步请求测试最大的区别。

6. 参考资料

On this page