mcp-best-practices について
MCP Best Practicesは、TypeScript SDKで本番向けMCPサーバーを構築する開発者のための判断資料です。プロトコルのバージョン、トランスポート、セキュリティ、ツール設計、デプロイ時の注意点を扱います。
主な機能
- 01プロトコル版とSDKの挙動を比較
- 02トランスポート選択とステートレス設計を解説
- 03ツール登録・命名・スキーマを説明
- 04ページネーション・注釈・結果の扱いを解説
- 05セキュリティと互換性の注意点を提示
活用シーン
- 01TypeScript MCP SDK更新前の互換性確認
- 02ローカルまたはリモートのトランスポート選択
- 03ツールインターフェースと安全設計の確認
MCP 対応
MCPサーバーの実装や、クライアントに提供する機能についての指針をまとめたスキルです。それ自体はMCPサーバーではありません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tenequm/ skills --skill mcp-best-practicesインストール後、Claude Code で/mcp-best-practicesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
CHANGELOG.mdLICENSE.txtreferences/error-handling.mdextensions-registry.mdmcp-apps.mdsdk-bugs.mdsecurity-auth.mdspec-2026-07-28.mdtool-schema-guide.mdtransport-patterns.mdv2-migration.md詳細
MCP のベストプラクティス
TypeScript SDK を使用して本番環境向け MCP サーバーを構築するための意思決定リファレンスです。チュートリアルではありません。すでに動作するサーバーがあり、それを正確、高速、安全にする必要があることを前提としています。
クイックリファレンス
| コンポーネント | 現在の状況 | 備考 |
|---|---|---|
| 仕様(リリース済み) | 2026-07-28 (仕様) | ステートレス/セッションレスへの大幅な刷新。「仕様 2026-07-28」と references/spec-2026-07-28.md を参照してください |
| 仕様(引き続きデプロイ済み) | 2025-11-25 | デプロイ済みソフトウェアの大半で引き続き使われており、TS クライアントのデフォルトです。ただし、Claude Code は現在、対応する HTTP サーバーと 2026-07-28 でネゴシエーションします |
| TS SDK(現行) | v2.3.1(2026-10-05):/server、/client、/core は 2.3.1、/node は 2.1.1、/express と /hono は 2.0.2、/fastify は 2.0.1(バージョンは連動しなくなりました) | createMcpHandler はデフォルトで両方の世代に対応します。クライアントは、別途指定しない限り 2025 世代のプロトコルを使用します |
| TS SDK(レガシー) | v1.32.1(@modelcontextprotocol/sdk) | v2 GA 後 6 か月以上にわたってバグ修正とセキュリティ修正を提供します。2025-11-25 より後のリビジョンはありません。ソースは v1.x ブランチ にあります |
| JSON Schema | 2020-12 がデフォルト(2019-09/draft-07 は v2.0.0 以降で受け入れ) | - |
| トランスポート | Streamable HTTP(リモート)、stdio(ローカル) | SSE と WebSocket は v2 で削除されました |
| 拡張機能 | MCP Apps(安定版、SEP-1865)、Auth Extensions(公式)、Tasks(ext-tasks) | ドメイン固有の WG |
| レジストリ | プレビュー。v0.1 API は 2025-10-24 から凍結されています(レジストリ) | GA は保留中です |
v2 のインポート(現行):
import { McpServer } from "@modelcontextprotocol/server";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/server";
import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/core";
v1 のインポート(レガシー系列、引き続き広くデプロイされています):
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2 つの世代
2026-07-28 のリリース後、意思決定に最も関わる事実は次のとおりです。TS SDK をアップグレードしても、クライアントが新しい仕様に移行するわけではありません。ただし、ほかの SDK やクライアントでは、このデフォルトは共通ではありません。rmcp(Rust)>= 3.0 は初期状態で 2026-07-28 を通知するため、依存関係を更新しただけでサーバーが気付かないうちに最新のワイヤープロトコルへ移行することがあります。SDK をアップグレードするたびに、通知するリビジョンを再確認してください。
2024-10-07 から 2025-11-25 までのすべてのリビジョンは initialize で開始し、共通のワイヤー動作を備えています。SDK ではこの世代を レガシー と呼びます。2026-07-28 からは モダン 世代となり、initialize はなく、代わりに server/discover による通知と、すべてのリクエストに付与される _meta エンベロープがあります。選択は明示的に行います。
versionNegotiation.mode | 動作 |
|---|---|
なし/'legacy' | 2025 年の initialize ハンドシェイクをバイト単位でそのまま使用します。プローブは行いません。これがデフォルトです。 |
'auto' | server/discover でプローブし、2025 のみをサポートするサーバーに対しては initialize にフォールバックします |
{ pin: '2026-07-28' } | このリビジョンのみを使用し、それ以外は使用しません。ピン留めした場合、フォールバックは行われません |
サーバーは両方の世代に対応してください。 Claude Code の現行 MCP ランタイムは HTTP サーバーに 2026-07-28 をサポートしているか確認し、サポートしている場合はその仕様を使用します(stdio では順次展開中です)。そのため、モダン対応を通知するサーバーは、現在すでに主流のクライアントからモダン世代のトラフィックを受け取ります。v2 の createMcpHandler はデフォルトで、モダン世代と並行して 2025 世代のリクエストにも対応します(legacy: 'stateless')。両方の世代でテストしてください。このスキル全体のステートレス設計に関するガイダンスに従えば、その対応は容易です。
ツール類:SDK ドキュメント(v2)、デフォルトでは legacy として接続する MCP Inspector(--protocol-era modern を渡してください。references/spec-2026-07-28.md の「各世代でのテスト」を参照)、適合性テストスイート、およびスキャフォールディング用の mcp-server-dev プラグインがあります。
サーバーのセットアップ
トランスポートの選択
| シナリオ | トランスポート | 主な設定 |
|---|---|---|
| リモート、v2(K8s、CF Workers、Node) | createMcpHandler(factory) | どちらの世代にも対応し、リクエストごとにステートレスで処理します |
| リモート、v1/ステートレス | WebStandardStreamableHTTPServerTransport | sessionIdGenerator: undefined、enableJsonResponse: true |
| リモート、ステートフル(2025-era のみ) | WebStandardStreamableHTTPServerTransport | sessionIdGenerator: () => randomUUID() |
| ローカル CLI/Claude Desktop | StdioServerTransport/v2 serveStdio(factory) | デフォルト |
| レガシー SSE クライアント | v2 では SSE が削除されています。Streamable HTTP に移行してください | - |
ステートレスパターン(リモートデプロイに推奨)
リクエストごとに新しいサーバーを用意するのが標準的なパターンです。また、[email protected] 以降はこれが必須です。「1 つのサーバーオブジェクト、または 1 つのステートレストランスポートをすべての HTTP リクエストで使うアプリは、このアップグレード後、2 回目のリクエストで失敗します。」 インスタンスの共有は、v1.26.0 より前のバージョンでもクライアント間のデータ漏えいを引き起こしていました(GHSA-345p-7cg4-v4c7)。v2 では、createMcpHandler に ファクトリー を渡すと、リクエストごとに 1 回呼び出されます。
import { createMcpHandler, hostHeaderValidationResponse, McpServer, originValidationResponse } from "@modelcontextprotocol/server";
const handler = createMcpHandler(() => {
const server = new McpServer({ name: "my-server", version: "1.0.0" });
registerTools(server); // register tools, resources, prompts inside the factory
return server;
});
export default {
async fetch(request: Request): Promise<Response> {
// createMcpHandler is deliberately validation-free: guard Host/Origin in front of it.
const rejected =
hostHeaderValidationResponse(request, ["mcp.example.com"]) ??
originValidationResponse(request, ["app.example.com"]);
return rejected ?? handler.fetch(request);
},
};
v1 では同じ形式で、リクエストごとに新しい McpServer と WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true }) を用意し、connect()、handleRequest() を実行した後、finally で両方を close() します。詳しくは references/transport-patterns.md を参照してください。
McpServer はリクエストごとに用意する必要がありますが、定数入力はそうではありません。モジュールレベルに配置してください:Zod スキーマ、アノテーションオブジェクト({ readOnlyHint: true, ... })、ツールの説明文字列、決済設定、上流 API クライアント。(v2.3.0 ではツールスキーマも遅延変換されるため、リクエストごとのサーバーでも、リクエストのたびにすべてのツールを再変換することはありません。)
POST のみをルーティングする場合(一般的なステートレス構成)、GET /mcp には明示的に 405 Method Not Allowed を返してください。SSE ストリームを提供しない場合、仕様ではこれが必須です。また、公式 TS クライアントは 405 をストリームがないことを示す問題のない応答として扱います。一方、空の 200 を返すと再接続が殺到します。
トランスポート、セッション、HTTP/2 の注意点、K8s のデプロイについては、
references/transport-patterns.mdを参照してください。
フレームワークとの統合
handler.fetch は Web 標準に準拠しているため、Workers、Deno、Bun、Hono ではアダプターは不要です。Express の場合、createMcpExpressApp(options) は JSON パーサーと DNS リバインディング対策を備えた express() アプリを返します。@modelcontextprotocol/node の toNodeHandler(handler) を使ってハンドラーをマウントしてください。createMcpHonoApp(options) は Hono 用の同等機能です。Cloudflare Workers では、モジュールスコープで preloadSchemas() を呼び出してください。v2 の workerd ビルドでは自動的に実行されます。例については references/transport-patterns.md を参照してください。
ツールの設計
登録 API
v1(レガシー系列) - server.tool(name, description, zodShape, annotations, handler)。位置引数のオーバーロードは曖昧です。v2 の下記と同じフィールドを使いますが、outputSchema はありません。v2 では完全に削除されました。
v2(現行) - 設定オブジェクトを使う registerTool():
server.registerTool("search_docs", {
title: "Document Search",
description: "Search documents by keyword or phrase",
inputSchema: z.object({
query: z.string().describe("Search query"),
max_results: z.number().optional().describe("Max results (default 20)"),
}),
outputSchema: z.object({
results: z.array(z.object({ id: z.string(), text: z.string() })),
has_more: z.boolean(),
}),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
}, async ({ query, max_results }) => {
const result = await fetchDocs(query, max_results);
return {
// Both channels carry IDENTICAL bytes. Divergent payloads = the text block
// silently vanishes on Claude Code/Codex/Copilot. See "Tool Result Delivery" below.
structuredContent: result,
content: [{ type: "text", text: JSON.stringify(result) }],
};
});
命名
仕様 2025-11-25(SHOULD。MUST ではありません):1~128 文字、大文字と小文字を区別し、使用できるのは A-Za-z0-9_-. のみです。推奨:search_docs、get_user_profile、admin.tools.list。非推奨:search(汎用的な名前はサーバー間で衝突します)、Search Docs(スペースは使用できません)。複数のサーバーがアクティブな場合は、サービス名をプレフィックスにします(github_*、jira_*)。LLM は汎用的な名前を混同するためです。プレフィックスはツール名自体に含めてください。仕様では、「サーバーの name(serverInfo 由来)はサーバー間で一意であるとは限らず、識別に使用することは SHOULD NOT」 と明記されています。そのため、アグリゲーターが安全なプレフィックスを自動で設定することはできません。
スキーマルール
すべてのフィールドに .describe() を指定してください。LLM はこれを使って引数を生成します。3 つの構文(z.union()、生の JSON Schema、z.transform())は黙って失敗します。クライアント側の AJV 厳格検証も同様です。詳しくは下記の「既知の SDK のバグ」を参照してください。
ページネーションは、ほとんどのサーバーが最初に必要とする基本機能です。50 件以上のエントリーを含む tools/list または resources/list では、ページネーションを実装してください。プロトコルの cursor は不透明です。解析したり生成したりせず、nextCursor がなくなるまでループしてください。ツール内の offset/limit 引数とは別のものです。
Zod から JSON Schema への変換ルール、outputSchema/structuredContent のパターン、テキスト以外のコンテンツタイプ、その他のツール定義フィールド(
icons、listChanged、execution.taskSupport)、および残りの基本機能(プロンプト、リソース、リソーステンプレート、補完、キャンセル)については、references/tool-schema-guide.mdを参照してください。
アノテーション
すべて任意のヒントです(仕様により、信頼できないサーバーからのヒントは信頼できません)。
| アノテーション | デフォルト | 意味 |
|---|---|---|
readOnlyHint | false | ツールは実行環境を変更しません |
destructiveHint | true | 破壊的な更新を実行する可能性があります(readOnly=false の場合のみ) |
idempotentHint | false | 同じ引数で繰り返し呼び出しても、追加の効果はありません |
openWorldHint | true | 外部エンティティ(API、ウェブ)とやり取りします |
正確に設定してください。クライアントはこれらを、同意の確認や自動承認の判断に使用します。
「致命的な三要素」:1 つのエージェントがプライベートデータへのアクセス、信頼できないコンテンツへの接触、外部との通信をすべて行えると、データ窃取が可能になります(悪意あるカレンダーイベント、MCP カレンダーサーバー、コード実行ツールを使った実証例があります)。1 つのエージェントがこの 3 つすべてを持たないよう、ツールセットを設計してください。
ステートフルなツール
2026-07-28 時点ではプロトコルレベルのセッションがないため、呼び出し間の状態はサーバーが発行したハンドルを通常のツール引数として渡すことで管理します。作成ツールが { basket_id: "bsk_a1b2c3" } を返し、後続のツールが basket_id を引数として受け取り、モデルがその値を引き継ぎます。ハンドルは名前であり、権限を与えるものではありません。呼び出しのたびに呼び出し元を検証し、十分なエントロピーを持たせて不透明な値にし、保持ポリシーを作成ツールの説明に記載してください。期限切れまたは不明なハンドルの場合はツール実行エラーを返し、モデルが新しい状態を作成して復旧できるようにします。完全なルールについては、references/spec-2026-07-28.md を参照してください。
ツール結果の受け渡し:content と structuredContent
陥りやすい問題:ツールがテキストの content ブロックと structuredContent の両方を返すと、主要なクライアントのいくつか(Claude Code、Codex CLI、VS Code Copilot、Goose)はテキストブロックを黙って破棄し、structuredContent だけをモデルに転送します。2 つのペイロードが異なる場合、人間が読める方は表示されなくなります。これは仕様で制約されていないクライアントの動作であり、SDK による変換ではありません。両方がモデルに届くと想定して、両方のチャンネルを返さないでください。
実測済み - Claude Code 2.1.165(MCP 2025-11-25)
claude -p --output-format=stream-json を使い、モデルが受け取った正確な tool_result を読み取って測定しました。
| ツールの返却内容 | モデルが受け取る内容 |
|---|---|
structuredContent のないテキストブロック 1 つ | テキストがそのまま渡されます |
content: [] + structuredContent | content スロット内の文字列としての JSON.stringify(structuredContent) - 動作します |
テキストブロック + structuredContent | テキストブロックは通知なしに破棄され、structuredContent が優先されます |
テキスト + structuredContent + outputSchema | 同じです - outputSchema による違いはありません |
structuredContent のないテキストブロック 2 つ | 両方ともそのまま保持されます |
Claude Code では、structuredContent はモデルに渡される独立した型付きチャネルではありません。標準の tool_result の content スロットに文字列化されるため、同等の JSON をテキストとして渡す場合と同じトークン数がかかります。より低コストな構造化データや、帯域外のデータを利用できるわけではありません。
これは意図的な仕様です。Anthropic のメンテナーによると(anthropics/claude-code#9962)、Claude Code v2.0.21 で structuredContent のサポートが導入され、「両方の形式が存在する場合は structuredContent をデフォルトにしました……エージェントのパフォーマンスを最適化するためです」。Laravel、Roblox Studio、YouTube など、互いに無関係なサーバーでも再現されています。これはホスト側の優先順位であり、サーバーのバグではありません。
仕様に実際に書かれていること(2025-11-25)
優先順位のルールはありません。両方が存在する場合に、クライアントがどちらのフィールドを優先すべきかは仕様に記載されていません(Discussion #1563)。この欠落が、クライアント間で挙動が異なる根本原因として記録されています。関連する規範的な記述は、後方互換性のための SHOULD のみです。「構造化コンテンツを返すツールは、シリアライズされた JSON も TextContent ブロックで返す SHOULD があります。」 公式の TypeScript SDK は両方のフィールドをそのまま渡します。観測される content への文字列化は、SDK ではなくホストハーネスによるものです。
クライアント間の挙動(上記の表は Claude Code のみ)
| クライアント | content と structuredContent の両方が存在する場合 |
|---|---|
| Claude Code CLI、OpenAI Codex CLI、VS Code Copilot、Goose | シャドウ - structuredContent のみがモデルに届きます(テキストは破棄されます) |
| Cursor、Claude.ai web、ChatGPT MCP connector | content を優先する/両方をモデルに提示します |
| Google ADK(フレームワーク) | デフォルトでは両方を転送します。content のみの転送はオプトインです |
(Claude Code 以外の行は、stream-json ハーネスではなく、イシュートラッカーやメンテナーの発言に基づいています。正確な受け渡し方はクライアントのバージョンによって異なるものとして扱ってください。)
サーバー作成者向けのルール
- 禁止事項:
contentとstructuredContentの内容を異なるものにしないでください(例:テキストはレンダリング済みの ASCII テーブル、構造化データは異なる JSON)。シャドーイングクライアントではテキストが何も表示されず、JSON だけがモデルに届きます。 - 推奨事項:
structuredContentを出力する場合は、テキストブロックにも同じバイト列を複製してください:content: [{ type: "text", text: JSON.stringify(payload) }]。これは仕様が後方互換性のために推奨している方法です。シャドーイングクライアントは構造化データのコピーを使い、それ以外のクライアントは同一のテキストにフォールバックするため、いずれの場合もモデルにデータが届きます。シャドーイングクライアントではテキストが破棄されるため、複製してもトークン数は二重になりません。 - ツールごと、モードごとに出力チャンネルを1つにすることを推奨します。 人が読みやすい形式(テーブル、要約)をモデルに届けるには、テキストのみで返し、
structuredContentは使わないか、format: "table" | "json"引数を用意します(table→ テキストのみ、json→ JSON を両方のチャンネルに複製)。どちらの方法も Claude Code で実証済みであり、呼び出しごとにチャンネルを1つに保てます。 outputSchemaが制御するのはクライアント側の検証だけです。シャドーイングクライアントでテキストブロックが残るようにはなりません。
content ブロックはテキスト専用ではありません。image、audio、resource_link、埋め込みの resource ブロックがあり、アノテーション(audience、priority、lastModified)も付けられます。これらと画像プレビュー+URLのパターンについては、references/tool-schema-guide.md を参照してください。
エラー処理
LLM からの見え方が異なる、2種類のメカニズムがあります。
| 種類 | LLM に見えるか | 用途 |
|---|---|---|
ツールエラー(CallToolResult の isError: true) | はい - 自己修正が可能になります | 入力検証、API の失敗、ビジネスロジックのエラー |
| プロトコルエラー(JSON-RPC エラーレスポンス) | 場合によります - クライアントが公開することがあります | 不明なツール、不正なリクエスト、サーバーのクラッシュ |
SEP-1303(spec 2025-11-25 に統合)に従い、入力検証エラーはプロトコルエラーではなく、ツール実行エラーにしなければなりません。LLM が自己修正できるように、「日付は未来である必要があります」という内容を見せる必要があります。
// DO: Tool execution error - LLM can self-correct
return {
isError: true,
content: [{ type: "text", text: "Date must be in the future. Current date: 2026-03-25" }],
};
// DON'T: throw for validation - you lose control of what the LLM sees
throw new ProtocolError(ProtocolErrorCode.InvalidParams, "Invalid date");
例外をスローするとどうなるか: ツールハンドラー内では、SDK はスローされた ProtocolError/McpError を含むあらゆる例外を、isError: true の結果に変換します(例外は UrlElicitationRequiredError です)。メッセージは残りますが、エラーのコードと error.data は破棄されるため、そこに埋め込まれた構造化データはクライアントに届きません。isError の結果は自分で返してください。このため、x402/MPP エコシステムでは structuredContent を含む isError: true の結果が標準化されています。
エラーの分類全般、コード例、決済エラーのパターン、および
-32042を「Payment Required」コードとして使用できない理由については、references/error-handling.mdを参照してください。
リソースと指示
サーバーコンストラクターで instructions を設定します。これは、サーバーの使い方を LLM に伝えるシステムレベルのヒントです。
const server = new McpServer({
name: "docs-api",
version: "1.0.0",
instructions: "Knowledge base API. Use search_docs for full-text search, get_doc for retrieval by ID. All tools are read-only.",
});
Claude Code ではツール検索がデフォルトで有効になっているため、セッション開始時に読み込まれるのはツール名とサーバーの instructions のみです。モデルがツールを読み込むかどうかを判断する前に読む主な情報は、現在 instructions です。Claude Code は各ツールの説明と各サーバーの指示を2,048 文字で切り詰めるため、重要な内容を先頭に置いてください。
ガイドや構造化データは、docs:// URI スキームのリソース(server.resource(...))として提供します。references/tool-schema-guide.md の「その他のサーバープリミティブ」を参照してください。
パフォーマンス
トークン肥大化の軽減
ツール定義は会話が始まる前からコンテキストウィンドウを消費します。GitHub MCP:80 個のツールで 20,444 トークン(SEP-1576)。
戦略:
- サーバーあたり 5〜15 個のツール - コミュニティで最適とされる範囲です。これを超える場合は分割してください。
- 成果指向のツール - 複数ステップの操作を単一のツールにまとめます(例:
get_user+list_orders+get_statusではなくtrack_order(email))。 - レスポンスの粒度 - 生の API ダンプではなく、厳選した結果を返します。800 トークンのユーザーオブジェクトではなく、20 トークンの要約を返します。
outputSchema+structuredContent- プログラムから利用するクライアント/PTC クライアント向けの型付き出力です。ただし、シャドウイングを行うクライアントでは、structuredContentはモデルのコンテキスト内でテキストと同じトークンコストで文字列化されるため、帯域外で無料に使えるチャネルではありません(「ツール結果の配信」を参照)。- 動的なツール読み込み - リクエストのコンテキストに応じて、関連するツールのサブセットのみを登録します(例:
?tools=search,fetchクエリパラメーター)。セッション中にセットが変わる場合は、listChangedと組み合わせます。会話中ではなく、接続ごとにセットを変えてください。ツール定義はプロンプトのプレフィックスに含まれるため、「会話中にツール定義を追加または削除すると、そのキャッシュが無効になり、キャッシュミスによって、削除した定義自体より多くのトークンを消費する可能性があります。」 クライアントは、通知したttlMsの期限前であっても、list_changedを受け取った時点でキャッシュ済みリストを古いものとして扱う必要があります。 - 段階的なツール検出/コードモード - 大規模なカタログを扱うクライアントでは、
search_toolsメタツールや、モデルのコンテキスト外でstructuredContentを使用するプログラムによるツール呼び出しがますます利用されています(クライアントのベストプラクティス)。厳選され、適切に説明されたツールが、こうした処理を支えます。
結果サイズの上限(クライアントごと)
クライアントは、大きなツール結果を通知せずに切り詰めます。対象とするクライアントのうち、最も厳しい上限に合わせてください。
| クライアント | デフォルトの上限 | 設定可能 |
|---|---|---|
| Claude Code | 25,000 トークン(10k で警告)。50,000 文字を超えるテキスト結果はファイルに保存され、そのパスに置き換えられます。トークン数は関係ありません | MAX_MCP_OUTPUT_TOKENS env。ツールごとの _meta["anthropic/maxResultSizeChars"] で 50k の保存しきい値を最大 500,000 文字まで引き上げられます。また、テキストではトークン上限の範囲内に制限されるのではなく、トークン上限を置き換えます |
| OpenAI Codex CLI | 現行のすべてのモデルで 10,000 トークン(約 40KB)。bytes モードで 10,000 が維持されるのは、レガシーの gpt-5.2 と不明なモデルのフォールバックのみです | tool_output_token_limit config |
| Gemini CLI | 40,000 文字(先頭 20%/末尾 80% を残して切り詰め。全出力はファイルに保存) | settings。0 以下にすると無効 |
サーバー側で独自の上限を適用してください。references/tool-schema-guide.md の「結果サイズの上限と切り詰め」を参照してください。ここで明記しておくべきルールは 2 つです。isError の結果は決して切り詰めないでください(支払い/認証の確認は完全な状態で保持する必要があります)。また、結果は小さく保ってください。Claude Code 自体も、約 11,000 文字を超えるエラーテキストを先頭と末尾の 5,000 文字に切り詰めます。さらに、クライアントの上限は接続ごとのプロパティとして扱い、すべてのツールスキーマに上書き用の引数を増やすのではなく、URL クエリパラメーター(?tools= と併せて ?max_chars=)として受け取ってください。
長時間実行されるツール
Claude Code には3つの独立したタイマーがあり、進捗に応じてリセットされるのはそのうち1つだけです。
| タイマー | デフォルト | 進捗によってリセットされますか? |
|---|---|---|
サーバーごとの timeout | 「ツール呼び出しごとの厳格な実時間制限」(未設定の場合は約28時間) | いいえ |
| アイドルタイムアウト | HTTP は5分、stdio は30分(CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT) | はい - 指定時間内に応答も進捗もない呼び出しは中断されます |
| 最初のバイトのタイマー(HTTP のみ) | max(60 s, tool timeout, MCP_TIMEOUT) | 該当なし - 各リクエストの最初の応答バイトまでを計測します |
そのため、時間のかかる呼び出しでは notifications/progress を送信してください(アイドルタイマーをリセットできます)。ただし、実時間制限を超えて実行できるわけではありません。最初のバイトのタイマーは enableJsonResponse: true で問題になります。JSON モードの応答はツールの処理が完了するまでバイトを送信しませんが、SSE 応答はすぐにストリーミングを開始します。メインの会話で2分を過ぎても実行中の呼び出しはバックグラウンドタスクに移されるため、時間のかかるツールによってセッションがブロックされることはなくなります。ただし、処理を完了する必要はあります。
上限を超えて処理するには、サーバーで生成したハンドルをすぐに返し、呼び出し側がポーリングするようにします(「ステートフルなツール」を参照)。または、まさにこの用途のために設計された io.modelcontextprotocol/tasks 拡張を採用してください。この拡張は CreateTaskResult を返し、クライアントは tasks/get を使ってポーリングします。Tasks はリクエストごとにオプトインします。クライアントが Tasks 機能を宣言していない場合に、同期的に呼び出しを処理できないサーバーは、暗黙にブロックするのではなく、拡張の名前を示す -32021(Missing Required Client Capability)を返さなければなりません。
パラメーターのないツール
入力のないツールでは、undefined や省略ではなく、明示的な空のスキーマを使用してください。
inputSchema: { type: "object" as const, additionalProperties: false }
セキュリティ
主な脅威(2025~2026年の実際のインシデント)
| 攻撃 | 例 | 緩和策 |
|---|---|---|
| ツールポイズニング | 説明文に隠された指示(WhatsApp MCP、2025年4月) | ツールの説明文を確認し、クライアント側で表示する |
| サプライチェーン | 悪意のある npm パッケージ(Smithery の侵害、2025年10月) | バージョンを固定し、依存関係を監査する |
| Stdio 設定インジェクション | ユーザー制御の入力がサニタイズされずに StdioServerParameters に渡される(OX Security、2026-04-15) | クライアントコードで stdio 設定をサニタイズし、ファーストパーティのサーバーを優先する。「仕様による動作」として扱われており、SDK では修正されていません |
| サーバー間のシャドーイング | 悪意のあるサーバーが正規のツール名を上書きする | ツール名にサービスのプレフィックスを付け、ツールの提供元を検証する |
| トークン窃取 | 広範なスコープを持つ、過剰な権限の PAT | スコープを最小限にし、OAuth 2.1 Resource Indicators(RFC 8707)を使用する |
| トークンパススルー | サーバーが、そのサーバー向けに発行されていないトークンを受け入れる、または転送する | audience クレームを検証し、クライアントのトークンを上流 API に中継しない |
| 混乱した代理人 | DCR を介してプロキシサーバーの同意 Cookie が悪用される | サードパーティの認証に転送する前に、クライアントごとに同意を得る |
| セッションハイジャック | 盗難または推測されたセッション ID によるなりすまし | 暗号学的に安全な乱数の ID を使い、ユーザー ID に紐付け、認証には決して使用しない |
| クライアント間のレスポンス漏えい | 複数のクライアントで McpServer/トランスポートを共有して再利用する(CVE-2026-25536、v1.10.0~1.25.3 に影響) | SDK >= v1.26.0 を必須にする。リクエストごとにサーバーとトランスポートを用意する |
| audience が異なるトークン | SDK のベアラー認証が、別のサービス向けに発行されたトークンを受け入れていた(GHSA-rvq5-wwqv-78pq、sdk <= 1.31.0、server <= 2.2.0) | アップグレードし、さらに expectedResource を設定する。デフォルトでは無効です |
| セッション間のタスクアクセス | v1 の実験的なタスクがセッションに紐付けられていない(GHSA-22jm-h49p-29qw、taskStore を使用する sdk 1.24.0~1.31.0) | sdk >= 1.32.0 にする。ステートレスサーバーではタスクが共有されるため、すべてのタスクリクエストを認可する |
| UriTemplate ReDoS | 悪意のある URI パターン(CVE-2026-0621) | v1.25.2+/v2.0.0-alpha.1+ にアップグレードする |
最低バージョン:@modelcontextprotocol/sdk >= 1.32.0、@modelcontextprotocol/server >= 2.3.0(認証ミドルウェアを使用する場合は、/express >= 2.0.2 も必要です)。同じ週に公開された追加のアドバイザリが2件あります。いずれもクライアント側の問題です(サーバーが選択した認可サーバーへの認証情報の送信、クロスオリジンリダイレクト)。詳しくは references/security-auth.md を参照してください。
一般的なセキュリティ対策も引き続き重要です。ツールの境界で入力を検証し、ユーザーごとのアクセス制御を徹底し、レート制限を設け、ツールの出力をサニタイズし、ツール入力をシェルコマンドに決して埋め込まず、送信先の取得処理ではプライベート IP をブロックし、ローカルサーバーは 127.0.0.1 にバインドしてください。new McpServer(info, { maxToolInputElements: 10_000 }) で引数のサイズを制限できます(sdk 1.32.0/server 2.3.0、デフォルトでは無効)。サイズ超過の呼び出しには、スキーマ検証の実行前に isError の結果が返されます。
サーバー側の要件(規範仕様)
Originヘッダーを検証します。ただし、存在し、かつ無効な場合にのみ拒否します。「Originヘッダーが存在し、無効である場合、サーバーは」 403 を返す必要があります。Originをまったく送信しないクライアントも実際に存在します。未指定なら一律 403 を返すと、そうしたクライアントを締め出してしまいます。- チェックを有効にします。 チェックを実装しない限り、
Host/Originは検証されません。createMcpHandlerは設計上、検証を行いません。Workers/Deno/Bun で単にexport default handlerとするだけでは保護されません(#2844)。hostHeaderValidationResponse/originValidationResponseを前段に配置してください。- 生のトランスポートでは
enableDnsRebindingProtectionのデフォルト値はfalseであり、このオプションはallowedOrigins/allowedHostsとともに、外部ミドルウェアの使用が推奨されているため@deprecatedになっています。 createMcpExpressApp/createMcpHonoAppは、デフォルトではループバックにバインドする場合に限り、Host と Origin の両方を検証します。それ以外の特定のホスト(192.168.1.10、mcp.internal)にバインドすると、両方のチェックが暗黙的にスキップされます(#2843)。allowedHostsを明示的に渡してください。allowedOriginsは、2.3.0 以降、ブラウザー拡張機能のクライアント向けに<scheme>://*(例:moz-extension://*)を受け付けます。
- 現代の通信では
MCP-Protocol-Versionは省略できません。 セッションレス化の後もこのヘッダーは維持されています。「MCP エンドポイントへのすべての POST リクエストには、MCP-Protocol-Versionヘッダーを含めなければならず」、その値は本文の_metaにあるio.modelcontextprotocol/protocolVersionと一致しなければなりません。一致しない場合、サーバーはHeaderMismatchエラーを伴う400 Bad Requestを返さなければなりません。バージョンは_metaとヘッダーの両方に、意図的に重複して含まれます。中間装置はヘッダーに基づいてルーティングし、サーバーは本文に基づいて処理するため、両者が一致する必要があります。 - バージョンの宣言の有無ではなく、どのバージョンを宣言しているかについて柔軟に対応します。 2025 年頃の通信では、ひとつのバージョンだけを強制するのではなく、宣言された複数のバージョンを受け入れてください。
2024-11-05を通知するクライアントは今も使われています。また、2025-06-18より前のクライアントをサポートするサーバーは、ヘッダーのないリクエストを2025-03-26として扱ってもかまいません。そうしたクライアントをサポートしないサーバーは、ヘッダーのないリクエストを拒否しなければなりません。
認証(OAuth 2.1)
MCP が規範的に求めているのは 2.0 ではなく OAuth 2.1(draft-ietf-oauth-v2-1-13)です。PKCE は必須で、インプリシットフローは削除されています。サーバーはリソースサーバーです。クライアントは、トークンをサーバーに紐付けるリソースインジケーター(RFC 8707)を送信しなければなりません。
- オーディエンスを検証します。自分のサーバー向けに発行されたものではないトークンを拒否してください(そのまま通すことは禁止されています)。SDK の
requireBearerAuthでは、expectedResource: new URL("https://mcp.example.com/mcp")を指定し、さらにトークンのaudからAuthInfo.resourceを設定する検証関数が必要です。前者だけを設定するとすべてのリクエストが 401 になり、どちらも設定しないとオーディエンスは検証されません。PKCES256、短期間で有効期限が切れるトークン、必要最小限のスコープを使用してください(ツールごとに権限を引き上げるにはscopeChallenge: requireScopes(...)を使用します。サーバーは 2.1.0 以降)。 - 任意認証ではクライアントによって挙動が分かれます。 匿名ユーザーとサインイン済みユーザーの両方にひとつのエンドポイントを提供する場合、一部のクライアントは
401を受け取ってから OAuth を開始します。そのため、認証情報のないtools/listにサーバーが200を返すと、クライアントは匿名のまま接続されます。「接続済み、N 個のツール」は認証された証拠にはなりません。一方、無条件に 401 で拒否すると、200とisErrorによるチャレンジを想定する決済クライアントが動作しなくなります。エンドポイントごとに意図して選択してください。 - Keycloak、Auth0 など、テスト済みの検証ライブラリを使用してください。独自に実装しないでください。Authorization ヘッダー、トークン、シークレットをログに記録してはいけません。
- RFC 9207 の
issに関する相互運用性の落とし穴:authorization_response_iss_parameter_supported: trueを通知すると、一部のクライアントが破棄するコールバックissを、厳格なクライアントが検証しなければならなくなります。このフラグはfalseとして通知しつつ、iss自体は引き続き送信してください。references/security-auth.mdを参照してください。
セキュリティ攻撃/緩和パターンと認証の実装詳細については、
references/security-auth.mdを参照してください。
既知の SDK のバグ
[email protected] / [email protected] 時点で必ず把握しておくべき事項:
- リリース済みの v1 では、すべてのバージョンで
z.union()/z.discriminatedUnion()が空のスキーマを黙って生成します(#1643、バックポートの PR #2017 は引き続き未マージ)— 代わりにフラットなz.object()+z.enum()を使用してください。 - セキュリティ上の最低要件は sdk >= 1.32.0 / server >= 2.3.0 です — 上記の脅威一覧を参照してください。
- v1 では
connect()の前に登録してください。 後から登録すると、リリース済みの v1 ではすべて例外が発生します(#893。v1 の修正はマージされていますが、1.32.1 の後も未リリースです)。v2 では、コンストラクターのcapabilitiesで機能を宣言すれば動作します。 - クライアントの AJV の厳密な検証では、不要な要素が除去されていない
structuredContentの追加プロパティが拒否されます — 上流データに対して先に.parse()を実行するか、意図した追加プロパティを許可する場合は.passthrough()を使用してください。 - v1 はすべてのツールスキーマに
"$schema": "http://json-schema.org/draft-07/schema#"を設定します(1.32.1 でも同様です)。そのため、厳密な 2020-12 クライアントではツール全体が拒否されます:"JSON Schema declares an unsupported dialect ... The default validator supports JSON Schema 2020-12 only." 不正なスキーマが1つあるだけで、tools/list全体を破棄するクライアントでは、サーバーのほかのツールまで使えなくなることがあります。v2 では 2020-12 が出力されます。未解決(#2721、正規の #2084);@modelcontextprotocol/inspector>= 2.4.0 ではこの問題が検出されます。 [email protected](マイナーリリース)以降、サーバーまたはステートレス転送の再利用は重大なエラーになります。createMcpHandler(() => sharedServer)は、重複するリクエストに対して500/-32603を返します。ステートレス転送を再利用すると、2回目のリクエストで例外が発生します。Node ラッパー経由では、ログに何も記録されず、内容のない500が返されます(#2704)。サーバーを構築するファクトリーを渡してください。
全項目を網羅した表(ステータス、zod 3->4 での
additionalPropertiesの削除、refine/superRefineが実行されない問題、転送の終了処理でのスタックオーバーフロー、HTTP/2、raw JSON Schema、z.transform()、ReDoS、2.1-2.3 の修正)については、references/sdk-bugs.mdを参照してください。
V2 への移行
すべての破壊的変更と変更前・変更後のコードを含む、包括的な移行ガイドについては、
references/v2-migration.mdを参照してください。
主な破壊的変更:
- パッケージ分割:
@modelcontextprotocol/sdk->@modelcontextprotocol/server+/client+/core - ESM を優先(CJS ビルドは beta.2 で復元)、Node.js 20 以降(Bun/Deno に対応)
- Zod v4 が必須(または任意の Standard Schema ライブラリ)
McpError->ProtocolError(@modelcontextprotocol/coreから)extraパラメーター ->ctx.mcpReqを含む構造化されたctxserver.tool()->registerTool()(位置引数ではなく設定オブジェクト)- SSE サーバー転送を削除(クライアントは引き続き従来の SSE サーバーに接続可能)
createMcpHandler(factory)により、リクエストごとの転送とconnect()の配線を置き換え。@modelcontextprotocol/hono/express/nodeがこれに対応します- localhost サーバーでは DNS リバインディング保護がデフォルトで有効(フレームワークのファクトリーのみ)
v1.x は少なくとも 2027-01-27 まで修正が提供されますが、2026-07-28 は実装されません。新しいコードは v2 で作成してください。
仕様 2026-07-28(リリース済み)
公開日:2026-07-28(リリースのお知らせ、変更履歴)— 最新の改訂版です。SDK ではオプトインであることに注意してください(「2 つの時代」を参照)。デプロイ済みソフトウェアの大半が現在も話すのは、2025-11-25 です。
現在の判断を変える 4 つの大きな変更:
- MCP はステートレスで、セッションレスです。
initializeハンドシェイクとMcp-Session-Idは廃止されました(SEP-2575、SEP-2567)。各リクエストはプロトコルバージョン、クライアントの ID、機能を_metaに含み、呼び出しをまたぐ状態にはハンドルを使用します(「ステートフルなツール」を参照)。新しいサーバーをセッションアフィニティに依存して構築しないでください。 server/discoverはサーバーの MUST です。バージョン、機能、ID を通知します。クライアントはこれを省略して、UnsupportedProtocolVersionErrorをインラインで処理してもかまいません。- Roots、Sampling、Logging、HTTP+SSE トランスポートは、正式な機能ライフサイクルのもとで非推奨になりました(SEP-2577/SEP-2596)。引き続き動作しますが、新しいサーバーはこれらを使わずに設計してください。HTTP+SSE の期限は、デフォルトの 12 か月より短く、2026-09-03 頃から削除可能になりました。
- アプリケーション定義のエラーコードは
-32768..-32000の範囲外に割り当ててください。-32020..-32099は仕様用に予約されており、-32000..-32019はレガシーで、新しい実装では SHOULD NOT 使用です(PR #2907)。
content と structuredContent の二重配信という落とし穴に変更はありません。優先順位のルールは追加されていないため、上記のガイダンスは引き続き有効です。
その他すべて — MRTR、
subscriptions/listen、_metaの ID キー、requestState、Mcp-Method/Mcp-Name、キャッシュ可能な結果、リクエストごとのログレベル、認証の変更、削除項目(SSE の再開機能、ping、execution.taskSupport)、時代ごとのテスト、ワーキンググループについては、references/spec-2026-07-28.mdを参照してください。
拡張機能
{vendor-prefix}/{extension-name} という名前の、任意で純粋に追加型の機能です(公式:io.modelcontextprotocol/*、サードパーティ:ドメイン名を逆順にした形式)。2025年頃のワイヤープロトコルでは initialize の capabilities でネゴシエーションします。2026-07-28 では、クライアントは _meta["io.modelcontextprotocol/clientCapabilities"] でリクエストごとにサポートを通知します。公式の拡張機能は、MCP Apps(/ui、インタラクティブな HTML UI、安定版、広くサポートされています。ext-apps 2.0.0 は 2026-09-08 から提供。破壊的変更は TypeScript 側のみで、ワイヤープロトコルに変更はありません)、OAuth Client Credentials(ドラフト)、Enterprise-Managed Authorization(2026-06-18 に安定版)、Tasks(2026-08-19 から公式)、Skills(io.modelcontextprotocol/skills、SEP-2640 最終版。スキルをリソースとして提供)です。- クライアントの対応状況
ツール以外のサーバー機能(特記がない限り、2025年頃の呼び出し形式):
| 機能 | 目的 | v2 API |
|---|---|---|
| Elicitation | ツール実行中に構造化されたユーザー入力を要求します | 両方の世代で使用可能:inputRequired({ inputRequests: { k: inputRequired.elicit(...) } }) を返します。ctx.mcpReq.elicitInput() は 2026-07-28 の接続ではエラーをスローします |
| Sampling | クライアントに LLM による補完を要求します | ctx.mcpReq.requestSampling() |
| Tasks | ライフサイクル管理を伴う長時間実行処理です | 公式拡張機能(SEP-2663) |
| Progress | リクエストの進行状況を段階的に通知します | ctx.mcpReq.sendProgress() |
2026-07-28 では、サーバーからクライアントへリクエストを送信することはできません。Elicitation と Sampling は MRTR を介して行います(InputRequiredResult を返し、再試行時に inputResponses を読み取ります)。inputRequired 形式でハンドラーを記述してください。SDK のデフォルトのレガシー互換シムにより、返されたリクエストは2025年頃のクライアント向けに実際の elicitation/create に変換されるため、1つのハンドラーで両方の世代に対応できます。Tasks はコアから切り離され、ポーリングする io.modelcontextprotocol/tasks 拡張機能に移行しました(ext-tasks)。
MCP Apps のアーキテクチャ、ext-apps SDK、ビルドパターンについては、
references/mcp-apps.mdを参照してください。 拡張機能システム、認証拡張機能、elicitation/sampling/tasks の詳細、MCP Registry については、references/extensions-registry.mdを参照してください。