MCP SDKで必須のZodバリデーション - 実装から理解する型安全なツール呼び出し

## はじめに

Model Context Protocol (MCP) SDKを使用していて、以下のようなエラーに遭遇しました。

```typescript
// 型エラーが発生するコード
const result = await client.callTool({
  name: "example",
  arguments: { value: "test" }
});
```

このエラーは、MCPのSDKが採用している**型安全性のための仕組み**に関係しています。本記事では、なぜこのようなエラーが発生するのか、そしてその解決方法について解説します。

サンプルコードは[mcp-client-demo](https://github.com/yonaka15/mcp-client-demo)で公開しています。

https://github.com/yonaka15/mcp-client-demo

## Zodとは何か

[Zod](https://github.com/colinhacks/zod)は、TypeScript用のスキーマ検証ライブラリです。以下のような特徴があります：

1. TypeScript優先設計
   - スキーマ定義から自動的に型が推論される
   - 実行時検証と型チェックが統合されている

2. 堅牢な型安全性
   - 定義したスキーマと実際の型が必ず一致する
   - 実行時のエラーを型システムで防ぐ

簡単な例を見てみましょう：

```typescript
import { z } from 'zod';

// スキーマを定義
const UserSchema = z.object({
  name: z.string(),
  age: z.number().min(0),
});

// 型が自動的に推論される
type User = z.infer<typeof UserSchema>;

// 実行時の検証
const validateUser = (data: unknown): User => {
  return UserSchema.parse(data);  // 検証失敗時はエラーをスロー
};
```

## なぜMCP SDKはZodを使用しているのか

MCP SDKは、AIモデルとアプリケーション間の通信を安全に行うために設計されています。この文脈で「安全」とは：

1. 型の整合性が保証されている
2. ランタイムでの予期せぬエラーを防ぐ
3. APIの使用方法を強制できる

これらを実現するために、MCPはZodを採用しています。

## 実装例：ツール呼び出しの型安全な実装

MCP SDKでは、ツールを呼び出す前に必ずスキーマ検証を行う必要があります：

```typescript
import {
  CallToolRequestSchema,
  CallToolResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";

async function callTool(
  toolName: string,
  input?: Record<string, unknown>
): Promise<void> {
  // リクエストの構築
  const toolRequest: CallToolRequest = {
    method: "tools/call",
    params: {
      name: toolName,
      arguments: input,
    },
  };

  // Zodによる検証（これが必須）
  const validatedRequest = CallToolRequestSchema.parse(toolRequest);
  
  // 検証済みデータでツール呼び出し
  const rawResult = await client.callTool(validatedRequest.params);
  
  // レスポンスも検証
  const validatedResult = CallToolResultSchema.parse(rawResult);
  
  return validatedResult;
}
```

この実装には以下のような利点があります：

1. リクエストの形式が正しいことを実行時に保証
2. 型推論により、IDEの補完が正確に機能
3. ランタイムエラーを事前に防止

## エラーハンドリング

Zodの検証に失敗した場合、`ZodError`が発生します。適切にハンドリングすることで、開発時のデバッグが容易になります：

```typescript
try {
  const validatedRequest = CallToolRequestSchema.parse(toolRequest);
  // ...
} catch (error) {
  if (error instanceof z.ZodError) {
    console.error('バリデーションエラー:', JSON.stringify(error.errors, null, 2));
    // エラーの詳細な情報が得られる
  }
  throw error;
}
```

## 実践的なTips

下記のようにBaseParamsSchemaを自分で定義してもいいですね

1. スキーマの再利用

```typescript
// 共通のパラメータスキーマ
const BaseParamsSchema = z.object({
  name: z.string(),
  arguments: z.record(z.unknown()).optional(),
});

// 特定のツール用にスキーマを拡張
const SpecificToolSchema = BaseParamsSchema.extend({
  arguments: z.object({
    specific: z.string(),
  }),
});
```

2. 型推論の活用

```typescript
// スキーマから型を生成
type ToolParams = z.infer<typeof BaseParamsSchema>;

// この型は自動的にスキーマと同期される
const params: ToolParams = {
  name: "example",
  arguments: { value: 123 }
};
```

## まとめ

MCP SDKにおけるZodの使用は、単なる型チェックツールではなく、APIの正しい使用を強制するための重要な機能です。この設計により：

1. 開発時の型安全性が保証される
2. ランタイムでの型の整合性が確保される
3. APIの使用方法が明確になる

実装例は[mcp-client-demo](https://github.com/yonaka15/mcp-client-demo)で確認できます。特に`src/worker.ts`のコードが参考になると思いｍ。

## 参考資料

- [Zod公式ドキュメント](https://zod.dev/)
- [Model Context Protocol](https://github.com/ModelTC/modelcontextprotocol)
- [mcp-client-demo](https://github.com/yonaka15/mcp-client-demo)