Claude Media
x-mcp-header仕様 — MCPツール引数をHTTPヘッダーに露出する仕組み

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. 1

    クライアントが値を取り出す

    ツールのinputSchemaを見て、x-mcp-header付きのプロパティを探します。呼び出しの引数から、そのパスの値を取り出します。

  2. 2

    値を文字列に変換して符号化する

    値を文字列に変換し、ASCIIで安全に表せない値はBase64で包みます。

  3. 3

    Mcp-Paramヘッダーを付けて送る

    標準ヘッダーとあわせて送信します。途中のプロキシは、知らないMcp-Param-ヘッダーも中身を見ずにそのまま転送します。

  4. 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 --help
Usage: 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で決めるのは、どの引数をヘッダーに出すかです。対象は、振り分けやレート制限に使う値のうち、ヘッダーに出ても困らないものです。パスワードやトークンのような値は、ボディの中にとどめます。

この記事を共有:XはてブLinkedIn
MCP をもっと見る →