x-mcp-header仕様 — MCPツール引数をHTTPヘッダーに露出する仕組み
MCPのx-mcp-header拡張は、ツール引数の一部をHTTPヘッダーに複製し、ロードバランサやWAFがボディを読まずに振り分けられるようにします。値が空のとき・食い違うときの扱いと、実装時の制約を解説します。
x-mcp-headerは何を解決する仕組みか
MCPのツール呼び出しは、通常すべての引数がJSON-RPCのリクエストボディに入ります。ロードバランサやWAF(Web Application Firewall)がリクエストの中身でルーティング先を変えたい場合、ボディをパースしないと引数の値が分かりません。
x-mcp-headerは、この問題を解決する拡張プロパティです。ツール定義のinputSchema内で特定のプロパティに付けると、その値がHTTPリクエストのヘッダーにも複製されます。中間装置はヘッダーだけを見て、ルーティングや処理を判断できます。
プロトコル改訂2026-07-28で加わった仕様です。2025-11-25改訂のツール仕様と標準ヘッダーの節には、このプロパティもMcp-Methodも出てきません。2026-07-28のchangelogでは、標準ヘッダー(Mcp-Method、Mcp-Name)の必須化とx-mcp-headerの追加が1行にまとめて書かれています。
ヘッダー名はどう決まり、どの引数が対象になるか
x-mcp-headerはプロパティのJSON Schemaに直接書きます。値は、ヘッダー名の可変部分です。実際のヘッダー名は、固定の接頭辞Mcp-Param-に指定した名前が続く形になります。
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "The SQL query to execute"
}
},
"required": ["region", "query"]
}
}このツールをregionがus-west1で呼ぶと、クライアントはリクエストにMcp-Param-Region: us-west1を追加します。queryにはx-mcp-headerが付いていないので、ボディの中にしか現れません。ヘッダー化するかどうかはプロパティ単位で選べます。
サーバー側の対応は任意ですが、仕様はクライアントに対応を求めています。ツール定義にx-mcp-headerがあれば、クライアントは指定された引数をヘッダーへ複製しなければなりません。
常に付く標準ヘッダーと、引数ヘッダーの役割分担
x-mcp-headerはオプトインですが、Streamable HTTPには常に付くヘッダーが2つあります。この2つと引数ヘッダーを合わせると、中間装置が見られる情報は次の3層になります。
ボディを読まずに分かる3つの情報
Mcp-Method
JSON-RPCの
methodをそのまま複製したものです。すべてのリクエストに必須です。Mcp-Name
呼び出すツール・リソース・プロンプトの名前です。
tools/call、resources/read、prompts/getで必須になります。Mcp-Param-名前
x-mcp-headerを付けた引数の値です。付けたプロパティの分だけ増えます。
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1値が無いとき・nullのときヘッダーはどうなるか
引数の呼び出しごとに、ヘッダーが付くかどうかが変わります。仕様は4つの場面を決めています。
| 場面 | クライアント | サーバー |
|---|---|---|
| 値を渡した | クライアントヘッダーを付ける | サーバーヘッダーとボディの一致を検証する |
値がnull | クライアントヘッダーを付けない | サーバーヘッダーを期待しない |
| 引数に含めなかった | クライアントヘッダーを付けない | サーバーヘッダーを期待しない |
| 値はボディにあるがヘッダーが無い | クライアント仕様に従わないクライアント | サーバーリクエストを拒否する |
上の例でregionはrequiredに入れてあるので、通常の呼び出しでは必ずMcp-Param-Regionが付きます。省略できる引数にヘッダーを付けた場合は、ヘッダーの有無が「その引数を渡したか」を表します。
ヘッダーとボディの値が食い違うとどうなるか
ヘッダーはボディの値の複製なので、両者の食い違いは許されません。ボディを処理するサーバーは、デコードしたヘッダー値とボディ値を照合する必要があります。食い違いを見つけたら400 Bad Requestと、JSON-RPCエラーHeaderMismatch(コード-32020)を返します。
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32020,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}照合が無いと、ロードバランサはヘッダーを見て「テナントAへ」と振り分けたのに、サーバーはボディの値でテナントBの処理をする、という事態が起こり得ます。仕様はこのずれを、ネットワーク上の部品ごとに「正しい情報源」が違うことによる脆弱性として挙げています。
HeaderMismatchが返る条件は、ヘッダー値の不一致だけではありません。MCP-Protocol-Version・Mcp-Method・Mcp-Nameのいずれかが欠けている場合と、ヘッダー値に不正な文字が含まれる場合も同じエラーです。
1回の呼び出しでヘッダーが生まれて検証されるまで
- 1
クライアントが値を取り出す
ツールの
inputSchemaを見て、x-mcp-header付きのプロパティを探します。呼び出しの引数から、そのパスの値を取り出します。 - 2
値を文字列に変換して符号化する
値を文字列に変換し、ASCIIで安全に表せない値はBase64で包みます。
- 3
Mcp-Paramヘッダーを付けて送る
標準ヘッダーとあわせて送信します。途中のプロキシは、知らない
Mcp-Param-ヘッダーも中身を見ずにそのまま転送します。 - 4
サーバーがデコードして照合する
ヘッダーの値をデコードし、ボディの値と突き合わせます。
不一致の原因が「ツール定義が変わったのにクライアントが古い定義でヘッダーを付けた」場合に備えて、仕様はクライアントに再試行の手順も示しています。HeaderMismatchで拒否されたら、tools/listでinputSchemaの変更を確かめ、正しいヘッダーを付けて元のリクエストを再送します。
中間装置の側にも決まりがあります。検証に失敗したリクエストには、400などの適切なHTTPエラーを返します。JSON-RPCのエラー本文までは求められていません。ミラーされたヘッダーでテナント別のルーティングやレート制限を行う装置は、MCP-Protocol-Versionが検証を要するバージョンかどうかを確かめるのが望ましいとされています。バージョンが古い、またはヘッダーが無い場合は、検証されていない値を信用せず、リクエストを拒否する扱いです。
日本語などの非ASCII値はどう流れるか
MCP仕様は、ヘッダーの値に使える文字を可視ASCII文字(0x21〜0x7E)・半角スペース・水平タブとしています。地域コードのような英数字はそのまま載ります。非ASCII文字や制御文字を含む値、前後に空白がある値は、そのままでは載せられません。
この場合クライアントは、UTF-8表現をBase64にして=?base64?{エンコード済みの値}?=の形で送らなければなりません。
| 元の値 | 理由 | ヘッダーの値 |
|---|---|---|
us-west1 | 理由ASCIIのみ | ヘッダーの値Mcp-Param-Region: us-west1 |
東京 | 理由非ASCII文字を含む | ヘッダーの値Mcp-Param-City: =?base64?5p2x5Lqs?= |
" padded " | 理由前後に空白 | ヘッダーの値Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
"line1\nline2" | 理由改行を含む | ヘッダーの値Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
=?base64?と?=は大文字小文字を区別し、小文字のまま書きます。サーバーや中間装置が値を比べるときは、デコードしてから比べます。つまり、地域名やテナント名に日本語を使うと、ヘッダーの値は素の文字列ではなくBase64の形で流れます。ロードバランサのルールを文字列一致で組むなら、この形に合わせて書くことになります。
同じBase64の規則はMcp-Nameにも掛かります。ツール名やプロンプト名がヘッダーに安全な文字だけとは限らない(仕様上の制約はSHOULD)ため、安全でない名前やリソースURIはBase64の形で運ばれます。
型ごとの変換も決まっています。整数は42や-7のような10進文字列、真偽値は小文字のtrue・falseです。素のASCII値がたまたま=?base64?で始まり?=で終わる形に一致する場合も、区別のためにBase64にします。仕様の例では"=?base64?literal?="が=?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=になります。サーバーは整数を比較するとき、文字列ではなく数値として照らすのが望ましいとされ、42.0と42は等しい扱いです。
付けられる引数の制約
x-mcp-headerの値と対象には制約があります。違反すると、そのツール定義ごと使えなくなります。
| 制約 | 内容 |
|---|---|
| 空文字禁止 | 内容x-mcp-headerの値は空であってはならない |
| トークン構文 | 内容RFC 9110のHTTPフィールド名トークン構文(1*tchar)に一致すること |
| 制御文字禁止 | 内容CR(\r)・LF(\n)を含む値は不可 |
| 一意性 | 内容同一inputSchema内で大文字小文字を区別せず重複しないこと |
| 型の制限 | 内容integer / string / booleanのみ。number型は不可。整数はJavaScriptの安全な範囲(−2^53+1〜2^53−1)に収める |
付けられる場所にも条件があります。スキーマのルートからpropertiesキーだけをたどって到達できるプロパティに限られます。itemsなどの配列キーワード、oneOf / anyOf / allOf / not、if / then / else、$refを経由する経路には付けられません。ネストしたオブジェクトのプロパティは、途中がすべてpropertiesキーなら許されます。条件を外れた位置の注釈は、注釈そのものと、ツール定義全体を無効にします。
クライアントは不正な定義をどう扱うか
トランスポートで変わるクライアントの義務
Streamable HTTP
制約に違反するツール定義をtools/listの結果から除外します。拒否したときは、ツール名と理由を警告としてログに残すのが望ましいとされています。
stdioなど
x-mcp-headerの注釈を丸ごと無視できます。同じツール定義でも、接続方式によって扱いが変わります。
除外はツール単位です。不正な定義が1つあっても、同じサーバーの他のツールは使えます。
機密性の高い値をヘッダーに載せない理由
仕様は、パスワード・APIキー・トークン・個人情報にあたる引数へx-mcp-headerを付けないよう、サーバー開発者に求めています(SHOULD NOT)。ヘッダーの値は、プロキシ・ロードバランサ・WAFといったネットワーク上の中間装置から見えるためです。
execute_sqlの例で見る値の振り分け
仕様の例は、Google Cloud Spannerに対してSQLを実行するツールです。regionをヘッダー化しておけば、たとえば複数リージョンにSpannerインスタンスを置く環境で、ロードバランサがボディをパースせずMcp-Param-Regionだけで振り分けられます。SQL文のqueryはヘッダー化しません。振り分けに要るのはリージョンだけだからです。
仕様がレート制限の例に挙げているのは、テナント別の制御です。マルチテナントのSaaSならテナントIDをヘッダー化し、WAFにテナントごとの上限を持たせる設計が考えられます。MCPからデータベースに接続する記事の読み取り専用アクセスと組み合わせると、ルーティングと権限の両面から設計できます。
Claude Codeで使うときの前提
Claude CodeでHTTPのMCPサーバーを足すコマンドはclaude mcp add --transport httpです。v2.1.287のclaude mcp add --helpには、トランスポートの指定がこう載っています。
claude mcp add --helpUsage: claude mcp add [options] <name> <commandOrUrl> [args...]
-H, --header <header...> Set headers for HTTP/SSE servers (e.g. -H
"X-Api-Key: abc123" -H "X-Custom: value")
-t, --transport <transport> Transport type (stdio, sse, http). Defaults to
stdio if not specified.--transportの選択肢はstdio・sse・httpで、streamable-httpという名前は--transportには出てきません。MCPドキュメントによると、JSON設定(.mcp.jsonなど)のtypeではstreamable-httpをhttpの別名として書けます。仕様書の用語をそのまま使いたい場合は、コマンドではなくJSON設定側です。
-Hは接続時に毎回付ける固定のヘッダー用で、x-mcp-headerの複製とは別の仕組みです。x-mcp-headerのヘッダーは、ツールを呼ぶたびにその引数の値から作られます。
Claude CodeのMCPクライアントには2つのランタイムがあります。v1はMCP TypeScript SDK 1.x、v2は同じコードをSDK 2.0に載せたもので、プロトコル改訂2026-07-28はv2で加わりました。v2はフィーチャーフラグを取得するセッションでv2.1.232以降、取得しないセッションの一部(Bedrock・Foundryなどのプロバイダー経由、テレメトリ無効など)でv2.1.274以降の既定です。環境変数MCP_SDK_GENERATIONでv1かv2を固定でき、MCP_PROTOCOL_NEGOTIATION=legacyにすると、2026-07-28を使えるかの確認を省いて従来のプロトコルで接続します。2026-07-28を使わない接続では、そもそもx-mcp-headerが仕様に存在しません。
一方、MCPドキュメント・環境変数一覧・changelogには、x-mcp-headerの挙動の記述がありません。仕様はクライアントに対応を求めているので、サーバー側ではヘッダーが実際に届くかを自分で見てから、WAFやロードバランサのルールを組む形になります。ツール呼び出しの生リクエストは、MCP Inspectorで見る方法と、サーバーでリクエストヘッダーをログに出す方法があります。ツール定義の基本からClaude Code接続までは、MCPサーバー自作ガイドにあります。
まとめ
x-mcp-headerで決めるのは、どの引数をヘッダーに出すかです。対象は、振り分けやレート制限に使う値のうち、ヘッダーに出ても困らないものです。パスワードやトークンのような値は、ボディの中にとどめます。