対象仕様:最新版。旧版との違いも明記しています。
目次
- 前提:最新版で何が変わったか
- 設計原則
- ツール設計
- トランスポートと運用
- セキュリティ
- MCP Apps(サーバーが提供する UI)
- 実装スタック
- テストと評価
- ドキュメント
- チェックリスト
0. 前提:最新版で何が変わったか
最新版は、MCP の公開以来最大の改訂です。旧版で書かれた記事やサンプルは古くなっている場合があるので、まず以下の変更点を押さえてください。
| 領域 | 変更内容 | 影響 |
|---|
| トランスポート | initialize ハンドシェイクを廃止(SEP-2575)。プロトコルバージョン・クライアント情報・capability は各リクエストの _meta に含める | ステートレス化。どのサーバーインスタンスでもどのリクエストでも処理できる |
| セッション | Mcp-Session-Id とプロトコルレベルのセッションを廃止(SEP-2567) | スティッキーセッションや共有セッションストアが不要になる |
| ディスカバリ | server/discover で capability を必要なときに取得 | サーバー側での実装が必須 |
| ルーティング | Streamable HTTP では Mcp-Method / Mcp-Name / MCP-Protocol-Version ヘッダが必須(SEP-2243) | ヘッダと本文が一致しているかの検証が必要 |
| スキーマ | inputSchema / outputSchema が 最新の JSON Schema に完全対応(SEP-2106) | oneOf / $ref / $defs などが使える |
| 拡張機能 | Extensions フレームワークを正式化(SEP-2133)。Tasks と MCP Apps は拡張機能扱いになった | extensions capability でネゴシエーションする |
| 非推奨 | Roots・Sampling・Logging を非推奨化(SEP-2577) | 少なくとも1年間は動作するので、その間に移行を計画する |
| 認可 | iss パラメータ(RFC 9207)、application_type、issuer バインディングなどを追加 | 認可サーバーとクライアント登録の見直しが必要 |
旧版との互換性:旧版に準拠したサーバーが今すぐ壊れるわけではありません。ただし、旧版は12か月の非推奨期間に入っています。
1. 設計原則
1.1 「API ラッパー」ではなく「エージェントが使う道具」として設計する
- サーバーの品質は「LLM が実際のタスクをどれだけうまく完了できるか」で決まります。
- 網羅的な API カバレッジと、よく使う流れをまとめたワークフローツールのバランスを取ります。迷ったら網羅性を優先します。エージェントは基本ツールを組み合わせて使えるからです。
- 1ツール = 1つの明確な操作にします。目的が曖昧な万能ツールは作らないでください。
1.2 コンテキストウィンドウを節約する
- ツールの説明文は簡潔かつ正確に書きます。ツール一覧そのものがモデルのコンテキストを消費します。
- 一覧を返すツールには、必ずフィルタやページネーションを付けます。
- 返すデータは必要なフィールドに絞ります。冗長なメタデータは省くか、
detail のようなオプションで切り替えられるようにします。
- ツール数が多いサーバーでは、カテゴリ分けや遅延ロードを想定した設計を検討します。
1.3 状態は「見える形」で受け渡す
- 最新版ではトランスポートに隠れた状態を持てません。
- 呼び出しをまたいで状態が必要な場合は、ツールが明示的なハンドル(例:
basket_id, job_id)を返し、モデルがそれを次の引数として渡す設計にします。
1.4 プリミティブの使い分け
| プリミティブ | 用途 | 使うかどうかを決めるのは |
|---|
| Tools | 副作用のある操作、動的な検索・計算 | モデル |
| Resources | 読み取り専用のデータやドキュメント(URI で識別) | アプリケーション・ユーザー |
| Prompts | 決まったワークフローのテンプレート | ユーザー |
- 迷ったら Tools にします。
- 変わらない参照データは Resources にすると、クライアント側でキャッシュしたり添付したりしやすくなります。
2. ツール設計
2.1 命名規則
- snake_case + サービス名のプレフィックスを使います(例:
github_create_issue, slack_send_message)。
- 他の MCP サーバーと同時に使われることを想定し、
create_issue のような汎用的な名前は避けます。
- 動詞で始めます(
get / list / search / create / update / delete)。
- 同じサーバー内では命名パターンを統一します。
2.2 サーバー名
- Python:
{service}_mcp(例:slack_mcp)
- TypeScript:
{service}-mcp-server(例:slack-mcp-server)
- バージョン番号は名前に入れません。
2.3 説明文(description)
- ツールが何をするか、いつ使うべきか、何を返すかを書きます。
- 説明文は実際の動作と厳密に一致させます。誇張や曖昧な表現は、ツールの誤用につながります。
- パラメータごとに説明・制約・例を書きます。
2.4 入力スキーマ
- Zod(TypeScript)または Pydantic(Python)で定義し、型・範囲・形式・必須かどうかを明示します。
- JSON Schema の
oneOf / anyOf / 条件付きスキーマ / $ref を活用できます。ルートは type: "object" のままにします。
- enum で表せる値は enum にします。自由入力の文字列は最小限にします。
2.5 出力(structured output)
- 構造化データを返すツールには
outputSchema を定義し、structuredContent で返します。
- 人間やモデルが読むためのテキスト(Markdown)も
content に併せて返すと、どのクライアントでも扱いやすくなります。
- レスポンス形式を
response_format: "json" | "markdown" で切り替えられるようにするのも有効です。
- Markdown:タイムスタンプを読みやすく整形し、
表示名 (ID) の形で書きます。
- JSON:全フィールドを一貫した型で返します。
2.6 ツールアノテーション
| アノテーション | 既定値 | 意味 |
|---|
readOnlyHint | false | 環境を変更しない |
destructiveHint | true | 破壊的な変更を行う可能性がある |
idempotentHint | false | 同じ引数で繰り返し呼んでも追加の影響がない |
openWorldHint | true | 外部の世界(インターネットなど)とやり取りする |
- すべてのツールに正しい値を設定します。クライアントはこれを見て確認ダイアログを出すかどうかなどを判断します。
- アノテーションはあくまでヒントで、セキュリティ上の保証ではありません。 クライアント側は、信頼できないサーバーのアノテーションを鵜呑みにしないでください。
2.7 ページネーション
limit パラメータを必ず尊重します(既定値は 20〜50 件程度)。
has_more, next_cursor(または next_offset), total_count を返します。
- 全件をメモリに読み込むことは避けます。
- 1回の応答の大きさに上限を設けます(例:約25,000トークン)。超える場合は切り詰め、「絞り込み条件を追加してください」と案内します。
{
"total": 150,
"count": 20,
"items": [ ... ],
"has_more": true,
"next_cursor": "eyJvZmZzZXQiOjIwfQ"
}
2.8 エラー処理
- ツールの実行エラーはプロトコルエラーにせず、結果オブジェクトの中で
isError: true として返します。モデルがエラーを読んで自分で修正できるようにするためです。
- エラーメッセージには次に何をすればよいかを含めます。
return {
isError: true,
content: [{
type: "text",
text: "結果が多すぎます(1,200件)。status='open' や label='bug' で絞り込んでください。"
}]
};
- 内部のスタックトレースや接続文字列などは外部に出さず、サーバー側のログに記録します。
- 外部 API のレート制限やタイムアウトは、サーバーの中で指数バックオフでリトライします。それでも失敗したら、分かりやすいエラーとして返します。
- 最新版では、リソースが見つからない場合のエラーコードが
-32002 から JSON-RPC 標準の -32602(Invalid Params) に変わりました(SEP-2164)。
3. トランスポートと運用
3.1 トランスポートの選び方
| 観点 | stdio | Streamable HTTP |
|---|
| 用途 | ローカル、CLI、デスクトップアプリ連携 | リモート、クラウド、複数クライアント |
| クライアント数 | 1 | 複数 |
| 構成の複雑さ | 低 | 中 |
| 認証 | 環境変数の資格情報 | OAuth 2.1 |
- SSE トランスポートは非推奨なので、新規開発では使いません。
- stdio サーバーは stdout にログを出さないでください(プロトコルが壊れます)。ログは stderr に出します。
3.2 ステートレス運用(最新版)
_meta に必要な情報を含む自己完結したリクエストを受け付けます。
server/discover を実装します。
Mcp-Session-Id やスティッキーセッションへの依存をなくします。これで水平スケールが簡単になります。
- リモートサーバーでは、ストリーミングよりステートレスな JSON レスポンスのほうが運用しやすいです。
3.3 キャッシュ
- 一覧系やリソース読み取りの結果には
ttlMs と cacheScope を付けられます(SEP-2549)。
cacheScope はテナントの境界です。ユーザーごとに異なるツール一覧を共有可能とマークすると、他のユーザーに情報が漏れます。迷ったら狭いスコープにします。
3.4 長時間処理(Tasks 拡張)
- 時間のかかる処理は、Tasks 拡張でタスクハンドルを返し、クライアントは
tasks/get / tasks/update / tasks/cancel で状態を確認します。
tasks/list は削除されました。旧版の実験的な Tasks API から移行してください。
- タスクの状態は外部ストアに保存し、どのインスタンスからでも参照できるようにします。
- 進捗の通知とキャンセルに対応し、タイムアウトと有効期限を設定します。
3.5 ユーザーへの追加入力要求(Elicitation)
- サーバーからクライアントへのリクエストは、クライアントのリクエストを処理している間だけ送れます(SEP-2260)。
- 追加入力が必要な場合は
InputRequiredResult と requestState を返し、クライアントが inputResponses を付けて再度呼び出します(SEP-2322)。
requestState は改ざんされうるので、署名または暗号化し、再呼び出し時に検証します。
3.6 可観測性
- W3C Trace Context を
_meta の traceparent / tracestate / baggage で伝播できます(SEP-414)。
baggage は下流にそのまま流れるので、機密情報を入れないでください。信頼境界を越えるときは中身を削除します。
- Logging 機能は非推奨になったので、stdio では stderr、リモートでは OpenTelemetry を使います。
- ツールごとに呼び出し回数・レイテンシ・エラー率を計測します。
- ログは JSON の構造化ログにし、リクエスト ID とツール名を必ず入れます。個人情報やトークンはマスクします。
- HTTP のサーバーにはヘルスチェックのエンドポイントを用意します。
- サーバーのバージョンをメタデータで返し、どのバージョンが動いているか追えるようにします。
3.7 非推奨機能の移行先
| 非推奨 | 移行先 |
|---|
| Roots | ツールのパラメータ、リソース URI、サーバー設定 |
| Sampling | LLM プロバイダーの API を直接呼ぶ |
| Logging | stderr(stdio)または OpenTelemetry |
4. セキュリティ
MCP サーバーは、ファイル・DB・SaaS などへの特権的なアクセスを持つインフラです。「チャットボット用のちょっとした連携」として扱わないでください。
4.1 認証・認可(リモートサーバー)
- OAuth 2.1 + PKCE に従います。Protected Resource Metadata(RFC 9728)で認可サーバーを公開します。
- 認可サーバーとリソースサーバー(MCP サーバー)を分離します。MCP サーバー自身が IdP を兼ねる構成は避けます。
- アクセストークンは自分のサーバー宛てに発行されたものだけを受け付けます(audience / resource indicator の検証、RFC 8707)。
- トークンパススルーは禁止です。クライアントから受け取ったトークンをそのまま下流の API に転送してはいけません。下流向けには別のトークンを取得します。
- 最新版の追加事項:
- 認可サーバーは
iss パラメータを返します(RFC 9207、ミックスアップ攻撃対策)。
- 動的クライアント登録では
application_type を尊重します。デスクトップや CLI のクライアントを web 扱いにしないでください。
- 登録した資格情報を発行元の issuer に紐づけ、リソースが移行したら再登録します。
4.2 ローカルサーバー
- API キーなどの秘密情報は環境変数やシークレットマネージャで管理し、コードや設定ファイルに直接書きません。
- root 権限のトークンや共有トークンは使わず、最小権限の資格情報を発行します。
- ローカルで HTTP サーバーを動かす場合:
0.0.0.0 ではなく 127.0.0.1 にバインドします。
Origin ヘッダを検証し、DNS リバインディング対策を有効にします。
- CORS は厳格モード(許可オリジンは空)を既定にします。
4.3 入力検証
- すべての入力をスキーマで検証します。
- ファイルパスは正規化し、許可されたディレクトリの外に出ないようにします(ディレクトリトラバーサル対策)。
- シェルコマンドを組み立てるときは引数を配列で渡し、文字列連結はしません(コマンドインジェクション対策)。
- URL を受け取る場合は、内部ネットワークやメタデータエンドポイントへのアクセスをブロックします(SSRF 対策)。
- スキーマ内の外部
$ref を自動で解決しないでください(SSRF・無制限フェッチのリスク)。スキーマの深さや検証時間に上限を設けます。
- Streamable HTTP では、
Mcp-Method / Mcp-Name ヘッダと本文の内容が一致しないリクエストを拒否します。ヘッダでルーティングして本文を実行する、という不一致はリクエストスマグリングにつながります。
4.4 プロンプトインジェクションとツールポイズニング
- ツールが返すデータ(メール本文、Web ページ、Issue のコメントなど)には、攻撃者が書いた指示が含まれている可能性があります。
- 信頼できない外部コンテンツであることが分かるように返します。
- 「読み取り」と「送信・削除」のツールを組み合わせると情報が外部に持ち出される恐れがあるため、破壊的な操作や外部送信には人間の確認を挟みます。
- ツールの説明文自体に悪意のある指示を仕込む「ツールポイズニング」があります。
- クライアントやホスト側では、承認済みのサーバーだけを許可し、ツール定義が変わったら再承認を求めます(ラグプル対策)。
- サーバーの提供元とサプライチェーン(パッケージ、依存関係)を確認します。
4.5 実行環境
- サーバーはコンテナやサンドボックスで、必要最小限の権限・ネットワークで実行します。
- ネットワークを分離し、許可された通信先だけに接続できるようにします。
- 外部 API にはレート制限とタイムアウトを設定します。
4.6 監査ログ
- どの人間ユーザーの依頼で、どのエージェントが、どのツールを、どの引数で呼んだかを追跡できるようにします。
- ログには秘密情報やトークンを残さないでください。
- 異常なパターン(大量の読み取り、普段使わないツールの呼び出しなど)を監視します。
5. MCP Apps(サーバーが提供する UI)
- サーバーは、サンドボックス化された iframe で表示される対話的な HTML UI を提供できます(SEP-1865)。
- UI テンプレートは事前に宣言し、ホストがプリフェッチやセキュリティレビューをできるようにします。
- UI から起きた操作も、通常のツール呼び出しと同じ監査・同意フローを通します。
6. 実装スタック
| 言語 | 推奨 SDK | 備考 |
|---|
| TypeScript(推奨) | v2:@modelcontextprotocol/server | v1(@modelcontextprotocol/sdk)はバグ・セキュリティ修正のみ。npx @modelcontextprotocol/codemod@latest v1-to-v2 . で移行できる |
| Python | 公式 Python SDK(FastMCP) | @mcp.tool デコレータ + Pydantic |
- 1つのプロジェクトで v1 と v2 を混ぜないでください。
- 共通処理(認証付き API クライアント、エラー整形、レスポンス整形、ページネーション)は共有ユーティリティにまとめます。
- I/O は async/await で書きます。
7. テストと評価
7.1 テスト
- MCP Inspector(
npx @modelcontextprotocol/inspector)で、ツール一覧・スキーマ・実際の呼び出しを確認します。
- 機能テスト(正常系・異常系)、結合テスト、セキュリティテスト(認証・入力検証・レート制限)、負荷テストを行います。
- ツールのハンドラーは MCP の層から切り離して、ユニットテストします。
- 複数のプロトコルバージョンを受け付けるなら、バージョンごとに結合テストをします。
7.2 LLM による評価(eval)
単体テストだけでは「LLM が使いこなせるか」は分かりません。実際の質問で評価します。
- 現実的で、複数のツール呼び出しが必要な質問を10問ほど作ります。
- 各質問は次の条件を満たすようにします。
- 他の質問に依存しない
- 読み取り専用の操作だけで答えられる
- 答えが1つに決まり、文字列比較で検証できる
- 時間が経っても答えが変わらない
- 正答率が低いツールは、説明文・パラメータ・出力形式を見直します。
8. ドキュメント
- すべてのツールと機能を説明し、主要な機能ごとに動く例を付けます。
- 必要な権限・スコープ・アクセスレベルを明記します。
- レート制限やパフォーマンス特性を記載します。
- セキュリティ上の注意点(何にアクセスするか、破壊的な操作は何か)を記載します。
9. チェックリスト
設計
最新版への対応
セキュリティ
テスト
参考資料