Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ export default defineConfig({
label: 'agentcore-gateway',
link: '/guides/agentcore-gateway',
},
{ label: 'ts#dcr-proxy', link: '/guides/ts-dcr-proxy' },
],
},
{
Expand Down
127 changes: 127 additions & 0 deletions docs/src/content/docs/en/guides/ts-dcr-proxy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: DCR Proxy
description: Generate an OAuth Dynamic Client Registration proxy for Cognito-authenticated MCP servers
generator: ts#dcr-proxy
---

import { FileTree } from '@astrojs/starlight/components';
import Link from '@components/link.astro';
import RunGenerator from '@components/run-generator.astro';
import GeneratorParameters from '@components/generator-parameters.astro';
import NxCommands from '@components/nx-commands.astro';
import Infrastructure from '@components/infrastructure.astro';
import Snippet from '@components/snippet.astro';

The DCR Proxy generator creates an OAuth [Dynamic Client Registration (DCR)](https://datatracker.ietf.org/doc/html/rfc7591) proxy in front of an Amazon Cognito User Pool.

MCP clients (such as Claude Code, Kiro CLI or the MCP Inspector) expect to authenticate against an OAuth authorization server that supports Dynamic Client Registration and metadata discovery. Amazon Cognito does not support DCR natively, and its App Client secret must never be exposed to a public client. This proxy bridges that gap: it keeps the Cognito Hosted UI flow intact, implements DCR, injects the App Client secret server-side during the token exchange, and forwards MCP traffic to your upstream MCP server.

## Usage

### Generate a DCR proxy

<RunGenerator generator="ts#dcr-proxy" />

### Options

<GeneratorParameters generator="ts#dcr-proxy" />

## Generator Output

The generator creates a standalone TypeScript project containing the Lambda handlers, and infrastructure to deploy them based on your selected `iacProvider`.
Comment thread
drskur marked this conversation as resolved.

<FileTree>

- \<dcr-proxy-name>
- src/
- handlers/
- authorization-server-metadata.ts Serves `/.well-known/oauth-authorization-server` and `/.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 `/mcp` requests to the upstream MCP server

</FileTree>

The handlers are bundled independently with [Rolldown](https://rolldown.rs/), and both IaC providers reference the resulting bundle output.

### Infrastructure

<Snippet name="shared-constructs" />

<Infrastructure>
<Fragment slot="cdk">
The generator creates a CDK construct which deploys the proxy, residing in the `packages/common/constructs/src/app/dcr-proxies/<dcr-proxy-name>` directory.
</Fragment>
<Fragment slot="terraform">
The generator creates a Terraform module which deploys the proxy, residing in the `packages/common/terraform/src/app/dcr-proxies/<dcr-proxy-name>` directory.
</Fragment>
</Infrastructure>
Comment thread
drskur marked this conversation as resolved.

The infrastructure provisions an API Gateway HTTP API with six Lambda functions wired to the following routes:

| Route | Description |
| --- | --- |
| `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 |

Only the token handler is granted read access to the Cognito App Client secret in Secrets Manager.

## Using the DCR Proxy
Comment thread
drskur marked this conversation as resolved.

The proxy does not create your Cognito resources or your MCP server. Instead, you inject the identifiers of resources managed elsewhere (whether generated by this plugin or provisioned separately), keeping the proxy decoupled from how those resources are provisioned.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like how decoupled this is :) I think we should add a section to the docs for how to use this with the ts#mcp-server or py#mcp-server, ie I assume you generate it with --auth cognito, then pass the same user pool and client it uses. Would be nice to have full examples for both cdk and tf :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also if you rebase, the example can use the new invocation url property from #942 :)


You provide:

- The Cognito User Pool id and App Client id
- The ARN of a Secrets Manager secret holding the App Client secret. The token handler reads this at runtime; the value is never exposed to the client.
- The base URL of the Cognito Hosted UI domain
- The full URL of your upstream MCP server

<Infrastructure>
<Fragment slot="cdk">
Instantiate the generated construct in your stack, passing the required properties:

```typescript
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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like allowedRedirectPatterns needs to be set for the proxy to work? We should call out what this should be set to for eg claude code in a Consuming a Proxied MCP Server

});
```

The construct exposes the proxy endpoints (`proxyUrl`, `mcpUrl`, `metadataUrl`, `tokenEndpoint`, `registrationEndpoint`) as readonly properties.
</Fragment>
<Fragment slot="terraform">
Reference the generated module from your Terraform configuration, passing the required variables:

```hcl
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
}
```

The module exposes the proxy endpoints (`proxy_url`, `mcp_url`, `metadata_url`, `token_endpoint`, `registration_endpoint`) as outputs.
</Fragment>
</Infrastructure>

:::note
The `/mcp` proxy uses API Gateway HTTP API integrations, which have a hard 29 second timeout. Long-running upstream requests are buffered by the proxy rather than streamed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use a REST API with response streaming to avoid this limitation? :)

:::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could we add a section like ## Consuming a Proxied MCP Server and include some documentation on how this enables agents to invoke an mcp server with auth? eg how do you set up Claude Code, Kiro CLI, or MCP Inspector to point to your proxied MCP server

6 changes: 6 additions & 0 deletions packages/nx-plugin/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@
"description": "Generate a TypeScript lambda function",
"metric": "g21"
},
"ts#dcr-proxy": {
"factory": "./src/ts/dcr-proxy/generator",
"schema": "./src/ts/dcr-proxy/schema.json",
"description": "Generate an OAuth Dynamic Client Registration (DCR) proxy construct for Cognito-authenticated MCP servers",
"metric": "g56"
},
"ts#mcp-server": {
"factory": "./src/ts/mcp-server/generator",
"schema": "./src/ts/mcp-server/schema.json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ The following list of generators are what is currently available in the \`@aws/n

- **ts#lambda-function**: Generate a TypeScript lambda function

- **ts#dcr-proxy**: Generate an OAuth Dynamic Client Registration (DCR) proxy construct for Cognito-authenticated MCP servers

- **ts#mcp-server**: Generate a TypeScript Model Context Protocol (MCP) server for providing context to Large Language Models

- **ts#nx-generator**: Generator for adding an Nx Generator to an existing TypeScript project
Expand Down
Loading
Loading