DCR Proxy
El generador DCR Proxy crea un proxy OAuth Dynamic Client Registration (DCR) frente a un Amazon Cognito User Pool.
Los clientes MCP (como Claude Code, Kiro CLI o el MCP Inspector) esperan autenticarse contra un servidor de autorización OAuth que soporte Dynamic Client Registration y descubrimiento de metadatos. Amazon Cognito no soporta DCR de forma nativa, y su secreto de App Client nunca debe exponerse a un cliente público. Este proxy cierra esa brecha: mantiene intacto el flujo de Cognito Hosted UI, implementa DCR, inyecta el secreto de App Client del lado del servidor durante el intercambio de tokens y reenvía el tráfico MCP a tu servidor MCP upstream.
Generar un DCR proxy
Sección titulada «Generar un DCR proxy»- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - ts#dcr-proxy - Complete los parámetros requeridos
- Haga clic en
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-proxyTambién puede realizar una ejecución en seco para ver qué archivos se cambiarían
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-runOpciones
Sección titulada «Opciones»| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
| name | string | dcr-proxy | El nombre de tu proxy DCR, utilizado para su proyecto de handler TypeScript, el nombre de la clase del construct/módulo, y su directorio bajo common/constructs o common/terraform |
| directory | string | packages | El directorio donde almacenar el proyecto de handler del proxy DCR. |
| subDirectory | string | - | El subdirectorio donde se coloca el proyecto de handler. Por defecto, este es el nombre del proyecto. |
| iac | inherit | cdk | terraform | inherit | El proveedor IaC preferido (cdk o terraform). Por defecto, se hereda de tu selección inicial. |
| preferInstallDependencies | boolean | true | Si se prefiere instalar las dependencias después de que se ejecute el generador. Establece en false para diferir la instalación al agrupar múltiples generadores (la instalación aún se ejecuta si es necesaria para que los generadores subsiguientes puedan calcular el grafo de proyectos Nx); instala una vez al final. |
Salida del Generador
Sección titulada «Salida del Generador»El generador crea un proyecto TypeScript independiente que contiene los manejadores Lambda, e infraestructura para desplegarlos basándose en tu iac seleccionado.
Directorio<dcr-proxy-name>
Directoriosrc/
Directoriohandlers/
- authorization-server-metadata.ts Sirve
/.well-known/oauth-authorization-servery/.well-known/openid-configuration - protected-resource-metadata.ts Sirve
/.well-known/oauth-protected-resource - register.ts RFC 7591 Dynamic Client Registration
- authorize.ts Redirige a la Cognito Hosted UI
- token.ts Inyecta el secreto de App Client e intercambia el token
- mcp-proxy.ts Redirige las solicitudes
/mcpal servidor MCP upstream
- authorization-server-metadata.ts Sirve
Los manejadores se empaquetan de forma independiente con Rolldown, y ambos proveedores IaC hacen referencia a la salida del paquete resultante.
Infraestructura
Sección titulada «Infraestructura»Dado que este generador proporciona infraestructura como código basada en tu iac seleccionado, creará un proyecto en packages/common que incluye los constructos CDK o módulos de Terraform correspondientes.
El proyecto común de infraestructura como código tiene la siguiente estructura:
Directoriopackages/common/constructs
Directoriosrc
Directorioapp/ Constructos para infraestructura específica de un proyecto/generador
- …
Directoriocore/ Constructos genéricos reutilizados por los constructos en
app- …
- index.ts Punto de entrada que exporta los constructos de
app
- project.json Objetivos de compilación y configuración del proyecto
Directoriopackages/common/terraform
Directoriosrc
Directorioapp/ Módulos de Terraform para infraestructura específica de un proyecto/generador
- …
Directoriocore/ Módulos genéricos reutilizados por los módulos en
app- …
- project.json Objetivos de compilación y configuración del proyecto
Para desplegar el proxy, se generan los siguientes archivos:
Directoriopackages/common/constructs/src
Directorioapp
Directoriodcr-proxies
Directorio<dcr-proxy-name>
- <dcr-proxy-name>.ts Construcción CDK que despliega el proxy
Directoriopackages/common/terraform/src
Directorioapp
Directoriodcr-proxies
Directorio<dcr-proxy-name>
- <dcr-proxy-name>.tf Módulo Terraform que despliega el proxy
La infraestructura aprovisiona una API Gateway HTTP API con las siguientes rutas:
| Ruta | Descripción |
|---|---|
GET /.well-known/oauth-protected-resource | Metadatos de recurso protegido |
GET /.well-known/oauth-authorization-server | Metadatos del servidor de autorización |
GET /.well-known/openid-configuration | Configuración OpenID (servida por el manejador de metadatos del servidor de autorización) |
POST /register | Dynamic Client Registration |
GET /authorize | Autorización (redirige a la Cognito Hosted UI) |
POST /oauth/token | Intercambio de tokens (inyecta el secreto de App Client) |
ANY /mcp | Proxy al servidor MCP upstream |
Solo el manejador de tokens tiene acceso de lectura al secreto de Cognito App Client en Secrets Manager.
Desplegar el DCR Proxy
Sección titulada «Desplegar el DCR Proxy»El proxy no crea tus recursos de Cognito ni tu servidor MCP. En su lugar, inyectas los identificadores de recursos gestionados en otro lugar (ya sean generados por este plugin o aprovisionados por separado), manteniendo el proxy desacoplado de cómo se aprovisionan esos recursos.
Proporcionas:
- El id del Cognito User Pool y el id del App Client
- El ARN de un secreto de Secrets Manager que contiene el secreto del App Client. El manejador de tokens lo lee en tiempo de ejecución; el valor nunca se expone al cliente.
- La URL base del dominio de Cognito Hosted UI
- La URL completa de tu servidor MCP upstream
Instancia la construcción generada en tu stack, pasando las propiedades requeridas:
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',});La construcción expone los endpoints del proxy (proxyUrl, mcpUrl, metadataUrl, tokenEndpoint, registrationEndpoint) como propiedades de solo lectura.
Referencia el módulo generado desde tu configuración de Terraform, pasando las variables requeridas:
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}El módulo expone los endpoints del proxy (proxy_url, mcp_url, metadata_url, token_endpoint, registration_endpoint) como salidas.
Poner un Servidor MCP al Frente
Sección titulada «Poner un Servidor MCP al Frente»Para poner un servidor MCP generado con el generador ts#mcp-server (o py#mcp-server) usando --auth cognito al frente, pasa el mismo User Pool y App Client tanto al servidor MCP como al proxy, y usa el invocationUrl de la construcción del servidor MCP como el upstreamUrl del 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 confidencial que el proxy usa para el intercambio de tokens. Registra las// URLs de callback que usan tus clientes (ver abajo).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 usado por Claude Desktop 'https://claude.ai/api/mcp/auth_callback', ], },});
// Almacena el secreto de App Client en Secrets Manager para que el manejador de tokens lo leaconst clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// El servidor MCP, autorizando JWTs emitidos para el mismo 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(), // Usa la URL de invocación de la construcción del servidor MCP en lugar de codificarla upstreamUrl: mcpServer.invocationUrl,});# Un App Client confidencial que el proxy usa para el intercambio de tokens. Registra las# URLs de callback que usan tus clientes (ver abajo).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 usado por Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}
# Almacena el secreto de App Client en Secrets Manager para que el manejador de tokens lo learesource "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}
# El servidor MCP, autorizando JWTs emitidos para el mismo 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" # Usa la URL de invocación del módulo del servidor MCP en lugar de codificarla upstream_url = module.my_project_mcp_server.invocation_url asset_bucket_name = module.asset_bucket.bucket_name}Poner un AgentCore Gateway al Frente
Sección titulada «Poner un AgentCore Gateway al Frente»Para poner un AgentCore Gateway generado con el generador agentcore-gateway usando --auth cognito al frente, pasa el mismo User Pool y App Client tanto al gateway como al proxy, y usa el gatewayUrl de la construcción del gateway como el upstreamUrl del 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 confidencial que el proxy usa para el intercambio de tokens. Registra las// URLs de callback que usan tus clientes (ver abajo).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 usado por Claude Desktop 'https://claude.ai/api/mcp/auth_callback', ], },});
// Almacena el secreto de App Client en Secrets Manager para que el manejador de tokens lo leaconst clientSecret = new secretsmanager.Secret(this, 'ClientSecret', { secretStringValue: proxyClient.userPoolClientSecret,});
// El gateway, autorizando JWTs emitidos para el mismo 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(), // Usa la URL de la construcción del gateway en lugar de codificarla upstreamUrl: gateway.gateway.gatewayUrl,});# Un App Client confidencial que el proxy usa para el intercambio de tokens. Registra las# URLs de callback que usan tus clientes (ver abajo).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 usado por Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}
# Almacena el secreto de App Client en Secrets Manager para que el manejador de tokens lo learesource "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}
# El gateway, autorizando JWTs emitidos para el mismo 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" # Usa la URL del módulo del gateway en lugar de codificarla upstream_url = module.my_gateway.gateway_url asset_bucket_name = module.asset_bucket.bucket_name}Permitir URIs de Redirección de Clientes
Sección titulada «Permitir URIs de Redirección de Clientes»Debido a que el proxy implementa Dynamic Client Registration de forma virtual — no hay un Cognito App Client por cliente — cada cliente MCP se autentica a través del único App Client que pasas al proxy. Durante el flujo OAuth, el proxy reenvía el redirect_uri del cliente a la Cognito Hosted UI sin cambios, por lo que Cognito realiza la verificación autoritativa: el callback debe estar registrado como una URL de callback en ese App Client, de lo contrario Cognito rechaza el inicio de sesión.
Este es un efecto secundario del diseño DCR virtual. Un cliente puede registrar cualquier redirect_uri con el proxy, pero el inicio de sesión solo tiene éxito si esa URL exacta es una de las URLs de callback del App Client. Cognito coincide las URLs de callback exactamente, incluido el puerto, por lo que los clientes que escuchan en un puerto efímero aleatorio no pueden cubrirse con un comodín — debes fijar cada cliente a una URL de callback fija y registrar esa URL exacta en el App Client.
Agrega las URLs de callback que usan tus clientes cuando creas el App Client:
const userPoolClient = userPool.addClient('DcrProxyClient', { generateSecret: true, oAuth: { flows: { authorizationCodeGrant: true }, callbackUrls: [ // Clientes locales: fija a un puerto fijo y poco común en lugar de uno predeterminado 'http://localhost:41100/callback', // Callback usado por 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 = [ # Clientes locales: fija a un puerto fijo y poco común en lugar de uno predeterminado "http://localhost:41100/callback", # Callback usado por Claude Desktop "https://claude.ai/api/mcp/auth_callback", ]}Consumir un Servidor MCP con Proxy
Sección titulada «Consumir un Servidor MCP con Proxy»El proxy permite a los clientes MCP autenticarse contra tu Cognito User Pool sin ninguna configuración específica del cliente más allá de la URL del proxy. Cuando un cliente se conecta al endpoint /mcp, descubre los metadatos OAuth (a través de /.well-known/oauth-protected-resource y /.well-known/oauth-authorization-server), se registra dinámicamente y guía al usuario a través del inicio de sesión de Cognito Hosted UI. El proxy inyecta el secreto de App Client durante el intercambio de tokens, por lo que el cliente nunca lo necesita.
Para conectar un cliente, apúntalo al mcpUrl del proxy (es decir, <proxyUrl>/mcp). Los ejemplos a continuación asumen que tu proxy está desplegado en https://my-proxy.example.com.
Claude Code
Sección titulada «Claude Code»Agrega el servidor con proxy con el comando claude mcp add, usando el transporte HTTP. Por defecto, Claude Code escucha en un puerto de callback aleatorio; pasa --callback-port para fijarlo al puerto registrado en tu App Client (41100 en los ejemplos anteriores). Claude Code siempre usa la ruta /callback, por lo que el URI de redirección resultante es http://localhost:41100/callback:
claude mcp add --transport http --callback-port 41100 my-proxied-server https://my-proxy.example.com/mcpCuando invocas por primera vez una herramienta del servidor, Claude Code abre la Cognito Hosted UI para autenticarse antes de que la solicitud se redirija upstream.
Kiro CLI
Sección titulada «Kiro CLI»Agrega el servidor a tu configuración MCP de Kiro CLI, usando el transporte HTTP. Sin un oauth.redirectUri explícito, Kiro elige un puerto de callback aleatorio; configúralo a la URL registrada en tu App Client para que el puerto y la ruta coincidan exactamente:
{ "mcpServers": { "my-proxied-server": { "type": "http", "url": "https://my-proxy.example.com/mcp", "oauth": { "redirectUri": "http://localhost:41100/callback" } } }}Kiro CLI activa el inicio de sesión de Cognito Hosted UI en el primer uso y gestiona los tokens resultantes para solicitudes posteriores.
Reutilizar un User Pool de UserIdentity
Sección titulada «Reutilizar un User Pool de UserIdentity»Si ya tienes un User Pool del generador ts#website#auth (la construcción UserIdentity), puedes poner un servidor MCP frente a los mismos usuarios que inician sesión en tu sitio web. Reutiliza su userPool, pero agrega un App Client separado para el proxy: el cliente del sitio web es un cliente público sin secreto, mientras que el proxy DCR requiere un cliente confidencial (generateSecret: true) cuyo secreto el manejador de tokens inyecta durante el intercambio de tokens.
UserIdentity configura el dominio del User Pool con Managed Login (versión 2). Managed Login requiere un estilo de marca por App Client, por lo que debes crear uno para el nuevo cliente del proxy — de lo contrario, su página de inicio de sesión alojada devuelve 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';
// El user pool creado por ts#website#auth para los usuarios de tu sitio webconst identity = new UserIdentity(this, 'Identity');
// Un App Client confidencial en el MISMO user pool para el 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 necesita un estilo de marca para el nuevo clientenew 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, // La construcción UserIdentity siempre crea un dominio cognitoHostedUiBase: identity.userPoolDomain.baseUrl(), upstreamUrl: mcpServer.invocationUrl,});# El módulo de user pool creado por ts#website#auth para los usuarios de tu sitio webmodule "user_identity" { source = "../../common/terraform/src/core/user-identity"}
# Un App Client confidencial en el MISMO user pool para el 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 necesita un estilo de marca para el nuevo clienteresource "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}