Proxy DCR
Le générateur DCR Proxy crée un proxy OAuth Dynamic Client Registration (DCR) devant un Amazon Cognito User Pool.
Les clients MCP (tels que Claude Code, Kiro CLI ou le MCP Inspector) s’attendent à s’authentifier auprès d’un serveur d’autorisation OAuth qui prend en charge Dynamic Client Registration et la découverte de métadonnées. Amazon Cognito ne prend pas en charge DCR nativement, et son secret App Client ne doit jamais être exposé à un client public. Ce proxy comble cette lacune : il maintient le flux Cognito Hosted UI intact, implémente DCR, injecte le secret App Client côté serveur lors de l’échange de jeton, et transmet le trafic MCP à votre serveur MCP en amont.
Utilisation
Section intitulée « Utilisation »Générer un proxy DCR
Section intitulée « Générer un proxy DCR »- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - ts#dcr-proxy - Remplissez les paramètres requis
- Cliquez sur
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-proxyVous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:ts#dcr-proxy --dry-runyarn nx g @aws/nx-plugin:ts#dcr-proxy --dry-runnpx nx g @aws/nx-plugin:ts#dcr-proxy --dry-runbunx nx g @aws/nx-plugin:ts#dcr-proxy --dry-run| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
| name | string | dcr-proxy | Le nom de votre proxy DCR, utilisé pour son projet de gestionnaire TypeScript, le nom de la classe construct/module, et son répertoire sous common/constructs ou common/terraform |
| directory | string | packages | Le répertoire dans lequel stocker le projet de gestionnaire du proxy DCR. |
| subDirectory | string | - | Le sous-répertoire dans lequel le projet de gestionnaire est placé. Par défaut, il s'agit du nom du projet. |
| iac | inherit | cdk | terraform | inherit | Le fournisseur IaC préféré (cdk ou terraform). Par défaut, cela est hérité de votre sélection initiale. |
| preferInstallDependencies | boolean | true | Indique s'il faut privilégier l'installation des dépendances après l'exécution du générateur. Définir sur false pour différer l'installation lors du traitement par lots de plusieurs générateurs (une installation s'exécute toujours si nécessaire pour que les générateurs suivants puissent calculer le graphe de projet Nx) ; installer une fois à la fin. |
Sortie du générateur
Section intitulée « Sortie du générateur »Le générateur crée un projet TypeScript autonome contenant les gestionnaires Lambda, et l’infrastructure pour les déployer en fonction de votre iac sélectionné.
Répertoire<dcr-proxy-name>
Répertoiresrc/
Répertoirehandlers/
- authorization-server-metadata.ts Sert
/.well-known/oauth-authorization-serveret/.well-known/openid-configuration - protected-resource-metadata.ts Sert
/.well-known/oauth-protected-resource - register.ts RFC 7591 Dynamic Client Registration
- authorize.ts Redirige vers le Cognito Hosted UI
- token.ts Injecte le secret App Client et échange le jeton
- mcp-proxy.ts Proxie les requêtes
/mcpvers le serveur MCP en amont
- authorization-server-metadata.ts Sert
Les gestionnaires sont regroupés indépendamment avec Rolldown, et les deux fournisseurs IaC référencent la sortie du bundle résultant.
Infrastructure
Section intitulée « Infrastructure »Ce générateur fournit de l’infrastructure as code basée sur votre iac choisi. Il créera un projet dans packages/common qui inclut les constructions CDK ou modules Terraform pertinents.
Le projet commun d’infrastructure as code est structuré comme suit :
Répertoirepackages/common/constructs
Répertoiresrc
Répertoireapp/ Constructions pour l’infrastructure spécifique à un projet/générateur
- …
Répertoirecore/ Constructions génériques réutilisées par celles dans
app- …
- index.ts Point d’entrée exportant les constructions depuis
app
- project.json Cibles de build et configuration du projet
Répertoirepackages/common/terraform
Répertoiresrc
Répertoireapp/ Modules Terraform pour l’infrastructure spécifique à un projet/générateur
- …
Répertoirecore/ Modules génériques réutilisés par ceux dans
app- …
- project.json Cibles de build et configuration du projet
Pour déployer le proxy, les fichiers suivants sont générés :
Répertoirepackages/common/constructs/src
Répertoireapp
Répertoiredcr-proxies
Répertoire<dcr-proxy-name>
- <dcr-proxy-name>.ts Construct CDK qui déploie le proxy
Répertoirepackages/common/terraform/src
Répertoireapp
Répertoiredcr-proxies
Répertoire<dcr-proxy-name>
- <dcr-proxy-name>.tf Module Terraform qui déploie le proxy
L’infrastructure provisionne une API Gateway HTTP API avec les routes suivantes :
| Route | Description |
|---|---|
GET /.well-known/oauth-protected-resource | Métadonnées de ressource protégée |
GET /.well-known/oauth-authorization-server | Métadonnées du serveur d’autorisation |
GET /.well-known/openid-configuration | Configuration OpenID (servie par le gestionnaire de métadonnées du serveur d’autorisation) |
POST /register | Dynamic Client Registration |
GET /authorize | Autorisation (redirige vers le Cognito Hosted UI) |
POST /oauth/token | Échange de jeton (injecte le secret App Client) |
ANY /mcp | Proxy vers le serveur MCP en amont |
Seul le gestionnaire de jeton dispose d’un accès en lecture au secret Cognito App Client dans Secrets Manager.
Déploiement du proxy DCR
Section intitulée « Déploiement du proxy DCR »Le proxy ne crée pas vos ressources Cognito ni votre serveur MCP. Au lieu de cela, vous injectez les identifiants de ressources gérées ailleurs (qu’elles soient générées par ce plugin ou provisionnées séparément), gardant le proxy découplé de la manière dont ces ressources sont provisionnées.
Vous fournissez :
- L’id du Cognito User Pool et l’id de l’App Client
- L’ARN d’un secret Secrets Manager contenant le secret App Client. Le gestionnaire de jeton le lit à l’exécution ; la valeur n’est jamais exposée au client.
- L’URL de base du domaine Cognito Hosted UI
- L’URL complète de votre serveur MCP en amont
Instanciez le construct généré dans votre stack, en passant les propriétés requises :
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',});Le construct expose les points de terminaison du proxy (proxyUrl, mcpUrl, metadataUrl, tokenEndpoint, registrationEndpoint) en tant que propriétés en lecture seule.
Référencez le module généré depuis votre configuration Terraform, en passant les variables requises :
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}Le module expose les points de terminaison du proxy (proxy_url, mcp_url, metadata_url, token_endpoint, registration_endpoint) en tant que sorties.
Fronter un serveur MCP
Section intitulée « Fronter un serveur MCP »Pour fronter un serveur MCP généré avec le générateur ts#mcp-server (ou py#mcp-server) en utilisant --auth cognito, passez le même User Pool et App Client au serveur MCP et au proxy, et utilisez l’invocationUrl du construct du serveur MCP comme upstreamUrl du proxy.
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');
// Un App Client confidentiel que le proxy utilise pour l'échange de jeton. Enregistrez les// URL de rappel que vos clients utilisent (voir ci-dessous).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 utilisé par Claude Desktop 'https://claude.ai/api/mcp/auth_callback', ], },});
// Stockez le secret App Client dans Secrets Manager pour que le gestionnaire de jeton le liseconst clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// Le serveur MCP, autorisant les JWT émis pour le même 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(), // Utilisez l'URL d'invocation du construct du serveur MCP plutôt que de la coder en dur upstreamUrl: mcpServer.invocationUrl,});# Un App Client confidentiel que le proxy utilise pour l'échange de jeton. Enregistrez les# URL de rappel que vos clients utilisent (voir ci-dessous).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 utilisé par Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}
# Stockez le secret App Client dans Secrets Manager pour que le gestionnaire de jeton le liseresource "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}
# Le serveur MCP, autorisant les JWT émis pour le même 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" # Utilisez l'URL d'invocation du module du serveur MCP plutôt que de la coder en dur upstream_url = module.my_project_mcp_server.invocation_url asset_bucket_name = module.asset_bucket.bucket_name}Fronter une AgentCore Gateway
Section intitulée « Fronter une AgentCore Gateway »Pour fronter une AgentCore Gateway générée avec le générateur agentcore-gateway en utilisant --auth cognito, passez le même User Pool et App Client à la gateway et au proxy, et utilisez le gatewayUrl du construct de la gateway comme upstreamUrl du proxy.
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');
// Un App Client confidentiel que le proxy utilise pour l'échange de jeton. Enregistrez les// URL de rappel que vos clients utilisent (voir ci-dessous).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 utilisé par Claude Desktop 'https://claude.ai/api/mcp/auth_callback', ], },});
// Stockez le secret App Client dans Secrets Manager pour que le gestionnaire de jeton le liseconst clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// La gateway, autorisant les JWT émis pour le même 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(), // Utilisez l'URL du construct de la gateway plutôt que de la coder en dur upstreamUrl: gateway.gateway.gatewayUrl,});# Un App Client confidentiel que le proxy utilise pour l'échange de jeton. Enregistrez les# URL de rappel que vos clients utilisent (voir ci-dessous).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 utilisé par Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}
# Stockez le secret App Client dans Secrets Manager pour que le gestionnaire de jeton le liseresource "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}
# La gateway, autorisant les JWT émis pour le même 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" # Utilisez l'URL du module de la gateway plutôt que de la coder en dur upstream_url = module.my_gateway.gateway_url asset_bucket_name = module.asset_bucket.bucket_name}Autoriser les URI de redirection client
Section intitulée « Autoriser les URI de redirection client »Parce que le proxy implémente Dynamic Client Registration virtuellement — il n’y a pas d’App Client Cognito par client — chaque client MCP s’authentifie via le seul App Client que vous passez au proxy. Pendant le flux OAuth, le proxy transmet le redirect_uri du client au Cognito Hosted UI sans modification, donc Cognito effectue la vérification faisant autorité : le callback doit être enregistré en tant qu’URL de rappel sur cet App Client, sinon Cognito rejette la connexion.
C’est un effet secondaire de la conception DCR virtuelle. Un client peut enregistrer n’importe quel redirect_uri avec le proxy, mais la connexion ne réussit que si cette URL exacte est l’une des URL de rappel de l’App Client. Cognito fait correspondre les URL de rappel exactement, y compris le port, donc les clients qui écoutent sur un port éphémère aléatoire ne peuvent pas être couverts par un caractère générique — vous devez épingler chaque client à une URL de rappel fixe et enregistrer cette URL exacte sur l’App Client.
Ajoutez les URL de rappel que vos clients utilisent lorsque vous créez l’App Client :
const userPoolClient = userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, callbackUrls: [ // Clients locaux : épinglez à un port fixe et peu commun plutôt qu'à un port par défaut 'http://localhost:41100/callback', // Callback utilisé par 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 = [ # Clients locaux : épinglez à un port fixe et peu commun plutôt qu'à un port par défaut "http://localhost:41100/callback", # Callback utilisé par Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}Consommer un serveur MCP proxié
Section intitulée « Consommer un serveur MCP proxié »Le proxy permet aux clients MCP de s’authentifier auprès de votre Cognito User Pool sans aucune configuration spécifique au client au-delà de l’URL du proxy. Lorsqu’un client se connecte au point de terminaison /mcp, il découvre les métadonnées OAuth (via /.well-known/oauth-protected-resource et /.well-known/oauth-authorization-server), s’enregistre dynamiquement et guide l’utilisateur à travers la connexion Cognito Hosted UI. Le proxy injecte le secret App Client lors de l’échange de jeton, donc le client n’en a jamais besoin.
Pour connecter un client, pointez-le vers le mcpUrl du proxy (c’est-à-dire <proxyUrl>/mcp). Les exemples ci-dessous supposent que votre proxy est déployé sur https://my-proxy.example.com.
Claude Code
Section intitulée « Claude Code »Ajoutez le serveur proxié avec la commande claude mcp add, en utilisant le transport HTTP. Par défaut, Claude Code écoute sur un port de rappel aléatoire ; passez --callback-port pour l’épingler au port enregistré sur votre App Client (41100 dans les exemples ci-dessus). Claude Code utilise toujours le chemin /callback, donc l’URI de redirection résultant est http://localhost:41100/callback :
claude mcp add --transport http --callback-port 41100 my-proxied-server https://my-proxy.example.com/mcpLorsque vous invoquez pour la première fois un outil du serveur, Claude Code ouvre le Cognito Hosted UI pour s’authentifier avant que la requête ne soit proxiée en amont.
Kiro CLI
Section intitulée « Kiro CLI »Ajoutez le serveur à votre configuration MCP Kiro CLI, en utilisant le transport HTTP. Sans oauth.redirectUri explicite, Kiro choisit un port de rappel aléatoire ; définissez-le sur l’URL enregistrée sur votre App Client afin que le port et le chemin correspondent exactement :
{ "mcpServers": { "my-proxied-server": { "type": "http", "url": "https://my-proxy.example.com/mcp", "oauth": { "redirectUri": "http://localhost:41100/callback" } } }}Kiro CLI déclenche la connexion Cognito Hosted UI lors de la première utilisation et gère les jetons résultants pour les requêtes ultérieures.
Réutiliser un User Pool UserIdentity
Section intitulée « Réutiliser un User Pool UserIdentity »Si vous avez déjà un User Pool provenant du générateur ts#website#auth (le construct UserIdentity), vous pouvez fronter un serveur MCP pour les mêmes utilisateurs qui se connectent à votre site web. Réutilisez son userPool, mais ajoutez un App Client séparé pour le proxy : le client du site web est un client public sans secret, tandis que le proxy DCR nécessite un client confidentiel (generateSecret: true) dont le secret est injecté par le gestionnaire de jeton lors de l’échange de jeton.
UserIdentity configure le domaine User Pool avec Managed Login (version 2). Managed Login nécessite un style de branding par App Client, vous devez donc en créer un pour le nouveau client proxy — sinon sa page de connexion hébergée renvoie 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';
// Le user pool créé par ts#website#auth pour les utilisateurs de votre site webconst identity = new UserIdentity(this, 'Identity');
// Un App Client confidentiel sur le MÊME user pool pour le proxy DCRconst 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 nécessite un style de branding pour le nouveau 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, // Le construct UserIdentity crée toujours un domaine cognitoHostedUiBase: identity.userPoolDomain.baseUrl(), upstreamUrl: mcpServer.invocationUrl,});# Le module user pool créé par ts#website#auth pour les utilisateurs de votre site webmodule "user_identity" { source = "../../common/terraform/src/core/user-identity"}
# Un App Client confidentiel sur le MÊME user pool pour le proxy DCRresource "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 nécessite un style de branding pour le nouveau 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}