@taikiy/mcp-server-test
v1.0.0
Published
A test server for Model Context Protocol (MCP) implementations
Readme
MCP Server Test Library
概要
MCPサーバーをコードベースでテストするためのライブラリです。MCP Inspectorのプロキシアーキテクチャを参考に、テスト環境でMCPサーバーの動作を検証できるようにします。
特徴
- 完全なモック実装: 実際のプロセスを起動せずにMCPサーバーをテスト
- 実サーバーサポート: stdioベースの実際のMCPサーバーもテスト可能
- メッセージ履歴: すべての通信を記録し、後から検証可能
- 豊富なアサーション: ツール、リソース、プロンプトの検証用ヘルパー
- タイムアウト制御: レスポンス時間のテストに対応
- エラーシミュレーション: エラーケースのテストが容易
クイックスタート
import { createTestClient, createTestServer, expectTool } from '@taiki-ssss/mcp-server-tester';
describe('My MCP Server', () => {
let server: TestServer;
let client: TestClient;
beforeEach(async () => {
server = await createTestServer({
command: 'node',
args: ['./dist/index.js'],
});
client = await createTestClient();
await client.connect(server);
});
afterEach(async () => {
await client.disconnect();
await server.stop();
});
test('should list available tools', async () => {
const response = await client.request('tools/list');
expectTool(response).toContain({
name: 'my-tool',
description: 'My tool description',
inputSchema: expect.any(Object),
});
});
test('should execute tool successfully', async () => {
const result = await client.request('tools/call', {
name: 'my-tool',
arguments: { input: 'test' },
});
expect(result.content).toEqual([{
type: 'text',
text: 'Expected output',
}]);
});
});インストール
npm install @taiki-ssss/mcp-server-tester --save-dev基本的な使い方
1. モックサーバーでのテスト
import { describe, test, expect } from 'vitest';
import { createTestClient, createMockServer } from '@taiki-ssss/mcp-server-tester';
describe('My MCP Server Tests', () => {
test('should handle custom method', async () => {
const client = createTestClient();
const server = createMockServer();
// カスタムハンドラーを登録
server.registerHandler('myMethod', (params) => ({
result: `Processed: ${params.input}`
}));
await server.start();
await client.connect(server);
const response = await client.request('myMethod', { input: 'test' });
expect(response.result).toBe('Processed: test');
await client.disconnect();
await server.stop();
});
});2. 実際のMCPサーバーのテスト
import { createTestClient, createTestServer } from '@taiki-ssss/mcp-server-tester';
test('should connect to real server', async () => {
const server = await createTestServer({
command: 'node',
args: ['./dist/my-server.js'],
});
const client = createTestClient();
await server.start();
await client.connect(server);
// 実際のサーバーと通信
const response = await client.request('tools/list');
expect(response.tools).toBeDefined();
await client.disconnect();
await server.stop();
});3. テストヘルパーの使用
import {
expectTool,
expectResource,
expectNoErrors,
runTestScenario
} from '@taiki-ssss/mcp-server-tester';
// ツールのテスト
test('should provide calculator tool', async () => {
const response = await client.request('tools/list');
expectTool(response).toContain({
name: 'calculator',
description: 'Performs calculations'
});
expectTool(response).toHaveCount(3);
});
// シナリオテスト
test('complete workflow', async () => {
await runTestScenario({
name: 'User workflow',
execute: async (client) => {
// 1. ツールを取得
const tools = await client.request('tools/list');
// 2. ツールを実行
const result = await client.request('tools/call', {
name: tools.tools[0].name,
arguments: { input: 'test' }
});
// 3. 完了通知
await client.notify('workflow/complete', { id: '123' });
},
verify: (history) => {
expectNoErrors(history);
expect(history.requests).toHaveLength(3); // initialize + 2 requests
expect(history.notifications).toHaveLength(1);
}
});
});4. メッセージ履歴の検証
test('should track message history', async () => {
// ... setup ...
await client.request('method1');
await client.notify('event1', { data: 'test' });
await client.request('method2');
const history = client.getHistory();
// リクエストの検証
expect(history.requests).toHaveLength(3); // initialize + 2
expect(history.requests[1].method).toBe('method1');
// 通知の検証
expect(history.notifications).toHaveLength(1);
expect(history.notifications[0].method).toBe('event1');
// エラーがないことを確認
expectNoErrors(history);
});5. エラーハンドリングのテスト
test('should handle errors', async () => {
server.registerHandler('failingMethod', () => {
throw new Error('Something went wrong');
});
await expect(
client.request('failingMethod')
).rejects.toThrow();
const history = client.getHistory();
expect(history.errors).toHaveLength(1);
expect(history.errors[0].message).toContain('Something went wrong');
});6. タイムアウトのテスト
test('should timeout slow requests', async () => {
const client = createTestClient({ timeout: 100 });
server.registerHandler('slowMethod', async () => {
await new Promise(resolve => setTimeout(resolve, 200));
return { result: 'too slow' };
});
await expect(
client.request('slowMethod')
).rejects.toThrow('Request timeout');
});API リファレンス
TestClient
connect(server?: TestServer): Promise<void>- サーバーに接続disconnect(): Promise<void>- 接続を切断request<T>(method: string, params?: any): Promise<T>- リクエストを送信notify(method: string, params?: any): Promise<void>- 通知を送信getHistory(): MessageHistory- メッセージ履歴を取得getCapabilities(): ServerCapabilities | null- サーバー機能を取得
MockTestServer
start(): Promise<void>- サーバーを起動stop(): Promise<void>- サーバーを停止registerHandler(method: string, handler: Function): void- ハンドラーを登録removeHandler(method: string): void- ハンドラーを削除clearHandlers(): void- すべてのハンドラーをクリア
ヘルパー関数
expectTool(response)- ツール関連のアサーションexpectResource(response)- リソース関連のアサーションexpectPrompt(response)- プロンプト関連のアサーションexpectRequest(history, method)- リクエストの存在を確認expectNotification(history, method)- 通知の存在を確認expectNoErrors(history)- エラーがないことを確認runTestScenario(scenario)- テストシナリオを実行waitForMessage(client, predicate, timeout?)- 特定のメッセージを待機createConfiguredMockServer(handlers)- 事前設定されたモックサーバーを作成
ベストプラクティス
セットアップとクリーンアップ
beforeEach(async () => { client = createTestClient(); server = createMockServer(); }); afterEach(async () => { await client.disconnect(); await server.stop(); });タイムアウトの設定
const client = createTestClient({ timeout: 10000 // 10秒 });エラーの適切な処理
try { await client.request('method'); } catch (error) { // エラーを検証 expect(error.code).toBe(-32601); }メッセージ履歴のクリア
client.clearHistory(); // テスト間で履歴をリセット
