Skip to content

feat: ts dcr proxy generator#939

Open
drskur wants to merge 5 commits into
awslabs:mainfrom
drskur:feat/ts-dcr-proxy-generator
Open

feat: ts dcr proxy generator#939
drskur wants to merge 5 commits into
awslabs:mainfrom
drskur:feat/ts-dcr-proxy-generator

Conversation

@drskur

@drskur drskur commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Reason for this change

Cognito does not support DCR (Dynamic Client Registration), which made it hard to implement authentication via OAuth. So I implemented a generator for a proxy that virtually handles DCR.

Description of changes

  • Generate the handlers needed for the DCR proxy as a dcr-proxy project
  • Generate the IaC to serve them with AWS Lambda and API Gateway (cdk / terraform)

Description of how you validated changes

  • Updated and extended the generator unit tests, including a new terraform describe block covering module generation, the shared core http-api module, all seven routes, token-only secret access, the mcp-proxy
    upstream configuration, the shared terraform build dependency, and idempotency. Snapshots were regenerated.
  • The full @aws/nx-plugin test suite passes (133 files, 2644 tests), along with compile and lint.
  • The docs build succeeds and generates the new guide page.

Issue # (if applicable)

Closes #.

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

drskur added 5 commits July 3, 2026 14:49
Adds a ts#dcr-proxy generator that scaffolds an OAuth Dynamic Client
Registration (DCR) proxy CDK construct for Cognito-authenticated MCP
servers, ported from the standalone @drskur/dcr-proxy package.

- Generates the DcrProxy construct plus 6 TypeScript Lambda handlers
  (authorize, token, register, mcp-proxy, and two metadata endpoints)
  bundled at synth time via NodejsFunction (esbuild)
- Emits only into packages/common/constructs (no standalone project);
  Cognito UserPool/Client/Domain and the upstream MCP URL are injected
  by the consumer's own stack via DcrProxyProps
- Defaults the name to 'dcr-proxy'; CDK-only (throws on terraform)
- token handler is the sole reader of the Secrets Manager client secret
…objects

Change the DCR proxy construct props to take plain string identifiers
(userPoolId, userPoolClientId, cognitoHostedUiBase) and a Secrets Manager
secret ARN (cognitoClientSecretArn) instead of Cognito L2 objects. The
construct now references an existing secret via fromSecretCompleteArn rather
than creating one from the client's generated secret, giving an IaC-neutral
interface and decoupling the proxy from how Cognito resources are provisioned.
Move the DCR proxy Lambda handlers out of the shared constructs package into a
standalone TypeScript project that is bundled with rolldown, so both CDK and
Terraform can reference the same bundle output. Previously the generator was
CDK-only because it relied on NodejsFunction's synth-time esbuild bundling.

- Generate a dedicated TS handler project (tsProjectGenerator) and add a
  rolldown bundle target per handler
- CDK construct now uses Function + Code.fromAsset pointing at the bundles
  instead of NodejsFunction
- Add a Terraform module (6 Lambdas, Secrets Manager data source scoped to the
  token handler, shared core http-api module, 7 routes) mirroring the construct
- Accept cdk/terraform/inherit for iac and remove the terraform throw
- Reference the client secret by ARN via a data source so the value is never
  exposed in Terraform state
@codecov-commenter

codecov-commenter commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.61702% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.65%. Comparing base (2396847) to head (5fab451).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...utils/dcr-proxy-constructs/dcr-proxy-constructs.ts 89.47% 1 Missing and 1 partial ⚠️
packages/nx-plugin/src/ts/dcr-proxy/generator.ts 96.42% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #939      +/-   ##
==========================================
+ Coverage   87.56%   87.65%   +0.08%     
==========================================
  Files         162      164       +2     
  Lines        6007     6066      +59     
  Branches     1450     1461      +11     
==========================================
+ Hits         5260     5317      +57     
- Misses        375      377       +2     
  Partials      372      372              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cogwirrel cogwirrel left a comment

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.

Thanks so much @drskur! This is awesome, just have a few minor comments :)

Comment thread docs/src/content/docs/en/guides/ts-dcr-proxy.mdx
Comment thread docs/src/content/docs/en/guides/ts-dcr-proxy.mdx
</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? :)

Comment thread docs/src/content/docs/en/guides/ts-dcr-proxy.mdx

:::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.

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

function_name = "snapshot-proxy-\${each.key}-\${random_string.suffix.result}"
role = each.key == "token" ? aws_iam_role.token.arn : aws_iam_role.proxy.arn
handler = "index.handler"
runtime = "nodejs20.x"

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.

Same here can we use a newer node? :)

await tsDcrProxyGenerator(tree, { name: 'my-proxy', iac: 'cdk' });

expect(tree.exists(`${constructDir}/my-proxy.ts`)).toBe(true);
for (const handler of [

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 snapshot each handler? :)

import { createTreeUsingTsSolutionSetup } from '../../utils/test';
import { TS_DCR_PROXY_GENERATOR_INFO, tsDcrProxyGenerator } from './generator';

describe('ts#dcr-proxy generator', () => {

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 you also add ts#dcr-proxy to the generator-matrix.ts so it participates in the smoke tests?

const UPSTREAM_URL = process.env.UPSTREAM_URL!;
const PROXY_METADATA_URL = process.env.PROXY_METADATA_URL!;

const HOP_BY_HOP = new Set([

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.

I'm not sure what "hop by hop" means :) Is there a better name and/or could we add a short comment explaining what these headers are used for?

Comment on lines +153 to +160
// The token handler imports @aws-sdk/client-secrets-manager (provided by the
// Lambda runtime but needed for type-checking and local dev) and the handlers
// are typed against @types/aws-lambda.
addDependenciesToPackageJson(
tree,
withVersions(['@aws-sdk/client-secrets-manager']),
withVersions(['@types/aws-lambda']),
);

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.

I think this should be in dcr-proxy/generator.ts? The dependencies are for the handlers and this is currently only called if vending cdk infrastructure.

const cognitoIssuer = `https://cognito-idp.${region}.amazonaws.com/${props.userPoolId}`;

const baseEnv: Record<string, string> = {
COGNITO_USER_POOL_ID: props.userPoolId,

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.

nit: looks like this env variable is unused? :)

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


## Using the DCR Proxy

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants