DCR Proxy
DCR Proxyジェネレーターは、Amazon Cognito User Poolの前段にOAuth Dynamic Client Registration (DCR) プロキシを作成します。
MCPクライアント(Claude Code、Kiro CLI、MCP Inspectorなど)は、Dynamic Client RegistrationとメタデータディスカバリーをサポートするOAuth認可サーバーに対して認証することを期待します。Amazon CognitoはDCRをネイティブにサポートしておらず、そのApp Clientシークレットは決してパブリッククライアントに公開してはいけません。このプロキシはそのギャップを埋めます:Cognito Hosted UIフローをそのまま維持し、DCRを実装し、トークン交換時にサーバー側でApp Clientシークレットを注入し、MCPトラフィックを上流のMCPサーバーに転送します。
DCRプロキシの生成
Section titled “DCRプロキシの生成”- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - ts#dcr-proxy - 必須パラメータを入力
- クリック
Generate
pnpm nx g @aws/nx-plugin:ts#dcr-proxyyarn nx g @aws/nx-plugin:ts#dcr-proxynpx nx g @aws/nx-plugin:ts#dcr-proxybunx nx g @aws/nx-plugin:ts#dcr-proxy| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
| name | string | dcr-proxy | DCR プロキシの名前。TypeScript ハンドラープロジェクト、コンストラクト/モジュールのクラス名、および common/constructs または common/terraform 配下のディレクトリに使用されます |
| directory | string | packages | DCR プロキシハンドラープロジェクトを格納するディレクトリ。 |
| subDirectory | string | - | ハンドラープロジェクトが配置されるサブディレクトリ。デフォルトではプロジェクト名になります。 |
| iac | inherit | cdk | terraform | inherit | 優先する IaC プロバイダー(cdk または terraform)。デフォルトでは初期選択から継承されます。 |
| preferInstallDependencies | boolean | true | ジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する場合は false に設定してインストールを延期します(後続のジェネレーターが Nx プロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。 |
ジェネレーターの出力
Section titled “ジェネレーターの出力”ジェネレーターは、Lambdaハンドラーを含むスタンドアロンのTypeScriptプロジェクトと、選択したiacに基づいてそれらをデプロイするためのインフラストラクチャを作成します。
Directory<dcr-proxy-name>
Directorysrc/
Directoryhandlers/
- authorization-server-metadata.ts
/.well-known/oauth-authorization-serverと/.well-known/openid-configurationを提供 - protected-resource-metadata.ts
/.well-known/oauth-protected-resourceを提供 - register.ts RFC 7591 Dynamic Client Registration
- authorize.ts Cognito Hosted UIへリダイレクト
- token.ts App Clientシークレットを注入してトークンを交換
- mcp-proxy.ts
/mcpリクエストを上流のMCPサーバーにプロキシ
- authorization-server-metadata.ts
ハンドラーはRolldownで独立してバンドルされ、両方のIaCプロバイダーが結果のバンドル出力を参照します。
インフラストラクチャ
Section titled “インフラストラクチャ”このジェネレータは選択した iac に基づいてInfrastructure as Codeを生成するため、packages/common に関連するCDKコンストラクトまたはTerraformモジュールを含むプロジェクトを作成します。
共通のInfrastructure as Codeプロジェクトは以下の構造を持ちます:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ プロジェクト/ジェネレータ固有のインフラストラクチャ用コンストラクト
- …
Directorycore/
app内のコンストラクトで再利用される汎用コンストラクト- …
- index.ts
appからコンストラクトをエクスポートするエントリーポイント
- project.json プロジェクトのビルドターゲットと設定
Directorypackages/common/terraform
Directorysrc
Directoryapp/ プロジェクト/ジェネレータ固有のインフラストラクチャ用Terraformモジュール
- …
Directorycore/
app内のモジュールで再利用される汎用モジュール- …
- project.json プロジェクトのビルドターゲットと設定
プロキシをデプロイするために、以下のファイルが生成されます:
Directorypackages/common/constructs/src
Directoryapp
Directorydcr-proxies
Directory<dcr-proxy-name>
- <dcr-proxy-name>.ts プロキシをデプロイするCDKコンストラクト
Directorypackages/common/terraform/src
Directoryapp
Directorydcr-proxies
Directory<dcr-proxy-name>
- <dcr-proxy-name>.tf プロキシをデプロイするTerraformモジュール
インフラストラクチャは、以下のルートを持つAPI Gateway HTTP APIをプロビジョニングします:
| ルート | 説明 |
|---|---|
GET /.well-known/oauth-protected-resource | 保護されたリソースのメタデータ |
GET /.well-known/oauth-authorization-server | 認可サーバーのメタデータ |
GET /.well-known/openid-configuration | OpenID設定(認可サーバーメタデータハンドラーによって提供) |
POST /register | Dynamic Client Registration |
GET /authorize | 認可(Cognito Hosted UIへリダイレクト) |
POST /oauth/token | トークン交換(App Clientシークレットを注入) |
ANY /mcp | 上流のMCPサーバーへのプロキシ |
トークンハンドラーのみが、Secrets Manager内のCognito App Clientシークレットへの読み取りアクセスを許可されます。
DCR Proxyのデプロイ
Section titled “DCR Proxyのデプロイ”プロキシはCognitoリソースやMCPサーバーを作成しません。代わりに、他の場所で管理されているリソース(このプラグインによって生成されたものか、別途プロビジョニングされたもの)の識別子を注入し、プロキシをそれらのリソースのプロビジョニング方法から切り離します。
以下を提供します:
- Cognito User Pool idとApp Client id
- App Clientシークレットを保持するSecrets ManagerシークレットのARN。トークンハンドラーは実行時にこれを読み取ります。値はクライアントに公開されません。
- Cognito Hosted UIドメインのベースURL
- 上流のMCPサーバーの完全なURL
生成されたコンストラクトをスタック内でインスタンス化し、必要なプロパティを渡します:
import { DcrProxy } from ':my-scope/common-constructs';
new DcrProxy(this, 'DcrProxy', { userPoolId: userPool.userPoolId, userPoolClientId: userPoolClient.userPoolClientId, cognitoClientSecretArn: clientSecret.secretArn, cognitoHostedUiBase: userPoolDomain.baseUrl(), upstreamUrl: 'https://my-agentcore-runtime-url/mcp',});コンストラクトは、プロキシエンドポイント(proxyUrl、mcpUrl、metadataUrl、tokenEndpoint、registrationEndpoint)を読み取り専用プロパティとして公開します。
Terraform設定から生成されたモジュールを参照し、必要な変数を渡します:
module "dcr_proxy" { source = "../../common/terraform/src/app/dcr-proxies/dcr-proxy"
user_pool_id = aws_cognito_user_pool.main.id user_pool_client_id = aws_cognito_user_pool_client.main.id cognito_client_secret_arn = aws_secretsmanager_secret.client_secret.arn cognito_hosted_ui_base = "https://${aws_cognito_user_pool_domain.main.domain}.auth.${data.aws_region.current.region}.amazoncognito.com" upstream_url = "https://my-agentcore-runtime-url/mcp" asset_bucket_name = module.asset_bucket.bucket_name}モジュールは、プロキシエンドポイント(proxy_url、mcp_url、metadata_url、token_endpoint、registration_endpoint)を出力として公開します。
MCPサーバーの前段に配置
Section titled “MCPサーバーの前段に配置”ts#mcp-server(またはpy#mcp-server)ジェネレーターで--auth cognitoを使用して生成されたMCPサーバーを前段に配置するには、MCPサーバーとプロキシの両方に同じUser PoolとApp Clientを渡し、MCPサーバーコンストラクトのinvocationUrlをプロキシのupstreamUrlとして使用します。
import { DcrProxy, MyProjectMcpServer, UserIdentity,} from ':my-scope/common-constructs';import { OAuthScope } from 'aws-cdk-lib/aws-cognito';import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
const identity = new UserIdentity(this, 'Identity');
// プロキシがトークン交換に使用する機密App Client。クライアントが使用する// コールバックURLを登録します(以下を参照)。const proxyClient = identity.userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE], callbackUrls: [ 'http://localhost:41100/callback', // Claude Desktopで使用されるコールバック 'https://claude.ai/api/mcp/auth_callback', ], },});
// トークンハンドラーが読み取るためにApp ClientシークレットをSecrets Managerに保存const clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// 同じApp Client用に発行されたJWTを認可するMCPサーバーconst mcpServer = new MyProjectMcpServer(this, 'MyProjectMcpServer', { identity: { userPool: identity.userPool, userPoolClient: proxyClient, },});
new DcrProxy(this, 'DcrProxy', { userPoolId: identity.userPool.userPoolId, userPoolClientId: proxyClient.userPoolClientId, cognitoClientSecretArn: clientSecret.secretArn, cognitoHostedUiBase: identity.userPoolDomain.baseUrl(), // ハードコードではなく、MCPサーバーコンストラクトの呼び出しURLを使用 upstreamUrl: mcpServer.invocationUrl,});# プロキシがトークン交換に使用する機密App Client。クライアントが使用する# コールバックURLを登録します(以下を参照)。resource "aws_cognito_user_pool_client" "dcr_proxy" { name = "dcr-proxy-client" user_pool_id = module.user_identity.user_pool_id generate_secret = true allowed_oauth_flows = ["code"] allowed_oauth_flows_user_pool_client = true allowed_oauth_scopes = ["openid", "email", "profile"] callback_urls = [ "http://localhost:41100/callback", # Claude Desktopで使用されるコールバック "https://claude.ai/api/mcp/auth_callback", ]}
# トークンハンドラーが読み取るためにApp ClientシークレットをSecrets Managerに保存resource "aws_secretsmanager_secret" "client_secret" { name = "my-dcr-proxy-client-secret"}
resource "aws_secretsmanager_secret_version" "client_secret" { secret_id = aws_secretsmanager_secret.client_secret.id secret_string = aws_cognito_user_pool_client.dcr_proxy.client_secret}
# 同じApp Client用に発行されたJWTを認可するMCPサーバーmodule "my_project_mcp_server" { source = "../../common/terraform/src/app/mcp-servers/my-project-mcp-server"
user_pool_id = module.user_identity.user_pool_id user_pool_client_ids = [aws_cognito_user_pool_client.dcr_proxy.id] appconfig_application_id = module.runtime_config.application_id appconfig_application_arn = module.runtime_config.application_arn}
module "dcr_proxy" { source = "../../common/terraform/src/app/dcr-proxies/dcr-proxy"
user_pool_id = module.user_identity.user_pool_id user_pool_client_id = aws_cognito_user_pool_client.dcr_proxy.id cognito_client_secret_arn = aws_secretsmanager_secret.client_secret.arn cognito_hosted_ui_base = "https://${module.user_identity.user_pool_domain}.auth.${data.aws_region.current.region}.amazoncognito.com" # ハードコードではなく、MCPサーバーモジュールの呼び出しURLを使用 upstream_url = module.my_project_mcp_server.invocation_url asset_bucket_name = module.asset_bucket.bucket_name}AgentCore Gatewayの前段に配置
Section titled “AgentCore Gatewayの前段に配置”agentcore-gatewayジェネレーターで--auth cognitoを使用して生成されたAgentCore Gatewayを前段に配置するには、ゲートウェイとプロキシの両方に同じUser PoolとApp Clientを渡し、ゲートウェイコンストラクトのgatewayUrlをプロキシのupstreamUrlとして使用します。
import { DcrProxy, MyGateway, UserIdentity,} from ':my-scope/common-constructs';import { OAuthScope } from 'aws-cdk-lib/aws-cognito';import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
const identity = new UserIdentity(this, 'Identity');
// プロキシがトークン交換に使用する機密App Client。クライアントが使用する// コールバックURLを登録します(以下を参照)。const proxyClient = identity.userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE], callbackUrls: [ 'http://localhost:41100/callback', // Claude Desktopで使用されるコールバック 'https://claude.ai/api/mcp/auth_callback', ], },});
// トークンハンドラーが読み取るためにApp ClientシークレットをSecrets Managerに保存const clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// 同じApp Client用に発行されたJWTを認可するゲートウェイconst gateway = new MyGateway(this, 'MyGateway', { identity: { userPool: identity.userPool, userPoolClient: proxyClient, },});
new DcrProxy(this, 'DcrProxy', { userPoolId: identity.userPool.userPoolId, userPoolClientId: proxyClient.userPoolClientId, cognitoClientSecretArn: clientSecret.secretArn, cognitoHostedUiBase: identity.userPoolDomain.baseUrl(), // ハードコードではなく、ゲートウェイコンストラクトのURLを使用 upstreamUrl: gateway.gateway.gatewayUrl,});# プロキシがトークン交換に使用する機密App Client。クライアントが使用する# コールバックURLを登録します(以下を参照)。resource "aws_cognito_user_pool_client" "dcr_proxy" { name = "dcr-proxy-client" user_pool_id = module.user_identity.user_pool_id generate_secret = true allowed_oauth_flows = ["code"] allowed_oauth_flows_user_pool_client = true allowed_oauth_scopes = ["openid", "email", "profile"] callback_urls = [ "http://localhost:41100/callback", # Claude Desktopで使用されるコールバック "https://claude.ai/api/mcp/auth_callback", ]}
# トークンハンドラーが読み取るためにApp ClientシークレットをSecrets Managerに保存resource "aws_secretsmanager_secret" "client_secret" { name = "my-dcr-proxy-client-secret"}
resource "aws_secretsmanager_secret_version" "client_secret" { secret_id = aws_secretsmanager_secret.client_secret.id secret_string = aws_cognito_user_pool_client.dcr_proxy.client_secret}
# 同じApp Client用に発行されたJWTを認可するゲートウェイmodule "my_gateway" { source = "../../common/terraform/src/app/agentcore-gateway/my-gateway"
user_pool_id = module.user_identity.user_pool_id user_pool_client_ids = [aws_cognito_user_pool_client.dcr_proxy.id]}
module "dcr_proxy" { source = "../../common/terraform/src/app/dcr-proxies/dcr-proxy"
user_pool_id = module.user_identity.user_pool_id user_pool_client_id = aws_cognito_user_pool_client.dcr_proxy.id cognito_client_secret_arn = aws_secretsmanager_secret.client_secret.arn cognito_hosted_ui_base = "https://${module.user_identity.user_pool_domain}.auth.${data.aws_region.current.region}.amazoncognito.com" # ハードコードではなく、ゲートウェイモジュールのURLを使用 upstream_url = module.my_gateway.gateway_url asset_bucket_name = module.asset_bucket.bucket_name}クライアントリダイレクトURIの許可
Section titled “クライアントリダイレクトURIの許可”プロキシは仮想的にDynamic Client Registrationを実装するため — クライアントごとのCognito App Clientは存在しません — すべてのMCPクライアントは、プロキシに渡す単一のApp Clientを通じて認証します。OAuthフロー中、プロキシはクライアントのredirect_uriを変更せずにCognito Hosted UIに転送するため、Cognitoが権威的なチェックを実行します:コールバックはそのApp Clientのコールバック URLとして登録されている必要があり、そうでない場合Cognitoはログインを拒否します。
これは仮想DCR設計の副作用です。クライアントは任意のredirect_uriをプロキシに登録できますが、その正確なURLがApp Clientのコールバック URLの1つである場合にのみログインが成功します。Cognitoはコールバック URLをポートを含めて正確に照合するため、ランダムなエフェメラルポートでリッスンするクライアントはワイルドカードでカバーできません — 各クライアントを固定のコールバック URLにピン留めし、その正確なURLをApp Clientに登録する必要があります。
App Clientを作成する際に、クライアントが使用するコールバック URLを追加します:
const userPoolClient = userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, callbackUrls: [ // ローカルクライアント:デフォルトのポートではなく、固定の一般的でないポートにピン留め 'http://localhost:41100/callback', // Claude Desktopで使用されるコールバック 'https://claude.ai/api/mcp/auth_callback', ], },});resource "aws_cognito_user_pool_client" "dcr_proxy" { # ... generate_secret = true allowed_oauth_flows = ["code"] allowed_oauth_flows_user_pool_client = true callback_urls = [ # ローカルクライアント:デフォルトのポートではなく、固定の一般的でないポートにピン留め "http://localhost:41100/callback", # Claude Desktopで使用されるコールバック "https://claude.ai/api/mcp/auth_callback", ]}プロキシされたMCPサーバーの利用
Section titled “プロキシされたMCPサーバーの利用”プロキシにより、MCPクライアントはプロキシURL以外のクライアント固有の設定なしでCognito User Poolに対して認証できます。クライアントが/mcpエンドポイントに接続すると、OAuthメタデータ(/.well-known/oauth-protected-resourceと/.well-known/oauth-authorization-server経由)を検出し、動的に自身を登録し、Cognito Hosted UIログインを通じてユーザーを誘導します。プロキシはトークン交換中にApp Clientシークレットを注入するため、クライアントはそれを必要としません。
クライアントを接続するには、プロキシのmcpUrl(つまり<proxyUrl>/mcp)を指定します。以下の例では、プロキシがhttps://my-proxy.example.comにデプロイされていると仮定しています。
Claude Code
Section titled “Claude Code”claude mcp addコマンドを使用して、HTTPトランスポートでプロキシされたサーバーを追加します。デフォルトでは、Claude Codeはランダムなコールバックポートでリッスンします。--callback-portを渡して、App Clientに登録されたポート(上記の例では41100)にピン留めします。Claude Codeは常に/callbackパスを使用するため、結果のリダイレクトURIはhttp://localhost:41100/callbackになります:
claude mcp add --transport http --callback-port 41100 my-proxied-server https://my-proxy.example.com/mcpサーバーからツールを初めて呼び出すと、Claude CodeはCognito Hosted UIを開いて認証し、リクエストが上流にプロキシされる前に認証します。
Kiro CLI
Section titled “Kiro CLI”HTTPトランスポートを使用して、Kiro CLI MCP設定にサーバーを追加します。明示的なoauth.redirectUriがない場合、Kiroはランダムなコールバックポートを選択します。ポートとパスが正確に一致するように、App Clientに登録されたURLに設定します:
{ "mcpServers": { "my-proxied-server": { "type": "http", "url": "https://my-proxy.example.com/mcp", "oauth": { "redirectUri": "http://localhost:41100/callback" } } }}Kiro CLIは初回使用時にCognito Hosted UIログインをトリガーし、後続のリクエストのために結果のトークンを管理します。
UserIdentity User Poolの再利用
Section titled “UserIdentity User Poolの再利用”ts#website#authジェネレーター(UserIdentityコンストラクト)から既にUser Poolを持っている場合、Webサイトにログインする同じユーザーのためにMCPサーバーを前段に配置できます。そのuserPoolを再利用しますが、プロキシ用に別のApp Clientを追加します:Webサイトのクライアントはシークレットのないパブリッククライアントですが、DCRプロキシはトークン交換時にトークンハンドラーがシークレットを注入する機密クライアント(generateSecret: true)を必要とします。
UserIdentityは、Managed Login (version 2)でUser Poolドメインを設定します。Managed LoginはApp Clientごとにブランディングスタイルを必要とするため、新しいプロキシクライアント用に作成する必要があります — そうしないと、ホストされたログインページが403を返します。
import { DcrProxy, MyProjectMcpServer, UserIdentity,} from ':my-scope/common-constructs';import { OAuthScope, CfnManagedLoginBranding } from 'aws-cdk-lib/aws-cognito';import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
// ts#website#authによってWebサイトユーザー用に作成されたUser Poolconst identity = new UserIdentity(this, 'Identity');
// DCRプロキシ用の同じUser Pool上の機密App Clientconst proxyClient = identity.userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE], callbackUrls: ['http://localhost:41100/callback'], },});
// Managed Loginは新しいクライアント用のブランディングスタイルが必要new CfnManagedLoginBranding(this, 'DcrProxyClientBranding', { userPoolId: identity.userPool.userPoolId, clientId: proxyClient.userPoolClientId, useCognitoProvidedValues: true,});
const clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
const mcpServer = new MyProjectMcpServer(this, 'MyProjectMcpServer', { identity: { userPool: identity.userPool, userPoolClient: proxyClient, },});
new DcrProxy(this, 'DcrProxy', { userPoolId: identity.userPool.userPoolId, userPoolClientId: proxyClient.userPoolClientId, cognitoClientSecretArn: clientSecret.secretArn, // UserIdentityコンストラクトは常にドメインを作成 cognitoHostedUiBase: identity.userPoolDomain.baseUrl(), upstreamUrl: mcpServer.invocationUrl,});# ts#website#authによってWebサイトユーザー用に作成されたUser Poolモジュールmodule "user_identity" { source = "../../common/terraform/src/core/user-identity"}
# DCRプロキシ用の同じUser Pool上の機密App Clientresource "aws_cognito_user_pool_client" "dcr_proxy" { name = "dcr-proxy-client" user_pool_id = module.user_identity.user_pool_id generate_secret = true allowed_oauth_flows = ["code"] allowed_oauth_flows_user_pool_client = true allowed_oauth_scopes = ["openid", "email", "profile"] callback_urls = ["http://localhost:41100/callback"]}
# Managed Loginは新しいクライアント用のブランディングスタイルが必要resource "aws_cognito_managed_login_branding" "dcr_proxy" { user_pool_id = module.user_identity.user_pool_id client_id = aws_cognito_user_pool_client.dcr_proxy.id use_cognito_provided_values = true}
resource "aws_secretsmanager_secret" "client_secret" { name = "my-dcr-proxy-client-secret"}
resource "aws_secretsmanager_secret_version" "client_secret" { secret_id = aws_secretsmanager_secret.client_secret.id secret_string = aws_cognito_user_pool_client.dcr_proxy.client_secret}
module "my_project_mcp_server" { source = "../../common/terraform/src/app/mcp-servers/my-project-mcp-server"
user_pool_id = module.user_identity.user_pool_id user_pool_client_ids = [aws_cognito_user_pool_client.dcr_proxy.id] appconfig_application_id = module.runtime_config.application_id appconfig_application_arn = module.runtime_config.application_arn}
module "dcr_proxy" { source = "../../common/terraform/src/app/dcr-proxies/dcr-proxy"
user_pool_id = module.user_identity.user_pool_id user_pool_client_id = aws_cognito_user_pool_client.dcr_proxy.id cognito_client_secret_arn = aws_secretsmanager_secret.client_secret.arn cognito_hosted_ui_base = "https://${module.user_identity.user_pool_domain}.auth.${data.aws_region.current.region}.amazoncognito.com" upstream_url = module.my_project_mcp_server.invocation_url asset_bucket_name = module.asset_bucket.bucket_name}