Claude Media
Claude CodeのMCPでheadersHelperを使う — Kerberosや短寿命トークンの認証ヘッダー

Claude CodeのMCPでheadersHelperを使う — Kerberosや短寿命トークンの認証ヘッダー

OAuthを使わない社内SSOや短寿命トークンのMCPサーバーに、接続のたびにコマンドを走らせて認証ヘッダーを作る設定です。書き方、キャッシュ、設定場所ごとの違いを扱います。

headersHelperは、MCPサーバーへ接続する直前にシェルコマンドを実行し、その出力をリクエストヘッダーとして使う設定です。OAuthに対応していない社内SSO、Kerberos、有効期限の短いトークンを使うMCPサーバーを、Claude Codeにつなぐ場面で効きます。

書く場所は、.mcp.jsonなどのサーバー定義の中にあるheadersHelperの1行です。ただし、スクリプトの出力形式、10秒の制限、実行ディレクトリ、環境変数の扱いを知らないと、手元では動くのにClaude Codeからだけ失敗します。この記事は、設定を書いて動かすまでの手順を扱います。

headersHelperで何が変わるのか

静的なheadersには、Authorization: Bearer <固定値>のような値をそのまま書きます。短寿命のトークンだと、設定ファイルに書いた時点でもう期限が切れています。headersHelperは、値の代わりに「値を作るコマンド」を書く仕組みです。

Claude Codeはそのコマンドをシェルで実行し、出力をヘッダーとして接続に混ぜ込みます。実行のタイミングは、セッションの開始時と再接続時です。

くらべる

静的なheadersとheadersHelperの違い

固定値

headers

設定ファイルに書いた文字列をそのまま送ります。期限のあるトークンには向きません。

接続時に生成

headersHelper

接続のたびにコマンドを実行し、出力のJSONをヘッダーにします。静的なheadersと同じ名前があれば、ヘルパーの値が上書きします。

typeがhttpのサーバーのほか、sseとwsのサーバーでも使えます。WebSocket(ws)の認証はヘッダーだけで行うので、固定トークンはheadersに、接続時に作るトークンはheadersHelperに置きます。

設定ファイルへの書き方は2通り

スクリプトのパスを書く形と、コマンドをそのまま書く形があります。まずスクリプトのパスを書く形です。

{
  "mcpServers": {
    "internal-api": {
      "type": "http",
      "url": "https://mcp.internal.example.com",
      "headersHelper": "/opt/bin/get-mcp-auth-headers.sh"
    }
  }
}

短いコマンドなら、インラインでも書けます。JSONの中なので、ダブルクォートのエスケープが要ります。

{
  "mcpServers": {
    "internal-api": {
      "type": "http",
      "url": "https://mcp.internal.example.com",
      "headersHelper": "echo '{\"Authorization\": \"Bearer '\"$(get-token)\"'\"}'"
    }
  }
}

インラインは、クォートが2段に重なって読みにくくなります。トークンの有効期限やキャッシュを扱う段階に入るなら、スクリプトに切り出すほうが保守しやすい構成です。

コマンドが守る4つの条件

ヘルパーに課される条件は次の4つです。

条件内容
出力内容文字列のキーと値だけのJSONオブジェクトを標準出力に書く
時間内容シェルで実行され、10秒で打ち切られる
パス内容作業ディレクトリは設定した場所で決まるので、絶対パスかPATH上に置く
優先順位内容同名の静的headersはヘルパーの値で上書きされる

出力の値が文字列でないJSON(数値や入れ子のオブジェクト)は、この条件を外れます。jq -n --argで組み立てると、クォートの崩れも避けられます。

短寿命トークン向けのスクリプトを組む

Claude Codeは、ヘルパーの結果をキャッシュしません。接続のたびに毎回コマンドが走るので、トークンの再利用が必要なら、スクリプトの側で持ちます。

発行に時間がかかる認証基盤では、この点が効きます。10秒の制限に収まらない、または発行回数に上限があるなら、有効期限内のトークンをファイルに残す形にします。次は、トークンを5分だけ使い回す例です。get-tokenは社内の発行コマンドを想定した仮の名前です。

#!/bin/sh
# 短寿命トークンを5分キャッシュして、Authorization だけをJSONで出す
CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/mcp-token-${CLAUDE_CODE_MCP_SERVER_NAME:-default}"
NOW=$(date +%s)
if [ -f "$CACHE" ] && [ $((NOW - $(cat "$CACHE.ts"))) -lt 300 ]; then
  TOKEN=$(cat "$CACHE")
else
  TOKEN=$(get-token) || exit 1
  umask 077
  printf '%s' "$TOKEN" > "$CACHE"; printf '%s' "$NOW" > "$CACHE.ts"
fi
jq -n --arg t "$TOKEN" '{Authorization: ("Bearer " + $t)}'

シェルで直接実行すると、次のJSONが返ります(トークンの値は例です)。

{
  "Authorization": "Bearer tok-05000"
}

この出力は、複数行に整形されていても有効なJSONオブジェクトです。1秒後にもう一度実行したときは、get-tokenを呼ばずにキャッシュの値が返りました。get-tokenが見つからない環境では、スクリプトが終了コード1で止まり、JSONは何も出力されません。

キャッシュファイルには資格情報が入ります。umask 077で自分以外が読めない権限にしておきます。キャッシュのキーにCLAUDE_CODE_MCP_SERVER_NAMEを使っているのは、1本のスクリプトを複数のサーバーで共有したときに、トークンが混ざらないようにするためです。

401や403のときは自動で1回やり直す

接続後のツール呼び出しが401 Unauthorizedか403 Forbiddenを返すと、Claude Codeはヘルパーを再実行し、新しいヘッダーで再接続して、呼び出しを1回だけ再試行します。再試行も失敗した場合に限り、/mcpでそのサーバーが認証待ちとして示されます。

つまり、トークンの期限切れは一度までなら吸収されます。キャッシュを持つスクリプトでは、ここで古いキャッシュを返し続けると再試行が意味を失います。期限の判定は、サーバー側の有効期間より少し短めに設定するのが無難です。

Kerberosや社内SSOの場合の考え方

公式の説明は、headersHelperの用途として「Kerberos、短寿命トークン、社内SSOのようなOAuth以外の認証」を挙げています。Claude Code自身がKerberosを理解するわけではありません。やっているのは、コマンドを実行して出力のJSONをヘッダーにすることだけです。

そのため、設計の中心はスクリプトの中身になります。

  1. チケットやセッションから、MCPサーバーに渡す資格情報を得る社内コマンドを用意する
  2. そのコマンドの結果をAuthorizationなどのヘッダーのJSONに整形する
  3. 全体が10秒以内に終わることを確かめる
  4. 失敗したときに、空のトークンを送らず終了コードで止める

4番目は実務上の注意です。上のスクリプトのようにget-tokenの失敗を|| exit 1で止めておかないと、Bearer だけがサーバーに届き、原因の見えない401になります。ヘルパーが失敗したときの挙動は、公式のページに詳細な記載がありません。空の資格情報を出力しない作りにしておくのが安全です。

設定した場所で変わる3つの挙動

同じスクリプトでも、サーバー定義をどこに置くかで、実行ディレクトリ、環境変数、実行の可否が変わります。

サーバーを設定した場所作業ディレクトリ資格情報らしい環境変数
プラグイン作業ディレクトリプラグインのルート資格情報らしい環境変数除去される
プロジェクトの.mcp.json作業ディレクトリサーバーを宣言したプロジェクト資格情報らしい環境変数除去される
ローカルスコープ作業ディレクトリサーバーを宣言したプロジェクト資格情報らしい環境変数除去されない
ユーザースコープ作業ディレクトリ~/.claude(CLAUDE_CONFIG_DIRがあればそちら)資格情報らしい環境変数除去されない

claude.aiのコネクタと管理対象のMCPも、作業ディレクトリは設定ディレクトリ側です。--mcp-config、SDKのmcpServers、プロジェクトのエージェントファイルのサーバーは、セッションの主作業ディレクトリを基準にします。

環境変数の除去が効く場面

リポジトリやプラグインが持ち込んだヘルパーは、自分で書いていないコマンドです。そこでClaude Codeは、TOKEN・SECRET・PASSWORD・KEY・AUTHを名前に含む環境変数を、ヘルパーの実行環境から外します。大文字小文字は区別されず、ANTHROPIC_API_KEYもMY_REGISTRY_TOKENも対象です。

チームで.mcp.jsonを共有する構成では、スクリプトが環境変数から資格情報を読んでいると、他の人の環境で空になります。ファイルか資格情報ストアから読む作りにします。ユーザースコープに置いた自分専用のヘルパーなら、この除去は起きません。

サーバーのurlに除去対象の変数の値が入っている場合は、ヘルパーが受け取るCLAUDE_CODE_MCP_SERVER_URLのその部分がREDACTEDに置き換わります。

フォルダーのトラストが先に要る

.mcp.jsonのサーバーとローカルスコープのサーバーでは、ヘルパーはフォルダーのトラストを承認してから初めて実行されます。親フォルダーのトラストは数えられません。承認前は、静的なheadersだけで接続します。

claude -pやSDKのセッションでは、承認がないとheadersHelper not runという1行が標準エラーに出ます。ダイアログを使わずに承認する場合は、~/.claude.jsonのprojects["<パス>"].hasTrustDialogAcceptedをtrueにします。CIのように対話できない環境の設定で、見落としやすい点です。

ヘルパーを動かしたあとにServer rejected the Authorization headerが出る場合は、別の記事の領分です。切り分けはheadersHelperのServer rejectedエラーにまとめています。

1本のスクリプトを複数のサーバーで共有する

Claude Codeはヘルパーを実行するとき、次の環境変数を渡します。

変数値
CLAUDE_CODE_MCP_SERVER_NAME値MCPサーバーの名前
CLAUDE_CODE_MCP_SERVER_URL値MCPサーバーのURL
CLAUDE_PLUGIN_ROOT値プラグインのルート(プラグイン提供のサーバーのみ)

社内のMCPサーバーが複数あり、認証基盤が同じなら、サーバー名で分岐する1本のスクリプトで足ります。たとえば、サーバーごとにトークンの対象(audience)を変える構成です。

#!/bin/sh
case "$CLAUDE_CODE_MCP_SERVER_NAME" in
  internal-api)  AUD="api" ;;
  internal-docs) AUD="docs" ;;
  *) echo "unknown server: $CLAUDE_CODE_MCP_SERVER_NAME" >&2; exit 1 ;;
esac
TOKEN=$(get-token --audience "$AUD") || exit 1
jq -n --arg t "$TOKEN" '{Authorization: ("Bearer " + $t)}'

get-token --audienceは例として置いた仮のコマンドです。未知のサーバー名で止めておくと、設定の書き間違いが空のヘッダーとして流れ出ません。

プラグインでは${user_config.*}を使えない

プラグインが提供するheadersHelperは、プラグインのユーザー設定値${user_config.*}を参照できません。コマンドがシェルを通るため、設定値の中身がコードとして解釈されるのを避ける仕様です。参照するとサーバーは誤設定として報告され、値は置換されません。v2.1.207より前は置換されていました。

代わりの方法は2つあります。サーバーのheadersフィールドに${user_config.KEY}を書く方法と、ヘルパーのスクリプトが設定ファイルから値を読む方法です。headersはシェルを通らないので、こちらは使えます。

動かして確かめる順番

設定を入れたら、Claude Codeの外でスクリプトを単体で確かめてから、中で再接続します。

手順

動作確認の流れ

  1. 1

    単体で実行して出力を見る

    サーバーの名前とURLを環境変数で渡し、JSONが返ることを確認します。

  2. 2

    出力の形を検査する

    jqで、すべての値が文字列のオブジェクトかを調べます。

  3. 3

    Claude Codeで再接続する

    /mcpでサーバーを選び、再接続します。再接続のたびにヘルパーが実行されます。

手順1と2は、次のコマンドで一度にできます。変数の値とスクリプトのパスは、自分の環境に合わせて置き換えてください。

CLAUDE_CODE_MCP_SERVER_NAME=internal-api \
CLAUDE_CODE_MCP_SERVER_URL=https://mcp.internal.example.com \
  /opt/bin/get-mcp-auth-headers.sh \
  | jq -e 'type=="object" and (to_entries | all(.value | type=="string"))'

trueが返れば、出力の形は条件を満たしています。サーバーが値を受け入れるかどうかは、この検査では分かりません。.mcp.jsonのサーバーで環境変数が除去される影響を試すなら、該当する変数をenv -uで外して同じコマンドを実行します。

認証の入口を選ぶ目安

MCPサーバーの認証には、OAuthを使う方法もあります。選び分けは、サーバー側が何に対応しているかでほぼ決まります。

  • サーバーがOAuthに対応している: リモートMCPのOAuth認証の流れで、claude mcp loginなどから認証する
  • 固定のAPIキーで足りる: 静的なheadersに書く
  • OAuthに対応せず、トークンが短寿命またはSSO経由: headersHelper

ヘルパーの出力にAuthorizationが含まれると、そのサーバーではOAuthへのフォールバックが無効になります。サーバーが認証待ちになったときの扱いは、claude mcp login/logoutにあります。

登録の経路と、つながらないときの入口

JSONをそのまま登録するなら、claude mcp add-jsonの経路もあります。設定の追加後にサーバーが一覧に出ない、起動しないといった問題は、MCPサーバーに接続できないときの切り分けが4層に分けて扱っています。

この記事を共有:XはてブLinkedIn