mcp-server-development について
mcp-server-developmentは、信頼性の高いMCPサーバーを構築するためのスキルで、unity-mcp-serverのアーキテクチャと設計パターンを中心に解説します。MCPサーバー開発者向けに、ハンドラー、入力検証、トランスポート、エラー処理、テストを扱います。
主な機能
- 01ツールごとのハンドラー設計
- 02Zodによる入力検証
- 03Content-LengthとNDJSON入力への対応
- 04JSON-RPCエラー形式の利用
- 05ハンドラーとトランスポートのテスト
- 06Unityコマンドとワークスペースパスの適用
活用シーン
- 01MCPツールのハンドラー構成設計
- 02標準入出力トランスポートの実装
- 03プロトコルやエラー処理の調査
- 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 の契約
すべてのハンドラーは、次の要件を満たす必要があります。
- ツールのメタデータを指定して
super()を呼び出す execute(params)メソッドを実装する- プレーンオブジェクトを返す(JSON-RPC 形式へのラップはフレームワークが処理します)
- 説明的なメッセージを含むエラーをスローする
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
- 最初に契約テストを書く:期待される入出力スキーマを定義する
- Unity 接続をモックする:ハンドラーのロジックを分離する
- エラーパスをテストする:無効な入力、接続の失敗
- 最後に正常系をテストする:契約を検証した後に行う
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 に厳密に従ってください