Zenn
🦄

カスタムMCPサーバーのテンプレートとテスト実装

2025/03/22に公開

はじめに

近年、AIの進化とともに、大規模言語モデル(LLM)と外部ツールを連携させる需要が急速に高まっています。Model Context Protocol(MCP)は、AIモデルが外部の機能やデータにアクセスするための標準化されたプロトコルとして注目を集めています。

MCPサーバーを自作する価値は主に3つあります:

  1. カスタム機能の実現: 特定のドメインや業務に特化したツールをAIに提供できる
  2. セキュリティとプライバシーの制御: 自社のデータやAPIへのアクセスを安全に管理できる
  3. パフォーマンスの最適化: 特定のユースケースに合わせた効率的な実装が可能

今回は、MCPサーバーの理解を深めるために、シンプルなMCPサーバーとそのテストを実装してみました。この記事を通して、MCPサーバーの基本構造、ツールの実装方法、そしてテスト戦略について学べます。

完成のイメージはこちら

完成イメージ

コードはこちらで公開しています:
https://github.com/tkc/mcp-server-template

MCPの仕組みと概念

MCPを理解する上で重要なのは、AIモデルとツールの連携の流れです。以下の図はその基本的な仕組みを示しています:

主な処理の流れは:

  1. ユーザーがAIに対してクエリを送信
  2. AIモデルがそのクエリを分析し、外部ツールが必要か判断
  3. 必要と判断した場合、AIはMCPサーバーに適切なツールと引数を指定してリクエスト
  4. MCPサーバーがパラメータをバリデーションし、指定されたツールを実行
  5. 実行結果がAIに返され、AIは結果を考慮した応答をユーザーに提供

new McpServer()server.tool() の詳細解説

McpServerの初期化

MCPサーバーを作成する第一歩は、McpServerクラスのインスタンスを初期化することです。

const server = new McpServer({
  name: 'quiz-mcp-server',
  version: '1.0.0',
  description: '詳細な説明...',
});

ここで各パラメータの意味を詳しく見ていきましょう:

  • name: サーバーの識別名。これはクライアントがこのMCPサーバーを認識するために使用されます。
  • version: サーバーのバージョン番号。APIの互換性管理や変更追跡に役立ちます。
  • description: サーバーの詳細な説明。この説明はAIモデルがこのサーバーの機能を理解するのに役立ち、適切なツール選択をサポートします。詳細な説明を提供することで、AIはサーバーの能力をより正確に把握できます。

MCPサーバーの初期化時に提供する情報は、プロトコルのハンドシェイクやクライアントへの情報提供に使用されます。特に説明文は十分に詳細で具体的であることが重要です。AIモデルはこの説明を読み、どのような場合にこのサーバーとそのツールを使用すべきかを判断します。

ツールの登録

server.tool()メソッドは、MCPサーバーに新しいツールを登録するために使用されます。

server.tool(
  'get_quiz', // ツール名
  '詳細な説明...', // ツールの説明
  QuizSchema.shape, // パラメータのスキーマ
  // 実装関数(コード省略)
);

各パラメータの意味は以下の通りです:

  1. ツール名: AIモデルがこのツールを呼び出す際に使用する一意の識別子。名前は機能を明確に示す簡潔なものが望ましいです。

  2. ツールの説明: このツールが何をするのか、どのような場合に使用すべきかの詳細な説明。説明はAIモデルがこのツールの用途と機能を理解するために非常に重要です。適切なユースケース、入力パラメータ、期待される出力などを含めるべきです。

  3. パラメータのスキーマ: ツールが受け付けるパラメータの構造と型を定義するZodスキーマ。これにより、型安全性が確保され、パラメータのバリデーションが自動的に行われます。例えば:

    const QuizSchema = z.object({
      category: z.enum(['general', 'science', 'history']),
      difficulty: z.enum(['easy', 'medium', 'hard']),
      count: z.number().min(1).max(10),
    });
    
  4. 実装関数: ツールの実際の処理を行う関数。この関数はバリデーション済みのパラメータを受け取り、処理結果を返します。

ツール登録の重要なポイントは、AIモデルがツールを効果的に使用できるよう、明確で具体的な説明を提供することです。ツールの名前と説明は、モデルがユーザーの意図を理解し、適切なタイミングでツールを呼び出すための重要な手がかりとなります。

ツール選択と実行の仕組み

AIモデルがどのようにツールを選択し、MCPサーバーがどう処理するかを図で表すと:

実際の使用例

例えば、ユーザーが「科学に関する簡単なクイズを出して」と要求した場合、AIモデルは:

  1. 要求を理解し、クイズツールの使用が適切だと判断
  2. get_quizツールを選択
  3. 適切なパラメータ({category: "science", difficulty: "easy", count: 1})を設定
  4. MCPサーバーにリクエストを送信
  5. ツールの実行結果をユーザーに提示

という流れで処理を行います。

この仕組みにより、AIモデルはサーバー側で実装されたさまざまな機能に安全にアクセスし、より豊かな応答をユーザーに提供できるようになります。

MCPサーバーの概要

MCP (Model Context Protocol) は、AIモデルと外部ツールを接続するための標準化されたプロトコルです。このプロトコルは、AIモデルがさまざまな外部機能やAPIにアクセスするための一貫した方法を提供します。

MCPサーバーの主な機能:

  • AIモデルからのリクエストを処理
  • 外部ツールへのアクセスを提供
  • ツールの実行結果をAIモデルに返す
  • 型安全なパラメータ処理

このプロトコルを使うことで、AIモデルは天気情報の取得、データベースのクエリ、計算の実行など、外部システムとのインタラクションが可能になります。

テンプレート構成

今回作成したMCPサーバーテンプレートは以下のような構成になっています:

mcp-server-template/
├── src/
│   ├── server.ts          # MCPサーバーのメインコード
│   ├── tools/             # 各種ツール実装
│   │   ├── quiz.ts        # クイズツール
│   │   └── calculator.ts  # 計算ツール
│   └── schemas/           # パラメータのスキーマ定義
│       └── quiz.ts        # クイズパラメータスキーマ
├── tests/                 # テストコード
│   ├── server.test.ts     # サーバーのテスト
│   └── tools/             # 各ツールのテスト
│       └── quiz.test.ts   # クイズツールのテスト
└── package.json

主要な依存関係:

  • zod: パラメータバリデーション用ライブラリ
  • bun: 高速なJavaScript/TypeScriptランタイム

テスト実装

以下のようなテストを実装しました。

ツールのユニットテスト

各ツールが期待通りの動作をするかを個別にテストします:

// クイズツールのテスト例
describe('Quiz Tool', () => {
  // テスト準備
  const quizTool = new QuizTool();

  test('正しいパラメータで正常にクイズを返す', async () => {
    const result = await quizTool.execute({
      category: 'general',
      difficulty: 'easy',
      count: 1,
    });

    expect(result.status).toBe('success');
    expect(result.quizzes).toBeDefined();
    expect(result.quizzes.length).toBe(1);
  });

  test('無効なパラメータでエラーを返す', async () => {
    // Zodバリデーションがエラーをスローするため
    await expect(async () => {
      await quizTool.execute({
        category: 'invalid', // 無効なカテゴリ
        difficulty: 'easy',
        count: 1,
      });
    }).rejects.toThrow();
  });
});

サーバー統合テスト

サーバー全体の動作をテストします:

describe('MCP Server', () => {
  // テスト用サーバーの作成
  const server = new McpServer({
    name: 'test-server',
    version: '1.0.0',
  });

  // テスト用ツールの登録
  server.registerTool(new QuizTool());

  test('ツールが正しく登録されている', () => {
    expect(server.getTools()).toContain('get_quiz');
  });

  test('リクエストを正常に処理できる', async () => {
    const response = await server.processRequest({
      tool: 'get_quiz',
      params: {
        category: 'general',
        difficulty: 'easy',
        count: 1,
      },
    });

    expect(response.status).toBe('success');
  });

  test('存在しないツールへのリクエストを適切に処理', async () => {
    const response = await server.processRequest({
      tool: 'non_existent_tool',
      params: {},
    });

    expect(response.status).toBe('error');
    expect(response.error.message).toContain('Tool not found');
  });
});

パフォーマンステスト

MCPサーバーの性能をテストします:

test('複数の同時リクエストを処理できる', async () => {
  const requests = Array(50)
    .fill(null)
    .map(() =>
      server.processRequest({
        tool: 'get_quiz',
        params: {
          category: 'general',
          difficulty: 'easy',
          count: 1,
        },
      })
    );

  const results = await Promise.all(requests);

  // すべてのリクエストが成功していることを確認
  expect(results.every((r) => r.status === 'success')).toBe(true);
});

まとめ

MCPサーバーのテンプレートとテスト実装を通じて、AIモデルと外部ツールを統合するための堅牢な基盤を構築することができました。特に重要なポイントは以下の通りです:

  • 型安全性: TypeScriptとZodによる厳格な型チェックとバリデーション
  • テスト網羅性: ユニットテストから統合テスト、エラーケースまでカバー
  • 拡張性: 新しいツールを容易に追加できる設計

MCPプロトコルはまだ発展途上ですが、今後AIと外部システムの連携において重要な役割を果たしていくでしょう。このテンプレートを基に、様々なAIアプリケーションのバックエンドを効率的に構築できるようになりました。

参考リソース

Discussion

ログインするとコメントできます