mcpField
M
mcp-server-development

mcp-server-development

github.com/akiojin開発1037 回インストール2 日前に更新
リポジトリ

mcp-server-development について

mcp-server-developmentは、信頼性の高いMCPサーバーを構築するためのスキルで、unity-mcp-serverのアーキテクチャと設計パターンを中心に解説します。MCPサーバー開発者向けに、ハンドラー、入力検証、トランスポート、エラー処理、テストを扱います。

主な機能

  1. 01ツールごとのハンドラー設計
  2. 02Zodによる入力検証
  3. 03Content-LengthとNDJSON入力への対応
  4. 04JSON-RPCエラー形式の利用
  5. 05ハンドラーとトランスポートのテスト
  6. 06Unityコマンドとワークスペースパスの適用

活用シーン

  1. 01MCPツールのハンドラー構成設計
  2. 02標準入出力トランスポートの実装
  3. 03プロトコルやエラー処理の調査
  4. 04Unity連携のテスト作成

MCP 対応

ツール定義、JSON-RPCトランスポート、プロトコル準拠を含むMCPサーバー構築の指針を示します。アーキテクチャ例はunity-mcp-serverを中心にしています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/akiojin/llmlb --skill mcp-server-development

インストール後、Claude Code で/mcp-server-developmentを入力して読み込まれたか確認します。

詳細

このスキルでは、堅牢な MCP サーバーを構築するための包括的なガイダンスを提供します。特に、unity-mcp-server のアーキテクチャとパターンに焦点を当てています。

中核となる理念

MCP サーバーは、AI アシスタントと外部システムをつなぎます。次の要件を満たす必要があります。

  • 信頼性:エラーを適切に処理し、予期せずクラッシュしないこと
  • 見つけやすさ:ツールのスキーマが明確で、自己説明的であること
  • 高いパフォーマンス:特に stdio トランスポートで、レイテンシーを最小限に抑えること
  • プロトコル準拠:JSON-RPC 2.0 と MCP の仕様に厳密に従うこと

アーキテクチャパターン

ハンドラーベースの設計

必ず、ツールごとにハンドラークラスを使用してください。各ハンドラーには次の処理をまとめます。

  • 入力の検証(Zod スキーマ)
  • ビジネスロジックの実行
  • エラー処理とレスポンスのフォーマット
// Pattern: One handler per tool
export class SystemPingToolHandler extends BaseToolHandler {
  constructor(unityConnection) {
    super({
      name: 'system_ping',
      description: 'Check Unity Editor connectivity',
      inputSchema: { type: 'object', properties: {} }
    });
    this.unityConnection = unityConnection;
  }

  async execute(params) {
    const result = await this.unityConnection.send({ command: 'ping' });
    return { status: 'ok', ...result };
  }
}
BaseToolHandler の契約

すべてのハンドラーは、次の要件を満たす必要があります。

  1. ツールのメタデータを指定して super() を呼び出す
  2. execute(params) メソッドを実装する
  3. プレーンオブジェクトを返す(JSON-RPC 形式へのラップはフレームワークが処理します)
  4. 説明的なメッセージを含むエラーをスローする
Zod による入力検証

処理を始める前に、必ず入力を検証してください。

import { z } from 'zod';

const inputSchema = z.object({
  name: z.string().min(1).describe('GameObject name'),
  primitiveType: z.enum(['cube', 'sphere', 'cylinder']).optional()
});

validate(input) {
  return inputSchema.parse(input);
}

トランスポートレイヤー

Content-Length フレーミング(標準)

MCP では、stdio に LSP 形式の Content-Length フレーミングを使用します。

Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/call"...}

出力には必ず Content-Length を使用してください。1 つのセッションで異なるフレーミング形式を絶対に混在させないでください。

ハイブリッド入力(互換性)

クライアントとの互換性を保つため、Content-Length と NDJSON の両方の入力を受け付けてください。

  • Claude Desktop:Content-Length
  • 一部の CLI ツール:NDJSON(改行区切り)

エラー処理

構造化エラー

仕様で定められている MCP のエラーコードを使用してください。

const McpError = {
  ParseError: -32700,
  InvalidRequest: -32600,
  MethodNotFound: -32601,
  InvalidParams: -32602,
  InternalError: -32603
};

throw new Error(JSON.stringify({
  code: McpError.InvalidParams,
  message: 'primitiveType must be one of: cube, sphere, cylinder'
}));
エラーレスポンス形式
{
  jsonrpc: '2.0',
  id: requestId,
  error: {
    code: -32602,
    message: 'Invalid params',
    data: { field: 'name', issue: 'required' }
  }
}

Unity 固有のパターン

コマンドプロトコル

Unity との通信には、シンプルなリクエスト/レスポンスパターンを使用します。

const result = await this.unityConnection.send({
  command: 'gameobject_create',
  params: { name: 'Cube', primitiveType: 'cube' }
});
ワークスペースルートの解決

ファイルパスを必要とするコマンドには、必ず workspaceRoot を含めてください。

execute(params) {
  return this.unityConnection.send({
    command: 'screenshot_capture',
    workspaceRoot: config.workspaceRoot,
    ...params
  });
}

テストパターン

ハンドラーの TDD
  1. 最初に契約テストを書く:期待される入出力スキーマを定義する
  2. Unity 接続をモックする:ハンドラーのロジックを分離する
  3. エラーパスをテストする:無効な入力、接続の失敗
  4. 最後に正常系をテストする:契約を検証した後に行う
describe('CreateGameObjectHandler', () => {
  it('validates primitiveType enum', async () => {
    const handler = new CreateGameObjectHandler(mockConnection);
    await assert.rejects(
      () => handler.handle({ name: 'Cube', primitiveType: 'invalid' }),
      /primitiveType must be one of/
    );
  });
});
統合テスト

JSON-RPC のフレーミングを含む、リクエスト/レスポンスの全サイクルをテストしてください。

const stdin = new PassThrough();
const stdout = new PassThrough();
const transport = new HybridStdioServerTransport(stdin, stdout);

stdin.write('Content-Length: 50\r\n\r\n{"jsonrpc":"2.0","id":1,"method":"ping"}');
// Assert stdout contains Content-Length response

よくある間違い

フレーミング形式の混在:

  • 禁止:Content-Length 入力の受信後に NDJSON を出力する
  • 必須:入力形式にかかわらず Content-Length を出力する

エラーの握りつぶし:

  • 禁止:catch (e) { return null; }
  • 必須:コンテキストを含めてエラーを伝播する

検証漏れ:

  • 禁止:クライアントからの生の入力を信頼する
  • 必須:処理の前に Zod で検証する

stdio のブロック:

  • 禁止:トランスポート上で同期処理を行う
  • 必須:全体を通して async/await を使用する

ハンドラーの登録

中央のインデックスですべてのハンドラーを登録してください。

// src/handlers/index.js
export function createHandlers(unityConnection) {
  return [
    new SystemPingToolHandler(unityConnection),
    new CreateGameObjectHandler(unityConnection),
    new ScreenshotHandler(unityConnection),
    // ...
  ];
}

覚えておくこと

  • 常に Content-Length を使用する:出力のフレーミングは一貫させる必要があります
  • すべてを検証する:クライアントからの入力を決して信頼しないでください
  • 1 つのハンドラーに 1 つのツール:ハンドラーの役割を絞り込んでください
  • エラーパスをテストする:バグの多くはエラー処理に潜んでいます
  • プロトコルに準拠する:JSON-RPC 2.0 に厳密に従ってください

コメント