Claude Media
headersHelperのServer rejectedエラーの原因 — OAuthに落ちない仕様

headersHelperのServer rejectedエラーの原因 — OAuthに落ちない仕様

headersHelperが出したAuthorizationをMCPサーバーが401か403で拒否すると、Claude Codeは接続失敗として報告します。OAuthには切り替わりません。原因の切り分け手順を扱います。

MCPサーバーにheadersHelperを設定した状態で次のエラーが出たら、サーバーがヘルパーの出したAuthorizationを401か403で拒否しています。

Server rejected the Authorization header minted by the configured headersHelper (HTTP 401). Check that the helper command returns a valid credential for this MCP endpoint — OAuth fallback is disabled when the helper supplies Authorization.

このとき/mcpで認証画面が開くことはなく、OAuthにも切り替わりません。直す場所は、ヘルパーが返す資格情報です。この記事では、仕様上なぜそうなるのかと、ヘルパーを手元で再現して原因を絞る手順を扱います。

なぜOAuthに切り替わらないのか

Claude Codeは、リモートMCPサーバーが401 Unauthorizedか403 Forbiddenを返すと、通常はそのサーバーを「認証が必要」として/mcpに表示し、OAuthフローへ進ませます。ただし例外があります。headersまたはheadersHelperでAuthorizationを自分で設定したサーバーです。この場合、401や403は認証待ちの印にならず、接続失敗として報告されます。直すべきなのは自分で設定した資格情報のほうだからです。

headersHelperでは、さらに一段はっきりしています。ヘルパーの出力にAuthorizationヘッダーが含まれると、Claude Codeはそれをサーバーの認証として扱い、そのサーバーについてOAuthへのフォールバックをしません。

くらべる

Authorizationを誰が出すかで分かれる扱い

OAuthへ進む

Authorizationを設定していない

401か403で/mcpに認証待ちが表示され、OAuthフローを完了できます。

接続失敗

headersHelperがAuthorizationを出す

401か403は接続失敗として報告されます。OAuthには切り替わりません。

OAuthそのものの流れはリモートMCPのOAuth認証で扱っています。

v2.1.248より前は別のエラーになっていた

v2.1.248より前は、ヘルパーがAuthorizationを出していてもClaude CodeがOAuthの探索を走らせていました。その探索がIncompatible auth server: does not support dynamic client registrationで失敗し、本来の原因である「資格情報が拒否された」ことが見えにくくなっていました。古いバージョンでこの別のエラーを見ているなら、まずClaude Codeの更新で表示が変わります。

似た2つのメッセージとの違い

headersHelperまわりの拒否には、出る場面が違うメッセージがもう1つあります。

場面メッセージの要点
接続時に401・403メッセージの要点Server rejected the Authorization header minted by the configured headersHelper
接続後のツール呼び出し中に拒否メッセージの要点MCP server "<name>" rejected the credential from its headersHelper

後者はツール呼び出しの途中で出ます。このときClaude Codeはヘルパーを再実行して呼び出しを1回だけ再試行済みで、それでも拒否されたときに表示されます。再試行も失敗した場合に限り、/mcpでそのサーバーが認証待ちとして示されます。対処はどちらも同じで、ヘルパーが返す資格情報がサーバーに受け入れられるかを確かめ、/mcpから再接続します。

静的なAuthorizationヘッダーを設定ファイルに書いているサーバーでは、rejected the Authorization header in its configという文言になります。こちらは設定した値そのものを更新します。

原因を切り分ける手順

最初に、エラーが出ている設定の形を確認します。headersHelperは、サーバー定義の中にコマンドを書いて使います。

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

WebSocket(type: "ws")のサーバーも、url・headers・headersHelperをhttpと同じ形で受け付けます。wsの認証はヘッダーだけで行われます。

ヘルパーの出力は、静的なheadersに同じ名前があっても上書きします。Authorizationをheadersにも書いていた場合、サーバーに届くのはヘルパー側の値です。ヘルパーの作業ディレクトリと、見える環境変数は、この定義をどこに置いたかで決まります。

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

上から順に確かめると、原因を絞れます。

切り分け

Server rejectedの切り分け

  1. Q`.mcp.json`かローカルスコープのサーバーで、そのフォルダーのトラストを承認済みか
    答えがいいえ

    ヘルパーが実行されておらず、静的なheadersだけで接続しています。フォルダーのトラストを承認してから、/mcpで再接続します。

    答えがはい
  2. Qエラーは`Incompatible auth server`ではなく、`Server rejected the Authorization header`か
    答えがいいえ

    v2.1.248より前のバージョンでは、同じ原因が別のエラーに見えます。Claude Codeを更新してから、もう一度表示を確かめます。

    答えがはい
  3. Q資格情報を環境変数から読むヘルパーで、変数名に`TOKEN`や`KEY`を含まないか
    答えがいいえ

    プロジェクトの.mcp.jsonやプラグインのサーバーでは、これらの変数が見えなくなり、値が空になります。ファイルや資格情報ストアから読む形に変えます。

    答えがはい
  4. Qヘルパーの出力を`curl`でサーバーに直接送ると401か403になるか
    答えがいいえ

    サーバーが受け入れる値をヘルパーが返せているので、作業ディレクトリのずれ、10秒の制限、ローテーション競合を疑います。

    答えがはい

資格情報の発行元かサーバーの条件が原因です

Claude Code側ではなく、トークンの発行元かサーバーの受け入れ条件を直します。

次の手順で、Claude Codeの中と同じ条件をシェルで再現します。ヘルパーは起動されるシェルコマンドで、自分の端末で素直に実行したときとは条件が違います。

手順

ヘルパーを手元で再現する

  1. 1

    実行されるディレクトリを確認する

    相対パスで書いたスクリプトは、上の表の作業ディレクトリを基準に解決されます。

  2. 2

    環境変数を揃える

    Claude CodeはヘルパーにCLAUDE_CODE_MCP_SERVER_NAMEとCLAUDE_CODE_MCP_SERVER_URLを渡します。複数のサーバーで1本のスクリプトを共有しているなら、この2つに依存した分岐が正しく動くかを確かめます。

  3. 3

    資格情報の変数が消えることを考慮する

    プロジェクトの.mcp.jsonやプラグインのサーバーでは、TOKEN・SECRET・PASSWORD・KEY・AUTHを名前に含む環境変数が、ヘルパーから見えなくなります。ANTHROPIC_API_KEYも対象です。自分の端末では動くのに、Claude Codeからだけ拒否されるときの有力な原因です。

  4. 4

    出力を取り出して、サーバーに直接投げる

    出力されたAuthorizationの値が、MCPエンドポイントで通るかを確かめます。

手順3の再現は、該当する変数を外して実行するだけです。次のコマンドは、MY_REGISTRY_TOKENが見えない状態でヘルパーを動かす一例です。変数名とパスは自分の環境のものに置き換えてください。

CLAUDE_CODE_MCP_SERVER_NAME=internal-api \
CLAUDE_CODE_MCP_SERVER_URL=https://mcp.internal.example.com \
env -u MY_REGISTRY_TOKEN \
  /opt/bin/get-mcp-auth-headers.sh

出力は、文字列のキーと値だけで構成されたJSONオブジェクトでなければなりません。たとえば次の形です。

{"Authorization": "Bearer eyJ..."}

得られた値をサーバーに直接送り、HTTPステータスだけを見る方法もあります。これはClaude Code側の機能ではなく、切り分け用の一例です。

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer <ヘルパーの出力から取った値>" \
  https://mcp.internal.example.com

ここでも401か403になるなら、問題はClaude Codeではなく、資格情報の発行元かサーバー側の受け入れ条件にあります。

考えられる原因

エラー文が示すのは「サーバーが拒否した」ことまでで、理由は書かれていません。手がかりになるのは、ヘルパーが満たすべき条件です。

ヘルパーは、文字列のキーと値だけのJSONオブジェクトを標準出力に書きます。出力がこの形式を外れると、期待したヘッダーは使えません。

資格情報の変数が空になる場合もあります。外部ファイルや環境変数から読むヘルパーでは、手順3の変数除外で値が空になり、Bearer だけが送られる状況が考えられます。

作業ディレクトリのずれも定番です。相対パスのスクリプトやトークンファイルが、想定と別の場所を指しています。公式の説明でも、スクリプトは絶対パスで書くかPATHに置くよう勧められています。

ヘルパーは10秒で打ち切られます。遅い認証基盤を呼ぶ処理は、打ち切られて期待した資格情報が得られない可能性があります。

最後がトークンのローテーションです。更新のタイミングで古いトークンが送られた場合は、一時的な拒否になりえます。Claude Codeは接続の試行ごとにヘルパーを再実行するので、再試行で新しい資格情報になり通ることがあります。認証エラーは設定の変更が要るため通常は再試行の対象外ですが、ヘルパーがAuthorizationの唯一の出どころであるサーバーは例外として再試行されます。ただしClaude Codeはヘルパーの結果をキャッシュしません。トークンの再利用が必要なら、スクリプト側で持ちます。

headers側でも同じ落とし穴がある

ヘルパーを使わずheadersでBearer ${ANTHROPIC_AUTH_TOKEN}のように書いた場合も、Claude Codeの資格情報やクラウドプロバイダーの資格情報は空として読まれます。サーバーにはBearer だけが届いて、通常は401になり、同じく接続失敗として報告されます。

直したあとの再接続

ヘルパーか資格情報の発行元を直したら、/mcpでそのサーバーを選び、Reconnectを実行します。再接続のたびにヘルパーが再実行されます。

ヘルパーの書き方そのものはClaude CodeのMCPサーバー設定ガイドで扱っています。OAuth側でつまずいているなら、Failed to start OAuth callback serverの記事が入口になります。

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