npm package: @taikiy/mcp-server-test
Description: A test server for Model Context Protocol (MCP) implementations
Author: taiki-ssss
Latest version: 1.0.0

# MCP Server Test Library

## 概要
MCPサーバーをコードベースでテストするためのライブラリです。MCP Inspectorのプロキシアーキテクチャを参考に、テスト環境でMCPサーバーの動作を検証できるようにします。

## 特徴

- **完全なモック実装**: 実際のプロセスを起動せずにMCPサーバーをテスト
- **実サーバーサポート**: stdioベースの実際のMCPサーバーもテスト可能
- **メッセージ履歴**: すべての通信を記録し、後から検証可能
- **豊富なアサーション**: ツール、リソース、プロンプトの検証用ヘルパー
- **タイムアウト制御**: レスポンス時間のテストに対応
- **エラーシミュレーション**: エラーケースのテストが容易

## クイックスタート

```typescript
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',
    }]);
  });
});
```

## インストール

```bash
npm install @taiki-ssss/mcp-server-tester --save-dev
```

## 基本的な使い方

### 1. モックサーバーでのテスト

```typescript
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サーバーのテスト

```typescript
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. テストヘルパーの使用

```typescript
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. メッセージ履歴の検証

```typescript
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. エラーハンドリングのテスト

```typescript
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. タイムアウトのテスト

```typescript
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)` - 事前設定されたモックサーバーを作成

## ベストプラクティス

1. **セットアップとクリーンアップ**
   ```typescript
   beforeEach(async () => {
     client = createTestClient();
     server = createMockServer();
   });
   
   afterEach(async () => {
     await client.disconnect();
     await server.stop();
   });
   ```

2. **タイムアウトの設定**
   ```typescript
   const client = createTestClient({ 
     timeout: 10000 // 10秒
   });
   ```

3. **エラーの適切な処理**
   ```typescript
   try {
     await client.request('method');
   } catch (error) {
     // エラーを検証
     expect(error.code).toBe(-32601);
   }
   ```

4. **メッセージ履歴のクリア**
   ```typescript
   client.clearHistory(); // テスト間で履歴をリセット
   ```