MCPでMcp-MethodとMcp-Nameヘッダーが必須になった理由
MCP仕様2026-07-28でStreamable HTTPのPOSTに必須化されたMcp-MethodとMcp-Nameヘッダー。HeaderMismatchErrorの発生条件とx-mcp-headerの使い方をまとめます。
MCP仕様の2026-07-28改訂で、Streamable HTTPを使うすべてのPOSTリクエストにMcp-MethodとMcp-Nameという2つのHTTPヘッダーが必須になりました。JSON-RPCのリクエストボディに書かれている値をヘッダーにも複製する仕組みです。ロードバランサーのような中継点が、JSON本文を解析せずにテナント単位のルーティングやレート制限をかけられるようにするための変更です(SEP-2243)。ヘッダーとボディの値が食い違うと、サーバーはHeaderMismatchエラー(コード-32020)でリクエストを拒否します。
自分でStreamable HTTPのMCPサーバーを実装している、あるいはこれから実装する人にとっては、この検証ロジックを正しく書けるかどうかが2026-07-28対応の合否を分けます。逆に、サーバーが古いプロトコル版のまま動いている分には、今日から何かが壊れるわけではありません。
同じ2026-07-28改訂では、ツール定義のinputSchema・outputSchemaが受け入れるJSON Schemaのキーワード範囲そのものも広がっています(MCPのツール定義がJSON Schema 2020-12の全機能に対応)。後述のx-mcp-headerのようなスキーマ拡張プロパティも、この広がった許容範囲の上に成り立っています。
Mcp-MethodとMcp-Nameヘッダーが運ぶ情報
Mcp-MethodはJSON-RPCボディのmethodフィールドをそのまま複製したもので、Streamable HTTP経由のすべてのリクエストで必須です。Mcp-Nameはparams.name(tools/callのツール名)かparams.uri(resources/readのリソースURI)を複製したもので、tools/call・resources/read・prompts/getの3つのメソッドでのみ必須になります。
| ヘッダー名 | 複製元のフィールド | 必須になる条件 |
|---|---|---|
Mcp-Method | 複製元のフィールドmethod | 必須になる条件すべてのリクエスト |
Mcp-Name | 複製元のフィールドparams.name または params.uri | 必須になる条件tools/call / resources/read / prompts/get |
実際のリクエストでは、標準ヘッダーとボディの_metaが次のように並びます。仕様の例ではMCP-Protocol-Versionも同じ値をヘッダーと_metaの両方に載せています。
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: execute_sql
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28", ...},"name":"execute_sql","arguments":{...}}}どちらのヘッダーもリクエストごとに毎回付けます。2026-07-28改訂ではMcp-Session-Idのような接続単位のヘッダーが廃止され、プロトコルバージョンとクライアントの能力が毎回_metaで運ばれ、クライアント情報も原則付くようになりました。サーバーが接続の状態を覚えなくても、1通のリクエストだけで中身が決まる形です。
HeaderMismatchErrorはどんな条件で起きるか
サーバーがリクエストボディを処理する場合、次の3条件のどれかに当てはまると400 Bad RequestとHeaderMismatch(-32020)エラーを返す義務があります。
HeaderMismatchになる3条件
必須ヘッダーが欠けている
MCP-Protocol-Version・Mcp-Method・Mcp-Nameのどれかがありません。ヘッダーとボディの値が違う
Base64エンコードされた値は、デコードしてから比べます。
MCP-Protocol-Versionも対象で、ボディの_metaにあるio.modelcontextprotocol/protocolVersionと食い違えば同じエラーです。ヘッダーの値に不正な文字がある
プレーンなASCIIで運べない値は、後述のBase64記法に変える必要があります。
エラーレスポンスの形はJSON-RPCの通常のエラーと同じです。
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32020,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}見落としやすいのは、整数パラメータの比較ルールです。仕様は「ヘッダー値とボディ値は文字列としてではなく数値として比較すべき」と定めており、42.0と42は一致とみなされます。文字列比較で書くと、この2つは別の値になります。
エラーコードの番号にも経緯があります。JSON-RPC 2.0がサーバー実装依存のエラー用に予約する-32000から-32099の範囲のうち、MCP仕様は-32020から-32099を自分専用に確保しました。-32020(HeaderMismatch)・-32021(MissingRequiredClientCapability)・-32022(UnsupportedProtocolVersion)がこの範囲に入ります。
2026-07-28の変更履歴によると、この3つは仕様の草案段階では-32001・-32003・-32004でした。草案の番号で実装済みなら、書き換えが必要です。手前の-32000から-32019は、方針ができる前に各SDKが独自に割り当てていた領域です。既存のSDKの使い方は黙認されますが、新しいコードをここに割り当ててはならず、新しい実装は使わないほうがよいとされています。独自のエラーコードを作るなら、JSON-RPCの予約範囲(-32768から-32000)の外に置きます。
中継サーバー(ロードバランサーやゲートウェイ)の扱いにも決まりがあります。認識できないMcp-Param-{Name}ヘッダーは、転送しつつ無視する義務があり、勝手に落としてはいけません。検証に失敗したときは、適切なHTTPエラーステータスを返す必要がありますが、JSON-RPCのエラー本文までは求められません。
ヘッダーを根拠にルーティングや制限をかける中継サーバーには、もう一つ条件があります。MCP-Protocol-Versionが、ヘッダーとボディの照合を要求する版を示しているかを確かめます。古い版や、ヘッダーがないリクエストは、ヘッダーの値を信用せず拒否すべきだとされています。照合のない古い版のヘッダーを信用すると、確かめていない値を信じることになるためです。
x-mcp-headerでツール引数を独自ヘッダーに昇格させる
標準の2ヘッダーとは別に、MCPサーバーはツール定義のinputSchemaにx-mcp-headerという拡張プロパティを付けることで、任意のパラメータをHTTPヘッダーとしても送らせることができます(仕様の詳細と制約条件)。ヘッダー名はMcp-Param-{name}という形になります。
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"x-mcp-header": "Region"
},
"query": { "type": "string" }
},
"required": ["region", "query"]
}
}このスキーマを受け取ったクライアントは、tools/callのリクエストにMcp-Param-Region: us-west1のようなヘッダーを自動で付け加えます。サーバー側での使用は任意ですが、x-mcp-headerが付いたツール定義を受け取ったクライアントは、この機能への対応が必須です。つまり、サーバーが注釈を使い始めた瞬間、対応済みのクライアントは自動でヘッダーを送るようになります。
値の変換にも細かい規定があります。文字列はそのまま、真偽値は小文字のtrue/false、整数は10進数表記に変換します。非ASCII文字・制御文字・前後の空白を含む値は、UTF-8のBase64を=?base64?{Base64値}?=で挟む記法にしなければなりません。Mcp-Nameヘッダーも同じ規則の対象で、記法の接頭辞と接尾辞は小文字で書きます。
| 元の値 | 理由 | エンコード後 |
|---|---|---|
"us-west1" | 理由ASCIIのみ | エンコード後Mcp-Param-Region: us-west1 |
"Hello, 世界" | 理由非ASCII文字を含む | エンコード後Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?= |
" padded " | 理由前後に空白 | エンコード後Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
"line1\nline2" | 理由改行を含む | エンコード後Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
"=?base64?literal?=" | 理由記法そのものと同じ形 | エンコード後Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?= |
最後の行は見落としやすい点です。素のASCIIでも=?base64?で始まり?=で終わる値は、曖昧さを避けるためにBase64化しなければなりません。表の値は、シェルのbase64コマンドで手元でも再現できます。
printf 'Hello, 世界' | base64
printf ' padded ' | base64出力はSGVsbG8sIOS4lueVjA==とIHBhZGRlZCA=で、表のエンコード後の値と一致します。
x-mcp-headerを付けられるプロパティには制約があります。主なものは次のとおりです。
- 値はHTTPのフィールド名として使える文字だけで書き、空にはできない
inputSchemaの中で、大文字小文字を無視して重複させない- 付けられる型は整数・文字列・真偽値だけで、
number型は不可(整数はJavaScriptの安全な整数範囲に収める) propertiesキーだけをたどって到達できる位置に限る。入れ子のオブジェクトは可、items・oneOf・anyOf・allOf・not・if/then/else・$refを経由する位置は不可
違反したツール定義は、Streamable HTTPのクライアント側でtools/listの結果から除外する義務があります。警告ログを出すことも勧められており、1つの壊れた定義が他のツールの利用を妨げないようにする設計です。
クライアントが踏む5ステップと大文字小文字の扱い
tools/callをStreamable HTTPで送るクライアントは、仕様の規則を処理順に並べると次のようになります。
ヘッダーを組み立てる5ステップ
- 1
ボディから値を取り出す
method・params.name・params.uriを読みます。 - 2
標準ヘッダーを付ける
Mcp-Methodと、該当すればMcp-Nameです。 - 3
x-mcp-header付きの値を探す
ツールの
inputSchemaから注釈付きプロパティを見つけ、引数に値があるものだけ取り出します。 - 4
エンコードする
型変換とBase64記法の規則を当てます。
- 5
Mcp-Param-{Name}を付ける
値がなければ付けません。
nullや未指定のパラメータに空のヘッダーを送るのは違反です。
ヘッダー名(RFC 9110でいう「フィールド名」)は大文字小文字を区別せず比較しますが、ヘッダーの値は区別されます。つまりmcp-methodとMcp-Methodは同じヘッダーとして扱われます。一方、Mcp-Method: tools/callとMcp-Method: Tools/Callは別の値です。ここを混同すると、本来一致するはずのリクエストがHeaderMismatchで弾かれます。
サーバーがMcp-Param-*ヘッダーの欠落や不一致でHeaderMismatchを返したときは、クライアントがtools/listを呼び直し、inputSchemaが変わっていないかを調べます。そのうえで適切なヘッダーを付け直して元のリクエストを再送すべきだとされています。ツール定義がサーバー側で更新された直後に起きやすい状況で、キャッシュしたスキーマのまま送り続けると、このエラーから抜け出せません。
新旧の版が混在する環境のためには、フォールバックの手順も定められています。両方の版に対応するクライアントは、まず新しい形式のリクエストを送ります。400が返ったら、すぐ旧式のinitializeに戻さず、レスポンス本文を見ます。本文が新しい版のJSON-RPCエラー(UnsupportedProtocolVersionやHeaderMismatchなど)なら、サーバーは新しい版を話しているので、対応版を確かめるか、リクエストを直して再送します。本文が空か、新しい版のエラーとして読めないときだけ、initializeから始まる旧版へ切り替えます。
実装者は何をすればいいか
必須ヘッダーへの対応が誰にどれくらい効くかは、どの立場でMCPに関わっているかで変わります。
| 立場 | 対応の要否 | 内容 |
|---|---|---|
| Streamable HTTPサーバーの自作者 | 対応の要否対応必須 | 内容Mcp-Method/Mcp-Nameの検証ロジックとエラーコードの実装 |
x-mcp-headerを使いたいサーバー | 対応の要否任意(使うなら仕様準拠) | 内容スキーマ拡張とヘッダー抽出ロジック |
| stdioサーバーの自作者 | 対応の要否対応不要 | 内容HTTPヘッダーを使わない |
| Claude Codeでサーバーを使うだけの利用者 | 対応の要否対応不要 | 内容クライアント側はMCP TypeScript SDK 2.0ベースのv2ランタイムが担う |
自作サーバーを持たず、公開されているMCPサーバーをClaude Codeにつなぐだけの利用者には、直接の作業は発生しません。サーバー側の設計全般はMCPサーバー自作ガイドにまとめています。
Claude Codeはいつ2026-07-28で話し始めるか
Claude CodeのMCPクライアントには、MCP TypeScript SDK 1.xベースのv1と、SDK 2.0ベースで2026-07-28に対応したv2の2つのランタイムがあります。どちらを使うかは起動のたびに決まり、終了まで変わりません。
v2ランタイムがサーバーに2026-07-28を尋ねる範囲
何もしなくても尋ねる
HTTPサーバーに対して、新しい版に対応しているかを尋ね、対応していればそちらで話します。claude.aiコネクタのサーバーは、機能フラグを取得するセッションでのみ尋ねます。
環境変数が要る
stdioサーバーと、機能フラグを取得しないセッションでのコネクタサーバーにも尋ねます。legacyを指定すると、すべてのサーバーで尋ねる処理を飛ばします。
v2ランタイムは、機能フラグを取得するセッションではClaude Code v2.1.232以降で使われます。取得しないセッションでも、v2.1.274以降なら既定になる場合があります。Amazon Bedrock・Google Cloudのエージェント基盤・Microsoft Foundryなどのクラウド経由で使うときや、テレメトリを切ったときです。ランタイムを固定したいときはMCP_SDK_GENERATIONをv1かv2にします。MCP_PROTOCOL_NEGOTIATIONはv2.1.221以降で効きます。
つまり、自作のStreamable HTTPサーバーのSDKを2026-07-28対応版に上げると、v2ランタイムのClaude Codeはこの新しいヘッダー要件込みの通信へ切り替わります。ただしAnthropicは、特定のサーバーを古いプロトコルに留める機能フラグを持っています。MCP接続の設定手順はClaude Code MCP設定ガイドにあります。
Claude Codeのv2.1.287では、claude mcp add --helpに-H, --header <header...>が出ます。説明は「HTTP/SSEサーバーにヘッダーを設定する」で、説明の例は-H "X-Api-Key: abc123"です。Examples欄には--header "Authorization: Bearer ..."もあります。固定の認証ヘッダーを足す用途のオプションで、Mcp-MethodやMcp-Nameのようにリクエストごとに値が変わるヘッダーは、仕様どおりクライアントが組み立てます。なお、新しい版に切り替わったstdioのチャネルサーバーは、チャネルメッセージを運べないためチャネルとして登録されません。
ヘッダーの検証が必須なのは、本文と食い違う値で攻撃できてしまうから
仕様がヘッダーとボディの照合を必須にしている理由は、仕様の中で具体的に述べられています。ロードバランサーがヘッダーの値でルーティングし、MCPサーバーは本文の値で実行する、という構成です。ネットワーク上の部品ごとに信じる値が違うと、セキュリティ上の脆弱性につながりうるためです。
Mcp-MethodとMcp-Nameの必須化は、ルーティングを本文なしで行う利便性と、その利便性が生む食い違いの危険を、サーバー側の検証義務で埋める設計です。中継サーバーがMCP-Protocol-Versionを先に確かめる決まりも、同じ危険への備えです。
まとめ
サーバーを自作するなら、草案時点の-32001を使っていないか、整数の数値比較とBase64記法を仕様どおりに書けているかを先に点検すると、HeaderMismatchでつまずきにくくなります。MCP自体の仕組みはMCPとはにあります。