mcp服务器模拟
用于集成测试的可编程模拟MCP服务器。
](https://www.npmjs.com/package/mcp-server-mock) ](https://www.npmjs.com/package/mcp-server-mock)  ](https://nodejs.org)
描述
mcp-server-mock 是一个完全可控的、进程内模拟MCP(模型上下文协议)服务器,专为测试MCP客户端代码而设计。它处理JSON-RPC请求并返回配置的响应,从而在不运行真实服务器的情况下实现确定性集成测试。
该包提供了一个流畅的生成器API,用于注册具有罐装响应、动态处理程序函数、错误注入、可配置延迟和场景状态机的工具、资源和提示处理程序。每个请求都会记录时间戳和响应详细信息,以便进行事后断言。
当您需要测试MCP客户端库、代理框架、MCP主机应用程序或任何使用MCP服务器的代码时,请使用此包。它在进程内运行,立即启动,并在每个平台上产生相同的结果。
零运行时依赖关系。
安装
npm install --save-dev mcp-server-mock需要Node.js>=18。
快速开始
import { MockMCPServer } from 'mcp-server-mock';
// Create a mock server
const server = new MockMCPServer({ name: 'test-server', version: '1.0.0' });
// Register a tool with a static response
server.tool('search', {
description: 'Search the web',
inputSchema: {
type: 'object',
properties: { query: { type: 'string' } },
required: ['query'],
},
}).returns({
content: [{ type: 'text', text: 'TypeScript is a typed superset of JavaScript' }],
});
// Complete the MCP initialization handshake
await server.handleRequest({
jsonrpc: '2.0', id: 1, method: 'initialize',
params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'my-client', version: '1.0.0' } },
});
server.handleNotification({ jsonrpc: '2.0', method: 'notifications/initialized' });
// Call the tool
const response = await server.handleRequest({
jsonrpc: '2.0', id: 2, method: 'tools/call',
params: { name: 'search', arguments: { query: 'TypeScript' } },
});
// Assert interactions
server.assertToolCalled('search', 1);
server.assertToolCalledWith('search', { query: 'TypeScript' });特性
- Fluent生成器API --使用可链接的配置方法注册工具、资源和提示。
- 请求录制 --每个请求都包含序列号、ISO 8601时间戳、参数、响应结果或错误以及以毫秒为单位的持续时间。
- 断言助手 --用于验证工具调用、资源读取、提示检索、方法调用和总请求计数的内置方法。所有断言在失败时都会抛出描述性错误。
- 场景状态机 --定义由特定请求触发的命名状态和转换。工具、资源和提示根据当前状态返回不同的响应。
- 误差仿真 --为所有标准JSON-RPC错误和自定义应用程序错误预先构建的工厂。为每个处理程序或全局注入错误。
- 延迟和抖动注入 --模拟具有固定延迟、随机抖动范围或无限超时的慢速服务器。
- 处理器耗尽 --将处理程序限制为N次调用,然后返回耗尽错误。
- 夹具装载 --从JSON夹具对象以声明方式配置整个模拟服务器。
- 响应拦截器 --在返回任何方法之前拦截并修改其响应。可用于模拟畸形或注入的字段。
- 动态处理程序修改 --在测试过程中的任何时候添加或删除工具、资源和提示。
- 自动能力推导 --服务器的
initialize响应准确地反映了注册了哪些处理程序,与真实的MCP服务器行为相匹配。 - 完整的MCP协议生命周期 --强制执行
initialize/notifications/initialized握手。手柄ping,logging/setLevel,resources/subscribe,resources/unsubscribe,completion/complete,以及所有标准MCP方法。 - 零运行时依赖关系 --仅依赖于开发进行构建和测试。
API 参考
MockMCPServer
主要类。创建实例、注册处理程序、处理请求并运行断言。
import { MockMCPServer } from 'mcp-server-mock';
const server = new MockMCPServer(options);构建器选项(MockServerOptions)
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
name | string | *必需的* | 中报告的服务器名称 initialize 回应。 |
version | string | *必需的* | 中报告的服务器版本 initialize 回应。 |
protocolVersion | string | '2025-03-26' | 要通告的MCP协议版本。 |
capabilities | Partial | *自动衍生* | 覆盖自动能力推导。 |
defaultDelayMs | number | 0 | 全局延迟应用于所有响应,除非每个处理程序都被覆盖。 |
recordNotifications | boolean | true | 是否记录客户端通知。 |
enforceInitialization | boolean | true | 要求 initialize / notifications/initialized 在接受请求之前握手。 |
处理程序注册
// Tools
server.tool(name: string, definition?: ToolDefinition): ToolBuilder
// Resources
server.resource(uri: string, definition?: ResourceDefinition): ResourceBuilder
// Resource templates
server.resourceTemplate(definition: ResourceTemplateDefinition): void
// Prompts
server.prompt(name: string, definition?: PromptDefinition): PromptBuilder
// Completions
server.completion(handler: CompletionHandlerFn): void请求处理
// Process a JSON-RPC request (returns a JSON-RPC response)
await server.handleRequest(request: JsonRpcRequest): Promise
// Process a JSON-RPC notification (no response)
server.handleNotification(notification: JsonRpcNotification): void请求录制
server.requests // All recorded requests
server.notifications // All recorded notifications
server.requestsFor(method: string) // Requests filtered by method
server.toolCalls(toolName: string) // Tool call requests filtered by tool name
server.resourceReads(uri: string) // Resource read requests filtered by URI
server.promptGets(promptName: string) // Prompt get requests filtered by prompt name断言
所有断言方法都抛出描述性 Error 当期望未得到满足时的消息。
server.assertToolCalled(toolName: string, times?: number): void
server.assertToolCalledWith(toolName: string, args: Record): void
server.assertToolNotCalled(toolName: string): void
server.assertResourceRead(uri: string, times?: number): void
server.assertPromptRetrieved(promptName: string, times?: number): void
server.assertMethodCalled(method: string, times?: number): void
server.assertNoRequests(): void
server.assertRequestCount(count: number): void场景状态机
server.scenario(definition: ScenarioDefinition): void
server.currentState: string | undefined // Current scenario state (readonly)
server.setState(stateName: string): void // Manually set state动态修改
server.removeTool(name: string): void
server.removeResource(uri: string): void
server.removePrompt(name: string): void
server.interceptResponse(method: string, fn: (response) => response): void生命周期
server.resetRecordings(): void // Clear recorded requests and notifications
server.resetAll(): void // Reset handlers, recordings, scenario, and initialization state
server.loadFixture(fixture: FixtureFile): void // Configure server from a fixture object
await server.close(): Promise // Close the mock servercreateMockServer
工厂功能相当于 new MockMCPServer(options).
import { createMockServer } from 'mcp-server-mock';
const server = createMockServer({ name: 'test', version: '1.0.0' });ToolBuilder
返回者 server.tool().所有方法返回 this 为了实现流畅的链接。
| 方法 | 说明 |
|---|---|
.returns(response: ToolResponse) | 设置静态响应。 |
.handlerFn(fn: ToolHandlerFn) | 设置一个动态处理函数,用于接收 (args, extra). |
.throws(error: MockError) | 使此工具返回JSON-RPC错误。 |
.withDelay(ms: number) | 在响应之前添加固定延迟。 |
.withJitter(minMs: number, maxMs: number) | 在给定范围内添加随机延迟。 |
.timesOut() | 从不响应(模拟超时)。 |
.times(n: number) | 仅回复 n 次,然后返回耗尽错误。 |
.inState(stateName: string, response: ToolResponse) | 当场景处于给定状态时,返回不同的响应。 |
ResourceBuilder
返回者 server.resource().所有方法返回 this 为了实现流畅的链接。
| 方法 | 说明 |
|---|---|
.returns(response: ResourceResponse) | 设置静态内容。 |
.handlerFn(fn: ResourceHandlerFn) | 设置一个动态处理函数,用于接收 (uri, extra). |
.throws(error: MockError) | 使此资源返回JSON-RPC错误。 |
.withDelay(ms: number) | 在响应之前添加固定延迟。 |
.timesOut() | 从不响应(模拟超时)。 |
.inState(stateName: string, response: ResourceResponse) | 当场景处于给定状态时,返回不同的内容。 |
PromptBuilder
返回者 server.prompt().所有方法返回 this 为了实现流畅的链接。
| 方法 | 说明 |
|---|---|
.returns(response: PromptResponse) | 设置静态响应。 |
.handlerFn(fn: PromptHandlerFn) | 设置一个动态处理函数,用于接收 (args, extra). |
.throws(error: MockError) | 使此提示返回JSON-RPC错误。 |
.withDelay(ms: number) | 在响应之前添加固定延迟。 |
.inState(stateName: string, response: PromptResponse) | 当场景处于给定状态时,返回不同的响应。 |
MockErrors
为常见JSON-RPC错误预构建错误工厂。
import { MockErrors } from 'mcp-server-mock';
MockErrors.methodNotFound(method?: string) // -32601
MockErrors.invalidParams(message?: string) // -32602
MockErrors.internalError(message?: string) // -32603
MockErrors.parseError() // -32700
MockErrors.invalidRequest(message?: string) // -32600
MockErrors.custom(code: number, message: string, data?: unknown) // Any codeRequestRecorder
捕获所有传入的请求和通知。通过访问 server.requests 和 server.notifications,或单独使用。
import { RequestRecorder } from 'mcp-server-mock';
const recorder = new RequestRecorder();
recorder.recordRequest(method, params, id, result, error, durationMs);
recorder.recordNotification(method, params, direction);
recorder.requests // ReadonlyArray
recorder.notifications // ReadonlyArray
recorder.requestsFor(method)
recorder.toolCalls(toolName)
recorder.resourceReads(uri)
recorder.promptGets(promptName)
recorder.lastRequests(n)
recorder.requestCount // number
recorder.reset()HandlerRegistry
工具、资源、提示、资源模板和完成处理程序的内部注册表。处理能力推导。
import { HandlerRegistry } from 'mcp-server-mock';
const registry = new HandlerRegistry();
registry.registerTool(name, definition);
registry.registerResource(uri, definition);
registry.registerResourceTemplate(definition);
registry.registerPrompt(name, definition);
registry.setCompletionHandler(handler);
registry.getTool(name)
registry.getResource(uri)
registry.getPrompt(name)
registry.removeTool(name)
registry.removeResource(uri)
registry.removePrompt(name)
registry.listTools()
registry.listResources()
registry.listResourceTemplates()
registry.listPrompts()
registry.deriveCapabilities()
registry.resetAll()ScenarioManager
管理场景状态机以进行多步骤交互测试。
import { ScenarioManager } from 'mcp-server-mock';
const manager = new ScenarioManager();
manager.configure({ initialState: 'idle', transitions: [...] });
manager.currentState // string | undefined
manager.isConfigured // boolean
manager.setState('active');
manager.processRequest(method, params); // Returns new state
manager.reset(); // Reset to initial state
manager.clear(); // Remove configuration entirelyAssertionHelper
用于验证模拟服务器交互的断言助手。当期望未得到满足时,所有方法都会产生描述性错误。
import { AssertionHelper } from 'mcp-server-mock';
const helper = new AssertionHelper(recorder);
helper.assertToolCalled(toolName, times?);
helper.assertToolCalledWith(toolName, args);
helper.assertToolNotCalled(toolName);
helper.assertResourceRead(uri, times?);
helper.assertPromptRetrieved(promptName, times?);
helper.assertMethodCalled(method, times?);
helper.assertNoRequests();
helper.assertRequestCount(count);配置
服务器功能
默认情况下,功能会自动从注册的处理程序中派生出来:
tools注册任何工具时都会声明功能。resources能力(与subscribe: true和listChanged: true)在注册任何资源或资源模板时声明。prompts当注册任何提示时,都会声明功能。logging能力总是被宣布的。completions当设置完成处理程序时,会声明功能。
通过传递来覆盖自动推导 capabilities 在构造函数选项中:
const server = new MockMCPServer({
name: 'test',
version: '1.0.0',
capabilities: {
tools: { listChanged: false },
resources: undefined, // Explicitly disable
},
});全局延迟
对所有处理程序应用默认延迟:
const server = new MockMCPServer({
name: 'test',
version: '1.0.0',
defaultDelayMs: 100,
});每处理程序延迟(通过 .withDelay() 或 .withJitter())优先于全局默认值。
初始化强制
默认情况下,服务器会拒绝之前的所有请求 initialize / notifications/initialized 握手完成。禁用此选项以进行更简单的测试设置:
const server = new MockMCPServer({
name: 'test',
version: '1.0.0',
enforceInitialization: false,
});错误处理
JSON-RPC错误注入
使用以下命令在特定处理程序上注入错误 MockErrors 工厂:
import { MockErrors } from 'mcp-server-mock';
// Standard JSON-RPC errors
server.tool('broken').throws(MockErrors.internalError('Database crashed'));
server.tool('missing').throws(MockErrors.methodNotFound('search'));
server.tool('bad_input').throws(MockErrors.invalidParams('Missing required field'));
// Custom application errors with data payload
server.tool('rate_limited').throws(
MockErrors.custom(-32000, 'Rate limited', { retryAfter: 60 })
);处理器耗尽
将处理程序限制为固定的调用次数:
server.tool('limited')
.returns({ content: [{ type: 'text', text: 'ok' }] })
.times(3);
// First 3 calls succeed; the 4th returns:
// { code: -32603, message: "Handler exhausted after 3 calls" }超时模拟
模拟一个从不响应的处理程序:
server.tool('black_hole').timesOut();
// The returned promise never resolves未处理的错误
如果一个动态处理函数抛出一个正则 Error,它被捕获并包装为JSON-RPC内部错误(-32603).如果它抛出一个与 MockError 形状({ code: number, message: string }),则直接返回该错误。
初始化错误
当 enforceInitialization 是 true (默认),在握手完成之前发送的任何请求都会收到 -32600 (无效请求)错误,消息为“服务器未初始化”。
高级用法
动态处理函数
对计算响应使用处理函数:
server.tool('add', {
inputSchema: {
type: 'object',
properties: { a: { type: 'number' }, b: { type: 'number' } },
},
}).handlerFn((args) => ({
content: [{ type: 'text', text: String(Number(args.a) + Number(args.b)) }],
}));处理函数接收 (args: Record, extra: RequestExtra)The extra 对象提供:
extra.state--当前场景状态(string | undefined).extra.server--参考MockMCPServer呼叫实例setState()或resetRecordings()从处理器内部。
场景状态机
定义多步骤交互测试的状态和转换:
server.scenario({
initialState: 'unauthenticated',
transitions: [
{ from: 'unauthenticated', method: 'tools/call', match: { name: 'login' }, to: 'authenticated' },
{ from: 'authenticated', method: 'tools/call', match: { name: 'logout' }, to: 'unauthenticated' },
],
});
server.tool('get_data')
.inState('unauthenticated', {
content: [{ type: 'text', text: 'Access denied' }],
isError: true,
})
.inState('authenticated', {
content: [{ type: 'text', text: '{"users": 42}' }],
});过渡支持三种匹配模式:
- 无匹配 --任何具有指定方法的请求都会触发转换。
- 对象匹配 --根据请求参数进行浅层部分匹配。匹配对象中的所有键值对都必须出现在参数中。
- 功能匹配 --谓词函数
(params) => boolean用于复杂的匹配逻辑。
// Function matcher example
{
from: 'start',
method: 'tools/call',
match: (params) => params.name === 'step1' && params.arguments?.mode === 'fast',
to: 'running',
}状态转换发生在处理程序执行后,因此处理程序可以看到请求时的状态。
夹具装载
以声明方式配置整个模拟服务器:
server.loadFixture({
server: { name: 'test', version: '1.0.0', defaultDelayMs: 10 },
tools: [
{
name: 'search',
description: 'Search tool',
inputSchema: { type: 'object', properties: { q: { type: 'string' } } },
response: { content: [{ type: 'text', text: 'result' }] },
},
{
name: 'fail',
error: { code: -32603, message: 'broken' },
},
{
name: 'stateful',
states: {
idle: { content: [{ type: 'text', text: 'idle data' }] },
active: { content: [{ type: 'text', text: 'active data' }] },
},
},
],
resources: [
{
uri: 'file:///config.json',
name: 'Config',
mimeType: 'application/json',
response: { contents: [{ uri: 'file:///config.json', text: '{"debug": true}' }] },
},
],
resourceTemplates: [
{ name: 'User', uriTemplate: 'db://users/{id}', description: 'User by ID' },
],
prompts: [
{
name: 'review',
description: 'Code review',
arguments: [{ name: 'language', required: true }],
response: {
messages: [{ role: 'user', content: { type: 'text', text: 'Review this code.' } }],
},
},
],
scenario: {
initialState: 'idle',
transitions: [
{ from: 'idle', method: 'tools/call', match: { name: 'stateful' }, to: 'active' },
],
},
});响应拦截器
拦截并修改任何方法的响应。可用于测试客户端如何处理格式错误或意外的响应字段:
server.interceptResponse('tools/call', (response) => {
const result = response.result as Record;
result.extraField = 'injected';
return response;
});完工处理人员
为注册处理程序 completion/complete 请求:
server.completion((ref, argument) => ({
completion: {
values: ['typescript', 'terraform'].filter(v => v.startsWith(argument.value)),
hasMore: false,
},
}));测试生命周期模式
import { describe, it, beforeEach, afterEach } from 'vitest';
import { MockMCPServer } from 'mcp-server-mock';
describe('my MCP client', () => {
let server: MockMCPServer;
beforeEach(async () => {
server = new MockMCPServer({ name: 'test', version: '1.0.0' });
// Complete initialization handshake
await server.handleRequest({
jsonrpc: '2.0', id: 1, method: 'initialize',
params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'client', version: '1.0.0' } },
});
server.handleNotification({ jsonrpc: '2.0', method: 'notifications/initialized' });
// Register handlers for this test suite
server.tool('search').returns({
content: [{ type: 'text', text: 'result' }],
});
});
afterEach(async () => {
await server.close();
});
it('should call the search tool', async () => {
// ... exercise your client code ...
server.assertToolCalled('search', 1);
server.assertToolCalledWith('search', { query: 'test' });
});
it('should handle errors', async () => {
server.resetRecordings();
// ... different test ...
});
});支持的MCP方法
模拟服务器处理所有标准MCP请求方法:
| 方法 | 说明 |
|---|---|
initialize | 协议握手。返回服务器信息和功能。 |
ping | 活体检查。返回空结果。 |
tools/list | 返回所有已注册的带有模式的工具。 |
tools/call | 按名称执行工具处理程序。 |
resources/list | 返回所有已注册的资源。 |
resources/read | 按URI读取资源 |
resources/templates/list | 返回所有已注册的资源模板。 |
resources/subscribe | 接受订阅(返回空结果)。 |
resources/unsubscribe | 接受取消订阅(返回空结果)。 |
prompts/list | 返回所有已注册的提示。 |
prompts/get | 按名称检索提示。 |
completion/complete | 委托给已注册的完成处理程序。 |
logging/setLevel | 接受日志级别更改。 |
未知方法返回a -32601 (找不到方法)错误。
TypeScript
mcp-server-mock 是用TypeScript编写的,并在编译后的JavaScript中附带类型声明。所有公共类型都从包入口点导出。
导出类型
import type {
MockServerOptions,
ServerCapabilities,
ToolDefinition,
ToolAnnotations,
ToolContent,
ToolResponse,
ToolHandlerFn,
ResourceDefinition,
ResourceContent,
ResourceResponse,
ResourceHandlerFn,
ResourceTemplateDefinition,
PromptDefinition,
PromptArgument,
PromptMessage,
PromptResponse,
PromptHandlerFn,
CompletionResponse,
CompletionHandlerFn,
MockError,
RecordedRequest,
RecordedNotification,
RequestExtra,
ScenarioDefinition,
ScenarioTransition,
JsonRpcRequest,
JsonRpcNotification,
JsonRpcResponse,
JsonRpcMessage,
FixtureFile,
FixtureTool,
FixtureResource,
FixtureResourceTemplate,
FixturePrompt,
RegisteredHandler,
MockMCPServerInterface,
} from 'mcp-server-mock';导出的类和函数
import {
MockMCPServer,
createMockServer,
MockErrors,
ToolBuilder,
ResourceBuilder,
PromptBuilder,
RequestRecorder,
HandlerRegistry,
ScenarioManager,
AssertionHelper,
} from 'mcp-server-mock';编译器目标
该包通过CommonJS模块输出编译为ES2022。包括声明文件和源代码映射。
许可证
麻省理工学院
