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 secretは公開クライアントに公開してはいけません。このプロキシはそのギャップを埋めます:Cognito Hosted UIフローをそのまま維持し、DCRを実装し、トークン交換時にサーバー側でApp Client secretを注入し、MCPトラフィックを上流のMCPサーバーに転送します。
DCRプロキシを生成する
Section titled “DCRプロキシを生成する”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- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - ts#dcr-proxy - 必須パラメータを入力
- クリック
Generate
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
| 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 Serves
/.well-known/oauth-authorization-serverand/.well-known/openid-configuration - protected-resource-metadata.ts Serves
/.well-known/oauth-protected-resource - register.ts RFC 7591 Dynamic Client Registration
- authorize.ts Redirects to the Cognito Hosted UI
- token.ts Injects the App Client secret and exchanges the token
- mcp-proxy.ts Proxies
/mcprequests to the upstream MCP server
- authorization-server-metadata.ts Serves
ハンドラーはRolldownで独立してバンドルされ、両方のIaCプロバイダーは結果のバンドル出力を参照します。
インフラストラクチャ
Section titled “インフラストラクチャ”このジェネレーターは、選択した iac に基づいてインフラストラクチャをコードとして提供するため、関連する CDK コンストラクトまたは Terraform モジュールを含む packages/common にプロジェクトを作成します。
共通のインフラストラクチャコードプロジェクトは、次のように構成されています:
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 construct which deploys the proxy
Directorypackages/common/terraform/src
Directoryapp
Directorydcr-proxies
Directory<dcr-proxy-name>
- <dcr-proxy-name>.tf Terraform module which deploys the proxy
インフラストラクチャは、以下のルートを持つAPI Gateway HTTP APIをプロビジョニングします:
| ルート | 説明 |
|---|---|
GET /.well-known/oauth-protected-resource | Protected resource metadata |
GET /.well-known/oauth-authorization-server | Authorization server metadata |
GET /.well-known/openid-configuration | OpenID configuration (served by the authorization server metadata handler) |
POST /register | Dynamic Client Registration |
GET /authorize | Authorization (redirects to the Cognito Hosted UI) |
POST /oauth/token | Token exchange (injects the App Client secret) |
ANY /mcp | Proxy to the upstream MCP server |
トークンハンドラーのみが、Secrets Manager内のCognito App Client secretへの読み取りアクセスを許可されます。
DCR Proxyのデプロイ
Section titled “DCR Proxyのデプロイ”プロキシはCognitoリソースやMCPサーバーを作成しません。代わりに、他の場所で管理されているリソース(このプラグインによって生成されたものか、別途プロビジョニングされたもの)の識別子を注入し、プロキシをそれらのリソースのプロビジョニング方法から切り離します。
以下を提供します:
- Cognito User Pool idとApp Client id
- App Client secretを保持する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サーバーをフロントする”--auth cognitoを使用してts#mcp-server(またはpy#mcp-server)ジェネレーターで生成された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');
// A confidential App Client the proxy uses for the token exchange. Register the// callback URLs your clients use (see below).const proxyClient = identity.userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE], callbackUrls: [ 'http://localhost:41100/callback', // Callback used by Claude Desktop 'https://claude.ai/api/mcp/auth_callback', ], },});
// Store the App Client secret in Secrets Manager for the token handler to readconst clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// The MCP server, authorizing JWTs issued for the same App Clientconst 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(), // Use the MCP server construct's invocation URL rather than hardcoding it upstreamUrl: mcpServer.invocationUrl,});# A confidential App Client the proxy uses for the token exchange. Register the# callback URLs your clients use (see below).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", # Callback used by Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}
# Store the App Client secret in Secrets Manager for the token handler to readresource "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}
# The MCP server, authorizing JWTs issued for the same App Clientmodule "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" # Use the MCP server module's invocation URL rather than hardcoding it upstream_url = module.my_project_mcp_server.invocation_url asset_bucket_name = module.asset_bucket.bucket_name}AgentCore Gatewayをフロントする
Section titled “AgentCore Gatewayをフロントする”--auth cognitoを使用してagentcore-gatewayジェネレーターで生成された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');
// A confidential App Client the proxy uses for the token exchange. Register the// callback URLs your clients use (see below).const proxyClient = identity.userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE], callbackUrls: [ 'http://localhost:41100/callback', // Callback used by Claude Desktop 'https://claude.ai/api/mcp/auth_callback', ], },});
// Store the App Client secret in Secrets Manager for the token handler to readconst clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// The gateway, authorizing JWTs issued for the same App Clientconst 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(), // Use the gateway construct's URL rather than hardcoding it upstreamUrl: gateway.gateway.gatewayUrl,});# A confidential App Client the proxy uses for the token exchange. Register the# callback URLs your clients use (see below).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", # Callback used by Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}
# Store the App Client secret in Secrets Manager for the token handler to readresource "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}
# The gateway, authorizing JWTs issued for the same App Clientmodule "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" # Use the gateway module's URL rather than hardcoding it 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: [ // Local clients: pin to a fixed, uncommon port rather than a default one 'http://localhost:41100/callback', // Callback used by 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 = [ # Local clients: pin to a fixed, uncommon port rather than a default one "http://localhost:41100/callback", # Callback used by 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 secretを注入するため、クライアントはそれを必要としません。
クライアントを接続するには、プロキシのmcpUrl(つまり<proxyUrl>/mcp)を指定します。以下の例では、プロキシがhttps://my-proxy.example.comにデプロイされていることを前提としています。
Claude Code
Section titled “Claude Code”claude mcp addコマンドを使用して、HTTPトランスポートでプロキシされたサーバーを追加します。デフォルトでClaude Codeはランダムなコールバックポートでリッスンします。App Clientに登録されているポート(上記の例では41100)に固定するには、--callback-portを渡します。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';
// The user pool created by ts#website#auth for your website usersconst identity = new UserIdentity(this, 'Identity');
// A confidential App Client on the SAME user pool for the DCR proxyconst 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 needs a branding style for the new clientnew 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, // The UserIdentity construct always creates a domain cognitoHostedUiBase: identity.userPoolDomain.baseUrl(), upstreamUrl: mcpServer.invocationUrl,});# The user pool module created by ts#website#auth for your website usersmodule "user_identity" { source = "../../common/terraform/src/core/user-identity"}
# A confidential App Client on the SAME user pool for the DCR proxyresource "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 needs a branding style for the new clientresource "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}