Claude CodeをCognito認証のMCPサーバーに接続する — 事前登録したクライアントIDとheadersHelperの2通り

Amazon Cognitoは動的クライアント登録に対応していない。その背後にあるBedrock AgentCore RuntimeのMCPサーバーへ、Claude Codeから接続する2つの方法を解説する。

白いカードに、人物の肖像と右下の丸囲みのチェックマークが付いた白い線描きのIDカードを配した赤い角丸正方形のAmazon Cognitoのアイコンと、濃紺のAmazon Cognitoの文字
目次

はじめに

Bedrock AgentCore Runtime 上で MCP サーバーを動かし、インバウンド認証を Amazon Cognito に任せていました。これを Claude Code に登録して、/mcp → Authenticate からブラウザでサインインできるようにしたいと考えました。

オプションなしの claude mcp add は、ブラウザが開く前に Incompatible auth server: does not support dynamic client registration で止まります。Cognito のアクセストークンをヘッダーとして貼り付ければ接続はできます。ただしトークンの期限(デフォルトで 1 時間)が切れると、サーバーは Token has expired を返すようになります。

Claude Code には OAuth の経路が 2 つ、ヘッダーの経路が 2 つあり、Cognito で使えるのはそのうちの 2 つ、事前登録したアプリクライアントの ID と headersHelper です。事前登録したクライアント ID を使えばブラウザでサインインでき、トークンの更新は Claude Code が担います。headersHelper は接続のたびにスクリプトで新しいトークンを発行します。どちらも、トークンが切れるたびに登録し直す必要はありません。

Claude Code がリモート MCP サーバーに認証する 4 つの方法

Claude Code の HTTP MCP サーバーの認証方法は 4 つあり、誰がトークンを取得するかで 2 つに分かれます。OAuth の 2 経路では、Claude Code 自身がブラウザのフローを実行し、トークンを保存します。ヘッダーの 2 経路では、Claude Code の外にある何かがトークンを用意し、Claude Code はそれを転送するだけです。

Cognito 配下の MCP サーバーに Claude Code が認証する 4 つの方法左の Claude Code が、右の AgentCore Runtime に接続する。Runtime はトークンのクライアント ID を allowedClients と照合する。両者の間に 4 つの経路が 2 グループに分かれて並ぶ。OAuth グループでは、動的クライアント登録は Cognito に登録エンドポイントがないため止まる。事前登録クライアントは Cognito Hosted UI でサインインし、Claude Code がトークンを保存・更新する。ヘッダーグループでは、静的ヘッダーはトークンの期限が切れるまで Runtime に届き、headersHelper は接続ごとと 401/403 の後にスクリプトで新しいトークンを発行する。OAuth: Claude Code がトークンを取得ヘッダー: 外部でトークンを取得Claude CodeAgentCoreRuntimeJWT 検証:allowedClients動的クライアント登録(デフォルト)registration_endpoint へ POSTCognito には存在しない事前登録クライアントoauth.clientIdCognito Hosted UI でサインインClaude Code がトークンを保存・更新静的ヘッダーheaders.Authorizationトークンを一度だけ貼り付け期限切れ。OAuth へのフォールバックなしheadersHelperコマンドがヘッダーを出力スクリプトが initiate-auth を実行接続ごと、および 401/403 の後
登録エンドポイントが必要なのはデフォルトの OAuth 経路だけで、Cognito に欠けているのはその 1 点です。
経路設定ブラウザでのサインイントークンの期限切れ時Cognito で使えるか
OAuth、動的クライアント登録なし(デフォルト)ありClaude Code が更新するいいえ
OAuth、事前登録したクライアントoauth.clientId、oauth.callbackPortありClaude Code が更新するはい
静的ヘッダーheaders.Authorizationなし差し替えるまで接続に失敗するトークン 1 本の有効期間だけ使える
headersHelperヘッダーを出力するコマンドなしコマンドが再実行されるはい

これらの経路は組み合わせられません。Claude Code のドキュメントには、headers.Authorization を設定すると OAuth にはフォールバックしない(「does not fall back to OAuth」)と書かれています。そのためヘッダーが拒否されると、サインインが必要なサーバーとしてではなく、接続に失敗したサーバーとして表示されます。

デフォルトの OAuth フローが Cognito で止まる理由

動的クライアント登録(RFC 7591)は、OAuth クライアントが認可サーバーの registration_endpoint に POST して、自分のクライアント ID を作成する仕組みです。Claude Code はデフォルトでこれを使います。公開 MCP サーバーなら URL だけで接続できるのはそのためです。

ディスカバリー自体は AgentCore に対して問題なく動きます。トークンなしのリクエストには 401 が返り、その WWW-Authenticate ヘッダーが Runtime の RFC 9728 保護リソースメタデータを指しています。そのメタデータには、認可サーバーとして Cognito ユーザープールが記載されています。フローが止まるのは、その先で Cognito のメタデータを取得したときです。

ターミナルウィンドウ
入力
curl -s https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX/.well-known/openid-configuration | jq '{registration_endpoint, code_challenge_methods_supported, scopes_supported}'
出力
{"registration_endpoint":null,"code_challenge_methods_supported":null,"scopes_supported":["openid","email","phone","profile"]}

registration_endpoint がないので、Claude Code にはクライアント ID を取得する手段がありません。そのため、ブラウザを開く前に処理を打ち切ります。

✘ Failed to connect — Incompatible auth server: does not support dynamic client registration
Claude Code のデフォルトの OAuth フローが Cognito で止まる理由Claude Code、AgentCore Runtime、Cognito ユーザープールの間のやり取り。Claude Code がトークンなしで MCP エンドポイントを呼び出すと 401 が返り、その WWW-Authenticate ヘッダーが Runtime の RFC 9728 保護リソースメタデータを指す。そのメタデータには、認可サーバーとして Cognito ユーザープールが記載されている。次に Claude Code が Cognito の openid-configuration ドキュメントを取得すると、registration_endpoint が null なので、ブラウザを開く前に Incompatible auth server で停止する。Claude CodeAgentCore RuntimeCognito ユーザープールトークンなしで MCP エンドポイントを呼び出す401 + WWW-Authenticate: resource_metadata保護リソースメタデータを取得(RFC 9728)authorization_servers: Cognito ユーザープールGET /.well-known/openid-configurationregistration_endpoint: null✘ Incompatible auth serverブラウザを開く前に停止
Cognito のメタデータを読むまでは、どの手順も成功します。止まるのは、POST 先の登録エンドポイントがそこにないためです。

また、Cognito は RFC 8414 のパス /.well-known/oauth-authorization-server には 400 を返し、提供しているのは OpenID Connect の openid-configuration ドキュメントだけです。これは後述する事前登録の経路で効いてきます。

いちばん手早い回避策は静的ヘッダーです。接続はできますが、保てるのはトークン 1 本の期限が切れるまでで、期限が切れると次のように表示されます。

Status: ✘ Failed to connect
Issue: Server rejected the configured Authorization header (HTTP 401). … Error detail: {"jsonrpc":"2.0","error":{"code":-32001,"message":"Token has expired"}}

事前登録した Cognito アプリクライアントでブラウザからサインインする

MCP の認可仕様は、事前登録を最優先に位置づけています。認可サーバー用のクライアント ID をすでに持っているクライアントは、ほかの方法を試す前にそれを使うべきだ、という規定です。Claude Code は oauth.clientId でこれに対応しています。登録のステップを丸ごと飛ばすので、Cognito にない機能は一切要求しません。

Cognito 側では、アプリクライアントに次の設定が必要です。

  1. 認可コードグラントを有効にし、クライアントシークレットは発行しないこと。Claude Code はパブリッククライアントで、PKCE は自動で送ります。
  2. コールバック URL を http://localhost:<port>/callback にすること。Cognito はコールバック URL を完全一致で照合するので、ポートは固定する必要があります。
  3. そのクライアント ID を AgentCore Runtime の allowedClients に登録すること。登録しないと、発行されたトークンは 401 で拒否されます。この 401 は、不正なトークンのときと見分けがつきません。

そのうえで、同じポートを指定し、メタデータ URL も明示してサーバーを登録します。

ターミナルウィンドウ
claude mcp add-json my-mcp-server '{
"type": "http",
"url": "https://bedrock-agentcore.ap-northeast-1.amazonaws.com/runtimes/<URL-encoded runtime ARN>/invocations?qualifier=DEFAULT",
"oauth": {
"clientId": "YOUR_APP_CLIENT_ID",
"callbackPort": 8765,
"authServerMetadataUrl": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX/.well-known/openid-configuration",
"scopes": "openid email profile"
}
}'

callbackPort を指定すると、Claude Code はランダムなポートではなく、アプリクライアントがすでに信頼しているポートで待ち受けます。authServerMetadataUrl は、Cognito が実際に提供しているドキュメントを直接指します。

これでサーバーは失敗ではなく認証待ちとして表示され、Authenticate を選ぶと Cognito Hosted UI が開きます。

Claude Code の /mcp のサーバー一覧。Local MCPs のグループが赤枠で囲まれ、5 つのツールで接続済みのサーバーと、「needs authentication」と表示された -oauth サーバーが並んでいる

Claude Code の /mcp でサーバーの詳細を表示した画面。ステータスは「needs authentication」、認証は「not authenticated」で、Authenticate の選択肢がある

ユーザー名とパスワードの入力欄がある Amazon Cognito Hosted UI のサインインフォーム

サインインすると、ブラウザがコールバックポート経由で Claude Code に認可コードを返します。

/mcp 実行後の Claude Code の出力。「Authentication successful. Connected to」と表示され、続くサーバー名はぼかしてある

接続したサーバーのツールを呼び出すと、結果が返ってきました。Claude Code がトークンを保存しただけでなく、AgentCore がそのトークンを受け入れたことがこれで確認できます。

スコープを固定するまで出る invalid_scope

oauth.scopes を指定しないと、ブラウザでのサインインは成功しません。Cognito からのエラー付きで、Claude Code のコールバックページに戻されます。

Claude Code のブラウザ用コールバックページ。「Authentication failed」と表示され、エラーは「invalid_request: invalid_scope」

authServerMetadataUrl を設定すると、Claude Code は要求するスコープを openid-configuration の scopes_supported から取ります。このドキュメントは、OpenID Connect の標準に沿ってユーザープールが公開しているもので、プール全体が対応するスコープ openid email phone profile が並んでいます。

アプリクライアントはその中から許可するスコープを個別に持っており、このアプリクライアントが許可していたのは openid email profile だけでした。リクエストに phone が含まれていたため、Cognito がそれを拒否したわけです。

アプリクライアントの許可スコープに合わせて "scopes": "openid email profile" を固定すると解決しました。アプリクライアントが許可しているスコープだけで組み合わせれば、どれでも動きます。失敗するのは、許可されていないスコープを 1 つでも要求したときです。

headersHelper で自動更新される静的ヘッダー

headersHelper に指定したコマンド(以下、ヘルパー)は接続のたびに実行され、Claude Code はその標準出力をリクエストヘッダーとして使います。ツール呼び出しが 401 か 403 で返ってきた場合は、Claude Code がこれを再実行して再接続し、1 回だけリトライします。新しい Cognito トークンを発行するヘルパーにすれば、期限切れは表に出なくなります。

scripts/mcp_headers.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
source .cognito.env # COGNITO_CLIENT_ID, COGNITO_USERNAME, COGNITO_PASSWORD, AWS_REGION
TOKEN=$(aws cognito-idp initiate-auth --no-sign-request \
--client-id "$COGNITO_CLIENT_ID" \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters "USERNAME=${COGNITO_USERNAME},PASSWORD=${COGNITO_PASSWORD}" \
--region "$AWS_REGION" \
--query 'AuthenticationResult.AccessToken' --output text)
printf '{"Authorization": "Bearer %s"}\n' "$TOKEN"

InitiateAuth は AWS の署名が不要な Cognito API なので、--no-sign-request を付ければ AWS の認証情報がまったくなくてもスクリプトが動きます。付けない場合、スクリプトは認証情報ブローカー経由で AWS の認証情報を取得する必要があり、ブローカーが MFA プロンプトを出して人の応答を待つことがあります。ヘルパーのタイムアウトは 10 秒なので、時間内に誰も応答しなければ接続は失敗します。

ほかにも条件が 2 つあります。

  • アプリクライアントで USER_PASSWORD_AUTH を有効にしておくこと。
  • AgentCore はアクセストークンを要求します。トークンの client_id クレームを allowedClients と照合するためです。
ターミナルウィンドウ
claude mcp add-json my-mcp-server '{
"type": "http",
"url": "https://bedrock-agentcore.ap-northeast-1.amazonaws.com/runtimes/<URL-encoded runtime ARN>/invocations?qualifier=DEFAULT",
"headersHelper": "/absolute/path/to/scripts/mcp_headers.sh"
}'

最初に登録したときは、接続されずに次の警告が表示されました。

MCP server 'my-mcp-server': headersHelper not run — this workspace has no persisted trust; accept the trust dialog here once interactively

ヘルパーは Claude Code が実行するコマンドなので、信頼したワークスペースでしか動きません。Claude Code のドキュメントは、これを任意のシェルコマンド(「an arbitrary shell command」)と説明しています。そのディレクトリで一度 claude を対話モードで起動し、信頼ダイアログを承認すれば十分でした。次の起動でサーバーが接続されました。

事前登録したクライアントとのトレードオフは、認証情報をどこに置くかです。ヘルパーには、ディスク上のパスワードと、パスワード認証を有効にしたアプリクライアントが必要です。一方、OAuth の経路で手元に残るのは、Claude Code が保存するリフレッシュトークンだけです。そのかわりヘルパーは、ヘッドレス環境やスクリプトのように、ブラウザでサインインする人がいない場面でも動きます。

本当に動的クライアント登録が必要な場合

事前登録では、ユーザーごとの Claude Code にあらかじめクライアント ID を設定しておくことになります。1 つのチームで使うなら問題ありませんが、URL を知っていれば誰でも追加できるサーバーにするには不便です。

事前の関係がないクライアントに対して、現行の MCP 認可仕様は Client ID Metadata Documents(CIMD)を推奨し、動的クライアント登録を後方互換のための選択肢に格下げしています。CIMD では、クライアント ID はクライアントを記述した JSON ドキュメントの HTTPS URL で、認可サーバーがそれを取得します。Claude Code はすでにこの方式に対応しており、サーバーが client_id_metadata_document_supported を公開していれば使います。ただし Cognito はこのフラグを公開していないので、ここでは CIMD は使えません。

残るのは次の 3 つの回避策です。いずれもここでは試していません。ほかの Cognito ユーザーや AWS の AgentCore ガイドが挙げている方法で、Cognito と AgentCore のインバウンド JWT 検証の両方を残せるのは /register エンドポイントだけです。

  1. Cognito の前段に置く /register エンドポイント。小さなサービスが RFC 7591 のリクエストを受け付けて CreateUserPoolClient を呼び、新しいクライアント ID を返します。あわせて、registration_endpoint を追加したメタデータドキュメントも提供します。代償は、管理系 API につながる公開エンドポイントを抱えることです。レート制限とリダイレクト URI の許可リストが必要になり、ユーザープールにはアプリクライアント数のクォータがあるので、不要になったクライアントの削除も欠かせません。新しいクライアントは、それぞれ AgentCore の allowedClients にも反映しなければなりません。
  2. OAuth プロキシ。FastMCP の Cognito 連携は、動的クライアント登録に自分で応答し、すべてを実在する 1 つの Cognito アプリクライアントに転送します。そのため Cognito には、クライアントごとの登録が一切届きません。AgentCore のインバウンド JWT 検証ではなく、FastMCP 自身の認証レイヤーで作ったサーバーに向いています。
  3. 動的クライアント登録に対応した ID プロバイダ。AWS の AgentCore Runtime MCP ガイドは、Cognito ではなく Auth0 で自動 OAuth を実現する例を示しています。プロバイダをまだ選べるなら、これが最短の経路です。

まとめ

  • Claude Code のデフォルトの OAuth 経路は動的クライアント登録を前提としています。Cognito は registration_endpoint を公開していないので、Incompatible auth server で失敗します。
  • 事前登録した Cognito アプリクライアントを使えば、ブラウザでサインインできます。oauth.clientId、アプリクライアントのコールバック URL と一致する callbackPort、openid-configuration を指す authServerMetadataUrl、アプリクライアントが許可する範囲に絞った oauth.scopes を設定します。
  • headersHelper を使えば、自動で更新されるヘッダーになります。initiate-auth --no-sign-request を呼んでアクセストークンを出力するスクリプトを、信頼したワークスペースで実行します。
  • 静的な Authorization ヘッダーを設定すると OAuth へのフォールバックが無効になり、接続もトークン 1 本の有効期間しか持ちません。
  • 事前の設定なしに登録させたい場合、MCP 認可仕様は現在 CIMD を推奨していますが、Cognito は対応していません。Cognito と AgentCore を組み合わせた構成でそのまま使えるのは /register のような登録用の中継サービスだけで、それ以外なら別の ID プロバイダに切り替えることになります。

参考リンク

この記事をシェア