-
Notifications
You must be signed in to change notification settings - Fork 30
feat: ts dcr proxy generator #939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cb6371f
dbbefbd
4401dd5
1e5d00c
5fab451
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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`. | ||
|
|
||
| <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> | ||
|
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 | ||
|
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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like |
||
| }); | ||
| ``` | ||
|
|
||
| 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? :) |
||
| ::: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please could we add a section like |
||
Uh oh!
There was an error while loading. Please reload this page.