AgentCore Gateway + Auth0を利用して、DCR可能なリモートMCPサーバーを構築してみた
本ブログは AWS AI Agent ブログ祭り(Zenn: #awsaiagentblogfes, X: #AWS_AI_AGENT_ブログ祭り)の第 10 日目です。
はじめに
Model Context Protocol (MCP) は、Anthropicが開発した、AIエージェントが外部ツールやデータソースと安全かつ標準的な方法で連携するためのオープンプロトコルです。
今回は、OAuth認証認可付きのセキュアなリモートMCPサーバーをAWS上に構築してみました。
リモートMCPサーバーをクラウド上に認証認可付きでホスティングすることを目指した理由は以下です。
-
利用者側の手間の削減
- ローカル環境にMCPサーバーを立てる必要がない
- 環境構築やメンテナンスの負担を軽減できる
-
ビジネス要件に対応したい
- MCPサーバーの機能をサービスとして提供する場合、認証・認可は必須
- アクセス制御により、誰がどのリソースにアクセスできるかを管理できる
- MCPの仕様でも、リモートホスティングする場合は認証機能の実装が推奨されている
そこで、Amazon Bedrock AgentCore Gateway と Auth0 を組み合わせて、OAuth 2.1準拠のセキュアなリモートMCPサーバーを構築してみることにしました。
また、今回の検証では、Claude CodeやCursorやChatGPTなどのAIツールから利用できるように、Dynamic Client Registration (DCR) に対応したリモートMCPサーバーの構築を目指します。
MCPの認証・認可の仕組みのおさらい
MCPは、OAuth 2.0/2.1 をベースとした認証・認可の仕組みを採用しています。構築を始める前に、押さえておくべきポイントを簡単にまとめておきます。
-
リソースサーバー
- MCPサーバー自身は認証を行わず、外部の認可サーバー(Auth0など)が発行したアクセストークンを検証する
-
RFC 9728: Protected Resource Metadata (PRM)
- MCPサーバーは
.well-known/oauth-protected-resourceエンドポイントでメタデータを公開 - クライアントは、このメタデータから認可サーバーの情報や必要なスコープを動的に取得できる
- MCPサーバーは
-
Dynamic Client Registration (DCR)
- クライアントが事前登録なしで、プログラム的に認可サーバーに自動登録できる仕組み
- Claude Code、Cursor、ChatGPTなどのAIツールは、クライアントIDを手動設定できないため、DCRが必須
Amazon Bedrock AgentCore Gatewayとは
Amazon Bedrock AgentCore Gateway は、既存のLambda関数やAPIやMCPサーバーを裏側に束ね、まとめて一つのMCPサーバーにできるマネージドサービスです。特徴を以下にまとめました。
-
マネージドサービス
- サーバーのプロビジョニング、スケーリング、パッチ適用をAWSが担当
- インフラ管理から解放され、ビジネスロジックに集中できる
-
既存資産の活用
- 既存のLambda関数やREST APIを登録するだけでMCPツールとして公開できる
- MCPプロトコルへの変換は全てゲートウェイが担当
-
RFC 9728のサポート
- RFC 9728のProtected Resource Metadata (PRM) を自動的に提供
- クライアントは
.well-known/oauth-protected-resourceエンドポイントから認可サーバーの情報を動的に取得できる
つまり、AgentCore Gatewayは既存のAPIやLambda関数と、MCPを話すAIエージェントとの間の通訳・仲介役として機能します。
なぜAuth0なのか
Auth0はDCRをサポートしているため、Claude Code、Cursor、ChatGPTなどの、クライアントIDを設定できないAIエージェントツールが初回接続時に自動的にクライアントを登録し、OAuth認証フローを開始できます。
このため、今回はAuth0をIDPとして使用することにしました。
実装ステップ
Auth0の設定
- Auth0にサインアップします。最初のテナントが自動で作成されます。
- DCRの設定をします。左側のメニューから「設定」→「詳細設定タブ」→ 「設定」へと進み、動的クライアント登録(DCR)をオンにします

- リソースサーバーの設定を追加します。左側のメニューから「アプリケーション」→「API」→「APIの作成」と進み、適当な名前、識別子でAPIを作成します。それ以外の設定はデフォルトで構いません。

- デフォルトオーディエンスを設定します。左側のメニューから「設定」→「一般設定」→ 「APIの認可設定」と進み、デフォルトのオーディエンスに、上記で作ったAPIを設定します。

- Auth0のテナントドメインを取得します。Auth0のダッシュボードで、左側のメニューから「アプリケーション」→「アプリケーション」→ 「Default App」と進み、「設定」タブから、「ドメイン」をコピーしておきます。

- 「接続」をドメインレベルに昇格します。これは、DCRで動的に作成されたクライアントがユーザーパスワードデータベースにデフォルトで接続できるようにするために必要です。まず、Auth0 Management APIのアクセストークンを発行します。左側のメニューから「アプリケーション」→「API」→「Auth0 Management API」と進み、「API Explorer」タブから、「テストアプリケーションを作成して認可する」を押します。

トークンが発行されるので、そのトークンをコピーしておきます。

左側メニューから、「認証」→「データベース」→「Username-Password-Authentication」と進み、「識別子」をコピーしておきます。

接続のドメインレベルへの昇格はダッシュボードからはできないのでAPI経由で行う必要があります。以下のcurlコマンドを実行します。
curl -L -X PATCH 'https://<テナントドメイン>/api/v2/connections/<識別子>' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <トークン>' \
-d '{"is_domain_connection":true}'
Lambda・AgentCore Gatewayの作成
- AgentCore GatewayのターゲットになるLambdaを作成します。このLambdaでMCPサーバーで提供するツールのロジックを記述できます。このロジックは今回の主題ではないので、適当なものを作成しておきます。コンソールからLambdaの画面を開き、「関数の作成」に進みます。以下のように適当な名前を設定し、ランタイムにはPython3.10を選んでおきます。

以下は今回使用したLambdaのサンプルコードです。
import json
def lambda_handler(event, context):
toolName = context.client_context.custom['bedrockAgentCoreToolName']
delimiter = "___"
if delimiter in toolName:
toolName = toolName[toolName.index(delimiter) + len(delimiter):]
if toolName == 'get_order_tool':
return {'statusCode': 200, 'body': "Order Id 123 is in shipped status"}
else:
return {'statusCode': 200, 'body': "Updated the order details successfully"}
上記のコードを、「コードソース」欄に貼り付け、Deployボタンを押します。

- AgentCore Gatewayを作成します。AWSコンソールからAgentCore Gatewayの画面を開き、「ゲートウェイを作成」に進みます。

インバウンド認証設定では、「JSON Web Tokenを使用」を選びます。JWTスキーマ設定では、「既存のIDプロバイダーの設定を使用」を選びます。検出URLには、以下の値を設定します。
https://<Auth0テナントドメイン>/.well-known/openid-configuration
許可されたオーディエンスの欄には、先ほど適当に名付けたリソースサーバーの識別子を入力します。

IAMロールは「新しいサービスロールを作成して使用」を選んでおきます。Lambdaにアクセスするためのポリシーは自動で付与されます。
ターゲットの設定では、ターゲットタイプの中から「Lambda ARN」を選び、Lambda ARNの項目には先ほど作成したLambda関数のARNを設定します。

インラインスキーマエディタには、以下のJSONを設定します。
[
{
"description": "tool to get the order",
"inputSchema": {
"properties": {
"orderId": {
"type": "string"
}
},
"required": [
"orderId"
],
"type": "object"
},
"name": "get_order_tool"
},
{
"description": "tool to update the orderId",
"inputSchema": {
"properties": {
"orderId": {
"type": "string"
}
},
"required": [
"orderId"
],
"type": "object"
},
"name": "update_order_tool"
}
]
Lambdaをターゲットにするスキーマの書き方について詳しくはこちら
正常にゲートウェイの作成が完了したら成功です。ゲートウェイの詳細画面にて、「ゲートウェイリソースURL」に表示されているものが、求めていたリモートMCPサーバーのURLとなります。

MCPクライアントからの接続
上記で得たURLが、最終的に完成したリモートMCPサーバーのURLでした。
しかしながら、このURLを用いて愚直にリモートMCP接続を行った場合、接続に成功するツールと、エラーが出て失敗するツールがあると思います。
考察:なぜ接続に成功するツールと失敗するツールがあるのか?
以下は完全に明らかな事実ではなく、あくまで私の考察であることをご了承ください。
手元の接続検証結果
- 成功したツール
- Claude Code
- ChatGPT
- 失敗したツール
- Claude Web, Desktop
- Cursor
- Amazon Q Developer CLI
Amazon Q Developer CLIの接続ログを確認したことにより分かった事実
- Amazon Q Dev CLIが、MCPサーバーに対して、認証が必要かどうか確かめる際のprobeリクエストとして、空のボディでリクエスト送信を行っている
- 一方で、AgentCore Gatewayは未認証のリクエストに対して、ステータスコード401ではなく400(リクエストボディが不正な形式であることを意味していると思われる)を返す。
- 認証付きのリモートMCPサーバーの責務として、未認証のリクエストに対して401を返答することが求められているが、そのリクエストボディについての言及はない。
すなわち認証の必要性を明らかにするためのリクエストのボディをどうするかについての取り決めが不十分であるため、クライアントとサーバーの相性によって接続ができたりできなかったりすると考察しています。
失敗した場合には、 mcp-remote をプロキシとして挟むと、接続に成功すると思います。
では、実際にリモートMCPサーバーが動くことを試してみます。Amazon Q Developer CLIのカスタムエージェントとして以下のようなエージェント my-agent を作成しました。
{
"$schema": "https://raw.githubusercontent.com/aws/amazon-q-developer-cli/refs/heads/main/schemas/agent-v1.json",
"name": "my-agent",
"description": "",
"prompt": null,
"mcpServers": {
"myRemoteMcpServer": {
"command": "npx",
"args": [
"mcp-remote",
"<ゲートウェイリソースURL>"
]
}
},
"tools": [
"*"
],
"toolAliases": {},
"allowedTools": [],
"resources": [],
"hooks": {},
"toolsSettings": {},
"useLegacyMcpJson": true
}
このエージェントをCLIから使ってみます。
q chat --agent my-agent
そうすると、ブラウザのポップアップが開き、Auth0の画面でログインが求められるはずです。

ログインすると、以下のような同意画面が表示されます。

同意したのちにAmazon Q Developer CLIに戻ってくると、このMCPサーバー経由でツールが使えるようになっているはずです。


DCRの実運用上の課題
今回はDCRを用いて動的なクライアント登録を行いましたが、これにはいくつかの根本的な課題があります。
1. データベースが無限に膨張する
DCRでは、ユーザーがクライアントをMCPサーバーに接続するたびに、新しい登録が作成されます。同じユーザーでも再接続したり、別のAIエージェントから接続するたびにクライアント数が増えていきます。それに伴って認可サーバーのデータベースが無制限に膨張していきます。
2. IDP側のクライアント数の制限
例えばAuth0の場合、無料プランは10個、有料プランでも100個までというクライアント数の上限があります。有料プランでも100人のユーザーが接続すると上限に達する可能性があります。
| プラン | クライアント数上限 |
|---|---|
| 無料プラン | 10個 |
| 有料プラン (Essentials/Professional) | 100個 |
| Enterpriseプラン | 実質無制限 (2,000 APIリソースサーバーまで) |
3. セキュリティ上の懸念
DCRの/registerエンドポイントは認証されていない書き込み操作を受け付けるため、DoS攻撃の脆弱性があります。また、クライアントIDの有効期限管理ができず、無効なIDをクライアントに伝える手段もありません。
このように、MCPのような開放的な環境では、DCRはスケーラビリティとセキュリティの両面で課題があります。
将来の展望:CIMDによる解決
この課題を根本的に解決するために、Client ID Metadata Documents (CIMD) という新しいアプローチの標準化が進んでいます。
MCPコミュニティでは、SEP-991としてCIMDの仕様化が進行中です。
CIMDは、OAuth 2.1における新しいクライアント識別方法です。従来のclient_id(ランダムな文字列)の代わりに、HTTPS URLをクライアントIDとして直接使用します。認可サーバーは、認可時にそのURLからクライアントのメタデータ(アプリケーション名、リダイレクト先URLなど)を取得します。
MCPサーバーの認証認可において、DCRの代わりにCIMDを利用することのメリットは以下です。
-
データベース膨張を回避
- 認可サーバー側でクライアント情報を保存する必要がない
- クライアント数制限の問題も解消される
-
登録エンドポイント不要
- 認証されていない書き込み操作がなくなり、DoS攻撃の脆弱性が解消される
-
有効期限の問題を解決
- URLがそのままIDであり、有効期限切れにならない
- 一つのアプリケーションにつき一つのURLで済む
まとめ
本記事では、Amazon Bedrock AgentCore GatewayとAuth0を使って、OAuth 2.1準拠のセキュアなリモートMCPサーバーを構築しました。
AgentCore Gatewayを使うことで、既存のLambda関数を登録するだけでMCPサーバー化でき、RFC 9728のサポートも自動で提供されます。Auth0のDCRサポートにより、Claude Code、Cursor、ChatGPT、Amazon Q Dev CLIなどのクライアントIDを手動設定できないAIツールからも認証認可付きのリモートMCPサーバーに接続できました。
ただし、本番ワークロードへの採用には注意が必要です。DCRには、データベースの無限膨張、IDP側のクライアント数制限、DoS攻撃の脆弱性など、実運用上の課題があります。これらの課題を解決する CIMD(Client ID Metadata Documents) の標準化がMCPコミュニティで進行中ですが、現時点ではプロトタイプ段階です。CIMDの標準化と各種ツール・サービスでの実装が完了すれば、本格的な本番ワークロードでの利用がより加速していくでしょう。
Discussion