@aws/nx-plugin 1.0.0-rc.44 → 1.0.0-rc.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generators.json +6 -0
- package/package.json +1 -1
- package/src/agentcore-gateway/files/cedar/policies/README.md +9 -0
- package/src/agentcore-gateway/files/cedar/policies/permit-all.cedar +15 -0
- package/src/agentcore-gateway/generator.js +12 -5
- package/src/agentcore-gateway/generator.js.map +1 -1
- package/src/agentcore-gateway/schema.d.js.map +1 -1
- package/src/agentcore-gateway/schema.d.ts +1 -1
- package/src/agentcore-gateway/schema.json +3 -2
- package/src/mcp-server/tools/general-guidance.js +1 -1
- package/src/mcp-server/tools/general-guidance.js.map +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +5 -2
- package/src/py/agent/gateway-connection/generator.js +1 -1
- package/src/py/agent/gateway-connection/generator.js.map +1 -1
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +16 -0
- package/src/py/rdb/files/Dockerfile.create-db-user.template +4 -0
- package/src/py/rdb/files/Dockerfile.migration.template +4 -0
- package/src/sdk/ts.d.ts +2 -0
- package/src/sdk/ts.js +2 -0
- package/src/sdk/ts.js.map +1 -1
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +5 -1
- package/src/ts/agent/files/deploy/Dockerfile.template +5 -1
- package/src/ts/agent/gateway-connection/generator.js +1 -1
- package/src/ts/agent/gateway-connection/generator.js.map +1 -1
- package/src/ts/agent/generator.js +6 -0
- package/src/ts/agent/generator.js.map +1 -1
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +982 -0
- package/src/ts/dcr-proxy/generator.d.ts +10 -0
- package/src/ts/dcr-proxy/generator.js +98 -0
- package/src/ts/dcr-proxy/generator.js.map +1 -0
- package/src/ts/dcr-proxy/schema.d.js +6 -0
- package/src/ts/dcr-proxy/schema.d.js.map +1 -0
- package/src/ts/dcr-proxy/schema.d.ts +13 -0
- package/src/ts/dcr-proxy/schema.json +44 -0
- package/src/ts/mcp-server/files/Dockerfile.template +5 -1
- package/src/ts/mcp-server/generator.js +1 -0
- package/src/ts/mcp-server/generator.js.map +1 -1
- package/src/ts/nx-plugin/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/nx-plugin/files/mcp-server/resources/GENERAL_GUIDANCE.md.template +1 -1
- package/src/ts/nx-plugin/files/mcp-server/utils.ts.template +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +7 -0
- package/src/ts/rdb/files/Dockerfile.template +7 -0
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -4
- package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +1 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.js +2 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.ts.template +38 -3
- package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +13 -3
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +22 -1
- package/src/utils/commands.js +1 -1
- package/src/utils/commands.js.map +1 -1
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.d.ts +38 -0
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js +93 -0
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js.map +1 -0
- package/src/utils/dcr-proxy-constructs/files/cdk/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.ts.template +227 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/authorization-server-metadata.ts.template +39 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/authorize.ts.template +26 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/mcp-proxy.ts.template +89 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/protected-resource-metadata.ts.template +13 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/register.ts.template +70 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/token.ts.template +75 -0
- package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +410 -0
- package/src/utils/docker.d.ts +4 -3
- package/src/utils/docker.js +11 -7
- package/src/utils/docker.js.map +1 -1
- package/src/utils/format.d.ts +1 -1
- package/src/utils/format.js +3 -2
- package/src/utils/format.js.map +1 -1
- package/src/utils/init.js +4 -0
- package/src/utils/init.js.map +1 -1
- package/src/utils/versions.d.ts +3 -1
- package/src/utils/versions.js +4 -0
- package/src/utils/versions.js.map +1 -1
|
@@ -34,11 +34,20 @@ export interface AgentCoreGatewayProps {
|
|
|
34
34
|
* @default true
|
|
35
35
|
*/
|
|
36
36
|
readonly enableWaf?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Inbound authorizer for the gateway. Defaults to IAM (SigV4) authentication;
|
|
39
|
+
* pass `GatewayAuthorizer.usingCognito(...)` or `usingCustomJwt(...)` to
|
|
40
|
+
* authenticate callers with JWT bearer tokens instead.
|
|
41
|
+
*
|
|
42
|
+
* @default GatewayAuthorizer.usingAwsIam()
|
|
43
|
+
*/
|
|
44
|
+
readonly authorizer?: agentcore.IGatewayAuthorizerConfig;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
/**
|
|
40
|
-
* AgentCore Gateway wired for MCP protocol
|
|
41
|
-
*
|
|
48
|
+
* AgentCore Gateway wired for MCP protocol, exposing attached MCP servers'
|
|
49
|
+
* tools under `<targetName>___<toolName>` names. Inbound auth defaults to IAM
|
|
50
|
+
* (SigV4); pass an `authorizer` to use Cognito/JWT instead.
|
|
42
51
|
*/
|
|
43
52
|
export class AgentCoreGateway extends Construct implements iam.IGrantable {
|
|
44
53
|
public readonly gateway: agentcore.Gateway;
|
|
@@ -60,7 +69,8 @@ export class AgentCoreGateway extends Construct implements iam.IGrantable {
|
|
|
60
69
|
searchType: agentcore.McpGatewaySearchType.SEMANTIC,
|
|
61
70
|
supportedVersions: [agentcore.MCPProtocolVersion.MCP_2025_03_26],
|
|
62
71
|
}),
|
|
63
|
-
authorizerConfiguration:
|
|
72
|
+
authorizerConfiguration:
|
|
73
|
+
props?.authorizer ?? agentcore.GatewayAuthorizer.usingAwsIam(),
|
|
64
74
|
});
|
|
65
75
|
|
|
66
76
|
if (props?.cedarPolicyPath) {
|
|
@@ -40,6 +40,18 @@ variable "enable_waf" {
|
|
|
40
40
|
type = bool
|
|
41
41
|
default = true
|
|
42
42
|
}
|
|
43
|
+
<%_ if (auth === 'cognito') { _%>
|
|
44
|
+
|
|
45
|
+
variable "user_pool_id" {
|
|
46
|
+
description = "ID of the Cognito user pool used to authenticate inbound requests to the gateway."
|
|
47
|
+
type = string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
variable "user_pool_client_ids" {
|
|
51
|
+
description = "IDs of the Cognito user pool clients allowed to authenticate inbound requests to the gateway."
|
|
52
|
+
type = list(string)
|
|
53
|
+
}
|
|
54
|
+
<%_ } _%>
|
|
43
55
|
<%_ if (cedarPolicy) { _%>
|
|
44
56
|
|
|
45
57
|
variable "policy_dependencies" {
|
|
@@ -161,7 +173,16 @@ resource "aws_bedrockagentcore_gateway" "this" {
|
|
|
161
173
|
}
|
|
162
174
|
}
|
|
163
175
|
|
|
164
|
-
authorizer_type = "AWS_IAM"
|
|
176
|
+
authorizer_type = "<%- auth === 'cognito' ? 'CUSTOM_JWT' : 'AWS_IAM' %>"
|
|
177
|
+
<%_ if (auth === 'cognito') { _%>
|
|
178
|
+
|
|
179
|
+
authorizer_configuration {
|
|
180
|
+
custom_jwt_authorizer {
|
|
181
|
+
discovery_url = "https://cognito-idp.${local.aws_region}.amazonaws.com/${var.user_pool_id}/.well-known/openid-configuration"
|
|
182
|
+
allowed_clients = var.user_pool_client_ids
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
<%_ } _%>
|
|
165
186
|
<%_ if (cedarPolicy) { _%>
|
|
166
187
|
|
|
167
188
|
policy_engine_configuration {
|
package/src/utils/commands.js
CHANGED
|
@@ -52,7 +52,7 @@ export const PACKAGE_MANAGER_COMMANDS = {
|
|
|
52
52
|
case 'npm':
|
|
53
53
|
return `npm install --legacy-peer-deps ${dev ? '-D ' : ''}${pkg}`;
|
|
54
54
|
case 'bun':
|
|
55
|
-
return `bun
|
|
55
|
+
return `bun add ${dev ? '-D ' : ''}${pkg}`;
|
|
56
56
|
default:
|
|
57
57
|
return `${pm} install ${dev ? '-D ' : ''}${pkg}`;
|
|
58
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/commands.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { TS_VERSIONS } from './versions';\n\n/**\n * The nx version the plugin is built against. Commands that download nx\n * (e.g. `nx init`) pin to this version so the workspace's nx matches the\n * plugin's `@nx/*` packages — a mismatch deadlocks `nx sync`.\n */\nexport const NX_VERSION = TS_VERSIONS['@nx/devkit'];\n\n/**\n * Display-friendly command prefixes for each package manager.\n *\n * - `exec`: prefix for running local binaries (e.g. `npx nx`, `pnpm nx`, `bunx nx`)\n * - `run`: prefix for running package.json scripts (e.g. `npm run build`, `pnpm build`)\n *\n * @see docs/src/components/package-manager-exec-command.astro\n * @see docs/src/components/package-manager-short-command.astro\n */\nexport interface PackageManagerDisplayCommands {\n /** Prefix for running local binaries: npx, pnpm, yarn, bunx */\n exec: string;\n /** Prefix for running package.json scripts: `npm run`, pnpm, yarn, bun */\n run: string;\n /** Prefix for `create` commands: `npm create`, `pnpm create`, etc. */\n create: string;\n}\n\nexport const PACKAGE_MANAGER_COMMANDS: Record<\n string,\n PackageManagerDisplayCommands\n> = {\n npm: { exec: 'npx', run: 'npm run', create: 'npm create' },\n pnpm: { exec: 'pnpm', run: 'pnpm', create: 'pnpm create' },\n yarn: { exec: 'yarn', run: 'yarn', create: 'yarn create' },\n bun: { exec: 'bunx', run: 'bun', create: 'bun create' },\n};\n\n/**\n * Build a package manager exec command (e.g. pnpm cmd, npx cmd, bunx cmd)\n */\nexport const buildPackageManagerExecCommand = (pm: string, command: string) => {\n const prefix = PACKAGE_MANAGER_COMMANDS[pm]?.exec ?? pm;\n return `${prefix} ${command}`;\n};\n\n/**\n * Build a short package manager command (e.g. pnpm build, npm run build)\n */\nexport const buildPackageManagerShortCommand = (\n pm: string,\n command: string,\n) => {\n const prefix = PACKAGE_MANAGER_COMMANDS[pm]?.run ?? pm;\n return `${prefix} ${command}`;\n};\n\n/**\n * Build an install command for a given package manager\n */\nexport const buildInstallCommand = (pm: string, pkg: string, dev: boolean) => {\n switch (pm) {\n case 'pnpm':\n return `pnpm add ${dev ? '-D' : '-'}w ${pkg}`;\n case 'yarn':\n return `yarn add ${dev ? '-D ' : ''}${pkg}`;\n case 'npm':\n return `npm install --legacy-peer-deps ${dev ? '-D ' : ''}${pkg}`;\n case 'bun':\n return `bun
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/commands.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { TS_VERSIONS } from './versions';\n\n/**\n * The nx version the plugin is built against. Commands that download nx\n * (e.g. `nx init`) pin to this version so the workspace's nx matches the\n * plugin's `@nx/*` packages — a mismatch deadlocks `nx sync`.\n */\nexport const NX_VERSION = TS_VERSIONS['@nx/devkit'];\n\n/**\n * Display-friendly command prefixes for each package manager.\n *\n * - `exec`: prefix for running local binaries (e.g. `npx nx`, `pnpm nx`, `bunx nx`)\n * - `run`: prefix for running package.json scripts (e.g. `npm run build`, `pnpm build`)\n *\n * @see docs/src/components/package-manager-exec-command.astro\n * @see docs/src/components/package-manager-short-command.astro\n */\nexport interface PackageManagerDisplayCommands {\n /** Prefix for running local binaries: npx, pnpm, yarn, bunx */\n exec: string;\n /** Prefix for running package.json scripts: `npm run`, pnpm, yarn, bun */\n run: string;\n /** Prefix for `create` commands: `npm create`, `pnpm create`, etc. */\n create: string;\n}\n\nexport const PACKAGE_MANAGER_COMMANDS: Record<\n string,\n PackageManagerDisplayCommands\n> = {\n npm: { exec: 'npx', run: 'npm run', create: 'npm create' },\n pnpm: { exec: 'pnpm', run: 'pnpm', create: 'pnpm create' },\n yarn: { exec: 'yarn', run: 'yarn', create: 'yarn create' },\n bun: { exec: 'bunx', run: 'bun', create: 'bun create' },\n};\n\n/**\n * Build a package manager exec command (e.g. pnpm cmd, npx cmd, bunx cmd)\n */\nexport const buildPackageManagerExecCommand = (pm: string, command: string) => {\n const prefix = PACKAGE_MANAGER_COMMANDS[pm]?.exec ?? pm;\n return `${prefix} ${command}`;\n};\n\n/**\n * Build a short package manager command (e.g. pnpm build, npm run build)\n */\nexport const buildPackageManagerShortCommand = (\n pm: string,\n command: string,\n) => {\n const prefix = PACKAGE_MANAGER_COMMANDS[pm]?.run ?? pm;\n return `${prefix} ${command}`;\n};\n\n/**\n * Build an install command for a given package manager\n */\nexport const buildInstallCommand = (pm: string, pkg: string, dev: boolean) => {\n switch (pm) {\n case 'pnpm':\n return `pnpm add ${dev ? '-D' : '-'}w ${pkg}`;\n case 'yarn':\n return `yarn add ${dev ? '-D ' : ''}${pkg}`;\n case 'npm':\n return `npm install --legacy-peer-deps ${dev ? '-D ' : ''}${pkg}`;\n case 'bun':\n return `bun add ${dev ? '-D ' : ''}${pkg}`;\n default:\n return `${pm} install ${dev ? '-D ' : ''}${pkg}`;\n }\n};\n\n/**\n * Build the command that adds Nx to a non-Nx project, pinned to the nx\n * version the plugin is built against.\n */\nexport const buildNxInitCommand = (pm: string) => {\n const dlxPrefix =\n { npm: 'npx', pnpm: 'pnpm dlx', yarn: 'yarn dlx', bun: 'bunx' }[pm] ??\n 'npx';\n return `${dlxPrefix} nx@${NX_VERSION} init`;\n};\n\n/**\n * Build a command to create a new workspace using @aws/create-nx-workspace.\n *\n * Uses the `create` shorthand for each package manager:\n * npm create @aws/nx-workspace my-project\n * pnpm create @aws/nx-workspace my-project\n * yarn create @aws/nx-workspace my-project\n * bun create @aws/nx-workspace my-project\n *\n * The package manager is auto-detected by @aws/create-nx-workspace from the\n * invoking command, so --pm is not needed.\n */\nexport const buildCreateNxWorkspaceCommand = (\n pm: string,\n workspace: string,\n iac?: 'cdk' | 'terraform',\n tag?: string,\n module?: 'esm' | 'cjs',\n) => {\n const createPrefix = PACKAGE_MANAGER_COMMANDS[pm]?.create ?? `${pm} create`;\n const pkgName = tag ? `@aws/nx-workspace@${tag}` : '@aws/nx-workspace';\n const parts = [\n createPrefix,\n pkgName,\n // npm requires '--' to stop interpreting subsequent flags as npm config\n ...(pm === 'npm' ? ['--'] : []),\n workspace,\n ...(iac ? [`--iac=${iac}`] : []),\n ...(module ? [`--module=${module}`] : []),\n ];\n return parts.join(' ');\n};\n"],"names":["TS_VERSIONS","NX_VERSION","PACKAGE_MANAGER_COMMANDS","npm","exec","run","create","pnpm","yarn","bun","buildPackageManagerExecCommand","pm","command","prefix","buildPackageManagerShortCommand","buildInstallCommand","pkg","dev","buildNxInitCommand","dlxPrefix","buildCreateNxWorkspaceCommand","workspace","iac","tag","module","createPrefix","pkgName","parts","join"],"mappings":"AAAA;;;CAGC,GACD,SAASA,WAAW,QAAQ,gBAAa;AAEzC;;;;CAIC,GACD,OAAO,MAAMC,aAAaD,WAAW,CAAC,aAAa,CAAC;AAoBpD,OAAO,MAAME,2BAGT;IACFC,KAAK;QAAEC,MAAM;QAAOC,KAAK;QAAWC,QAAQ;IAAa;IACzDC,MAAM;QAAEH,MAAM;QAAQC,KAAK;QAAQC,QAAQ;IAAc;IACzDE,MAAM;QAAEJ,MAAM;QAAQC,KAAK;QAAQC,QAAQ;IAAc;IACzDG,KAAK;QAAEL,MAAM;QAAQC,KAAK;QAAOC,QAAQ;IAAa;AACxD,EAAE;AAEF;;CAEC,GACD,OAAO,MAAMI,iCAAiC,CAACC,IAAYC;IACzD,MAAMC,SAASX,wBAAwB,CAACS,GAAG,EAAEP,QAAQO;IACrD,OAAO,GAAGE,OAAO,CAAC,EAAED,SAAS;AAC/B,EAAE;AAEF;;CAEC,GACD,OAAO,MAAME,kCAAkC,CAC7CH,IACAC;IAEA,MAAMC,SAASX,wBAAwB,CAACS,GAAG,EAAEN,OAAOM;IACpD,OAAO,GAAGE,OAAO,CAAC,EAAED,SAAS;AAC/B,EAAE;AAEF;;CAEC,GACD,OAAO,MAAMG,sBAAsB,CAACJ,IAAYK,KAAaC;IAC3D,OAAQN;QACN,KAAK;YACH,OAAO,CAAC,SAAS,EAAEM,MAAM,OAAO,IAAI,EAAE,EAAED,KAAK;QAC/C,KAAK;YACH,OAAO,CAAC,SAAS,EAAEC,MAAM,QAAQ,KAAKD,KAAK;QAC7C,KAAK;YACH,OAAO,CAAC,+BAA+B,EAAEC,MAAM,QAAQ,KAAKD,KAAK;QACnE,KAAK;YACH,OAAO,CAAC,QAAQ,EAAEC,MAAM,QAAQ,KAAKD,KAAK;QAC5C;YACE,OAAO,GAAGL,GAAG,SAAS,EAAEM,MAAM,QAAQ,KAAKD,KAAK;IACpD;AACF,EAAE;AAEF;;;CAGC,GACD,OAAO,MAAME,qBAAqB,CAACP;IACjC,MAAMQ,YACJ;QAAEhB,KAAK;QAAOI,MAAM;QAAYC,MAAM;QAAYC,KAAK;IAAO,CAAC,CAACE,GAAG,IACnE;IACF,OAAO,GAAGQ,UAAU,IAAI,EAAElB,WAAW,KAAK,CAAC;AAC7C,EAAE;AAEF;;;;;;;;;;;CAWC,GACD,OAAO,MAAMmB,gCAAgC,CAC3CT,IACAU,WACAC,KACAC,KACAC;IAEA,MAAMC,eAAevB,wBAAwB,CAACS,GAAG,EAAEL,UAAU,GAAGK,GAAG,OAAO,CAAC;IAC3E,MAAMe,UAAUH,MAAM,CAAC,kBAAkB,EAAEA,KAAK,GAAG;IACnD,MAAMI,QAAQ;QACZF;QACAC;QACA,wEAAwE;WACpEf,OAAO,QAAQ;YAAC;SAAK,GAAG,EAAE;QAC9BU;WACIC,MAAM;YAAC,CAAC,MAAM,EAAEA,KAAK;SAAC,GAAG,EAAE;WAC3BE,SAAS;YAAC,CAAC,SAAS,EAAEA,QAAQ;SAAC,GAAG,EAAE;KACzC;IACD,OAAOG,MAAMC,IAAI,CAAC;AACpB,EAAE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type Tree } from '@nx/devkit';
|
|
6
|
+
import type { Iac } from '../iac';
|
|
7
|
+
/**
|
|
8
|
+
* The DCR proxy Lambda handlers. Each is bundled independently and wired to a
|
|
9
|
+
* dedicated route on the HTTP API.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DCR_PROXY_HANDLERS: readonly ["authorization-server-metadata", "authorize", "mcp-proxy", "protected-resource-metadata", "register", "token"];
|
|
12
|
+
export type DcrProxyHandler = (typeof DCR_PROXY_HANDLERS)[number];
|
|
13
|
+
export interface AddDcrProxyInfraProps {
|
|
14
|
+
dcrProxyNameClassName: string;
|
|
15
|
+
dcrProxyNameKebabCase: string;
|
|
16
|
+
/**
|
|
17
|
+
* Fully qualified name of the TypeScript project that owns the bundled
|
|
18
|
+
* handlers, used to wire the shared package build to depend on it.
|
|
19
|
+
*/
|
|
20
|
+
proxyProjectName: string;
|
|
21
|
+
/**
|
|
22
|
+
* Map of handler name to the workspace-root-relative path of its bundle
|
|
23
|
+
* output directory (e.g. `dist/packages/dcr-proxy/bundle/token`).
|
|
24
|
+
*/
|
|
25
|
+
bundlePathsFromRoot: Record<DcrProxyHandler, string>;
|
|
26
|
+
iac: Iac;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Add a DCR proxy infrastructure (CDK construct or Terraform module) to the
|
|
30
|
+
* relevant shared package.
|
|
31
|
+
*
|
|
32
|
+
* The infrastructure exposes props/variables for the caller to inject their
|
|
33
|
+
* Cognito identifiers and the upstream MCP server URL from their own stack,
|
|
34
|
+
* since these may or may not be generated by this plugin. The Lambda handlers
|
|
35
|
+
* live in a standalone TypeScript project and are bundled by rolldown; both
|
|
36
|
+
* IaC providers reference the same bundle output.
|
|
37
|
+
*/
|
|
38
|
+
export declare const addDcrProxyInfra: (tree: Tree, options: AddDcrProxyInfraProps) => Promise<void>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { generateFiles, joinPathFragments, OverwriteStrategy, updateJson } from "@nx/devkit";
|
|
5
|
+
import { addStarExport } from "../ast.js";
|
|
6
|
+
import { esmVars } from "../module-format.js";
|
|
7
|
+
import { addDependencyToTargetIfNotPresent } from "../nx.js";
|
|
8
|
+
import { PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, SHARED_TERRAFORM_DIR } from "../shared-constructs-constants.js";
|
|
9
|
+
import { terraformProviderVersions } from "../versions.js";
|
|
10
|
+
/**
|
|
11
|
+
* The DCR proxy Lambda handlers. Each is bundled independently and wired to a
|
|
12
|
+
* dedicated route on the HTTP API.
|
|
13
|
+
*/ export const DCR_PROXY_HANDLERS = [
|
|
14
|
+
'authorization-server-metadata',
|
|
15
|
+
'authorize',
|
|
16
|
+
'mcp-proxy',
|
|
17
|
+
'protected-resource-metadata',
|
|
18
|
+
'register',
|
|
19
|
+
'token'
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Add a DCR proxy infrastructure (CDK construct or Terraform module) to the
|
|
23
|
+
* relevant shared package.
|
|
24
|
+
*
|
|
25
|
+
* The infrastructure exposes props/variables for the caller to inject their
|
|
26
|
+
* Cognito identifiers and the upstream MCP server URL from their own stack,
|
|
27
|
+
* since these may or may not be generated by this plugin. The Lambda handlers
|
|
28
|
+
* live in a standalone TypeScript project and are bundled by rolldown; both
|
|
29
|
+
* IaC providers reference the same bundle output.
|
|
30
|
+
*/ export const addDcrProxyInfra = async (tree, options)=>{
|
|
31
|
+
if (options.iac === 'cdk') {
|
|
32
|
+
await addDcrProxyCdkConstructs(tree, options);
|
|
33
|
+
} else if (options.iac === 'terraform') {
|
|
34
|
+
addDcrProxyTerraformModules(tree, options);
|
|
35
|
+
} else {
|
|
36
|
+
throw new Error(`Unsupported iac ${options.iac}`);
|
|
37
|
+
}
|
|
38
|
+
// Ensure the handler bundles are built before the shared package
|
|
39
|
+
updateJson(tree, joinPathFragments(PACKAGES_DIR, options.iac === 'cdk' ? SHARED_CONSTRUCTS_DIR : SHARED_TERRAFORM_DIR, 'project.json'), (config)=>{
|
|
40
|
+
addDependencyToTargetIfNotPresent(config, 'build', `${options.proxyProjectName}:build`);
|
|
41
|
+
return config;
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Add the DCR proxy CDK construct to the shared constructs package.
|
|
46
|
+
*/ const addDcrProxyCdkConstructs = async (tree, options)=>{
|
|
47
|
+
// Ensure the core HTTP API construct + utils are available (the proxy reuses
|
|
48
|
+
// them). These are shared with the api generators and safe to (re)generate
|
|
49
|
+
// with KeepExisting.
|
|
50
|
+
for (const coreApiFile of [
|
|
51
|
+
'http',
|
|
52
|
+
'utils'
|
|
53
|
+
]){
|
|
54
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, '..', 'api-constructs', 'files', 'cdk', 'core', 'api', coreApiFile), joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'core', 'api'), {
|
|
55
|
+
...esmVars(tree)
|
|
56
|
+
}, {
|
|
57
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'cdk', 'app', 'dcr-proxies'), joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'app', 'dcr-proxies'), {
|
|
61
|
+
nameClassName: options.dcrProxyNameClassName,
|
|
62
|
+
nameKebabCase: options.dcrProxyNameKebabCase,
|
|
63
|
+
bundlePathsFromRoot: options.bundlePathsFromRoot,
|
|
64
|
+
...esmVars(tree)
|
|
65
|
+
}, {
|
|
66
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
67
|
+
});
|
|
68
|
+
// Export the construct
|
|
69
|
+
await addStarExport(tree, joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'app', 'dcr-proxies', 'index.ts'), `./${options.dcrProxyNameKebabCase}/${options.dcrProxyNameKebabCase}.js`);
|
|
70
|
+
await addStarExport(tree, joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, 'src', 'app', 'index.ts'), './dcr-proxies/index.js');
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Add the DCR proxy Terraform module to the shared terraform package.
|
|
74
|
+
*/ const addDcrProxyTerraformModules = (tree, options)=>{
|
|
75
|
+
// Ensure the core HTTP API module is available (the proxy reuses it). This is
|
|
76
|
+
// shared with the api generators and safe to (re)generate with KeepExisting.
|
|
77
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, '..', 'api-constructs', 'files', 'terraform', 'core', 'api', 'http'), joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core', 'api'), {
|
|
78
|
+
...terraformProviderVersions()
|
|
79
|
+
}, {
|
|
80
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
81
|
+
});
|
|
82
|
+
// Generate the app specific terraform module
|
|
83
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'terraform', 'app', 'dcr-proxies'), joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'app', 'dcr-proxies'), {
|
|
84
|
+
nameClassName: options.dcrProxyNameClassName,
|
|
85
|
+
nameKebabCase: options.dcrProxyNameKebabCase,
|
|
86
|
+
bundlePathsFromRoot: options.bundlePathsFromRoot,
|
|
87
|
+
...terraformProviderVersions()
|
|
88
|
+
}, {
|
|
89
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=dcr-proxy-constructs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n generateFiles,\n joinPathFragments,\n OverwriteStrategy,\n type ProjectConfiguration,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport { addStarExport } from '../ast';\nimport type { Iac } from '../iac';\nimport { esmVars } from '../module-format';\nimport { addDependencyToTargetIfNotPresent } from '../nx';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../shared-constructs-constants';\nimport { terraformProviderVersions } from '../versions';\n\n/**\n * The DCR proxy Lambda handlers. Each is bundled independently and wired to a\n * dedicated route on the HTTP API.\n */\nexport const DCR_PROXY_HANDLERS = [\n 'authorization-server-metadata',\n 'authorize',\n 'mcp-proxy',\n 'protected-resource-metadata',\n 'register',\n 'token',\n] as const;\n\nexport type DcrProxyHandler = (typeof DCR_PROXY_HANDLERS)[number];\n\nexport interface AddDcrProxyInfraProps {\n dcrProxyNameClassName: string;\n dcrProxyNameKebabCase: string;\n\n /**\n * Fully qualified name of the TypeScript project that owns the bundled\n * handlers, used to wire the shared package build to depend on it.\n */\n proxyProjectName: string;\n\n /**\n * Map of handler name to the workspace-root-relative path of its bundle\n * output directory (e.g. `dist/packages/dcr-proxy/bundle/token`).\n */\n bundlePathsFromRoot: Record<DcrProxyHandler, string>;\n\n iac: Iac;\n}\n\n/**\n * Add a DCR proxy infrastructure (CDK construct or Terraform module) to the\n * relevant shared package.\n *\n * The infrastructure exposes props/variables for the caller to inject their\n * Cognito identifiers and the upstream MCP server URL from their own stack,\n * since these may or may not be generated by this plugin. The Lambda handlers\n * live in a standalone TypeScript project and are bundled by rolldown; both\n * IaC providers reference the same bundle output.\n */\nexport const addDcrProxyInfra = async (\n tree: Tree,\n options: AddDcrProxyInfraProps,\n) => {\n if (options.iac === 'cdk') {\n await addDcrProxyCdkConstructs(tree, options);\n } else if (options.iac === 'terraform') {\n addDcrProxyTerraformModules(tree, options);\n } else {\n throw new Error(`Unsupported iac ${options.iac}`);\n }\n\n // Ensure the handler bundles are built before the shared package\n updateJson(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n options.iac === 'cdk' ? SHARED_CONSTRUCTS_DIR : SHARED_TERRAFORM_DIR,\n 'project.json',\n ),\n (config: ProjectConfiguration) => {\n addDependencyToTargetIfNotPresent(\n config,\n 'build',\n `${options.proxyProjectName}:build`,\n );\n return config;\n },\n );\n};\n\n/**\n * Add the DCR proxy CDK construct to the shared constructs package.\n */\nconst addDcrProxyCdkConstructs = async (\n tree: Tree,\n options: AddDcrProxyInfraProps,\n) => {\n // Ensure the core HTTP API construct + utils are available (the proxy reuses\n // them). These are shared with the api generators and safe to (re)generate\n // with KeepExisting.\n for (const coreApiFile of ['http', 'utils']) {\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n '..',\n 'api-constructs',\n 'files',\n 'cdk',\n 'core',\n 'api',\n coreApiFile,\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'core',\n 'api',\n ),\n { ...esmVars(tree) },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n }\n\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'cdk',\n 'app',\n 'dcr-proxies',\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'dcr-proxies',\n ),\n {\n nameClassName: options.dcrProxyNameClassName,\n nameKebabCase: options.dcrProxyNameKebabCase,\n bundlePathsFromRoot: options.bundlePathsFromRoot,\n ...esmVars(tree),\n },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // Export the construct\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'dcr-proxies',\n 'index.ts',\n ),\n `./${options.dcrProxyNameKebabCase}/${options.dcrProxyNameKebabCase}.js`,\n );\n await addStarExport(\n tree,\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n 'src',\n 'app',\n 'index.ts',\n ),\n './dcr-proxies/index.js',\n );\n};\n\n/**\n * Add the DCR proxy Terraform module to the shared terraform package.\n */\nconst addDcrProxyTerraformModules = (\n tree: Tree,\n options: AddDcrProxyInfraProps,\n) => {\n // Ensure the core HTTP API module is available (the proxy reuses it). This is\n // shared with the api generators and safe to (re)generate with KeepExisting.\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n '..',\n 'api-constructs',\n 'files',\n 'terraform',\n 'core',\n 'api',\n 'http',\n ),\n joinPathFragments(PACKAGES_DIR, SHARED_TERRAFORM_DIR, 'src', 'core', 'api'),\n { ...terraformProviderVersions() },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n\n // Generate the app specific terraform module\n generateFiles(\n tree,\n joinPathFragments(\n import.meta.dirname,\n 'files',\n 'terraform',\n 'app',\n 'dcr-proxies',\n ),\n joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'app',\n 'dcr-proxies',\n ),\n {\n nameClassName: options.dcrProxyNameClassName,\n nameKebabCase: options.dcrProxyNameKebabCase,\n bundlePathsFromRoot: options.bundlePathsFromRoot,\n ...terraformProviderVersions(),\n },\n {\n overwriteStrategy: OverwriteStrategy.KeepExisting,\n },\n );\n};\n"],"names":["generateFiles","joinPathFragments","OverwriteStrategy","updateJson","addStarExport","esmVars","addDependencyToTargetIfNotPresent","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","terraformProviderVersions","DCR_PROXY_HANDLERS","addDcrProxyInfra","tree","options","iac","addDcrProxyCdkConstructs","addDcrProxyTerraformModules","Error","config","proxyProjectName","coreApiFile","dirname","overwriteStrategy","KeepExisting","nameClassName","dcrProxyNameClassName","nameKebabCase","dcrProxyNameKebabCase","bundlePathsFromRoot"],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import * as url from 'node:url';
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import { Duration, Stack } from 'aws-cdk-lib';
|
|
4
|
+
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
|
|
5
|
+
import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
|
6
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
7
|
+
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
8
|
+
import { HttpApi } from '../../../core/api/http-api<% if (esm) { %>.js<% } %>';
|
|
9
|
+
import {
|
|
10
|
+
HttpApiIntegration,
|
|
11
|
+
OperationDetails,
|
|
12
|
+
} from '../../../core/api/utils<% if (esm) { %>.js<% } %>';
|
|
13
|
+
|
|
14
|
+
export interface <%- nameClassName %>Props {
|
|
15
|
+
/**
|
|
16
|
+
* Cognito User Pool id (e.g. `us-east-1_abc123`).
|
|
17
|
+
*/
|
|
18
|
+
readonly userPoolId: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cognito User Pool App Client id.
|
|
22
|
+
*/
|
|
23
|
+
readonly userPoolClientId: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ARN of the Secrets Manager secret holding the Cognito App Client secret.
|
|
27
|
+
* The token handler reads this at runtime to complete the token exchange;
|
|
28
|
+
* the secret is never exposed to the client. Create the secret in your own
|
|
29
|
+
* stack (from your App Client's generated secret) and pass its ARN here.
|
|
30
|
+
*/
|
|
31
|
+
readonly cognitoClientSecretArn: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Base URL of the Cognito Hosted UI domain, without a trailing slash
|
|
35
|
+
* (e.g. `https://my-domain.auth.us-east-1.amazoncognito.com`). The proxy
|
|
36
|
+
* forwards `/authorize` and `/oauth/token` to this host.
|
|
37
|
+
*/
|
|
38
|
+
readonly cognitoHostedUiBase: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Full URL of the upstream MCP server (e.g. AgentCore Runtime/Gateway
|
|
42
|
+
* endpoint). The proxy forwards requests on its `/mcp` path to this URL and
|
|
43
|
+
* rewrites the upstream `WWW-Authenticate` header so MCP clients discover
|
|
44
|
+
* this proxy as the protected-resource metadata host.
|
|
45
|
+
*/
|
|
46
|
+
readonly upstreamUrl: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The proxy operations wired to the shared HttpApi construct. The
|
|
50
|
+
// openid-configuration route reuses the authorization-server-metadata handler.
|
|
51
|
+
type <%- nameClassName %>Operation =
|
|
52
|
+
| 'protectedResourceMetadata'
|
|
53
|
+
| 'authServerMetadata'
|
|
54
|
+
| 'openIdConfiguration'
|
|
55
|
+
| 'register'
|
|
56
|
+
| 'authorize'
|
|
57
|
+
| 'token'
|
|
58
|
+
| 'mcp';
|
|
59
|
+
|
|
60
|
+
const OPERATIONS: Record<<%- nameClassName %>Operation, OperationDetails> = {
|
|
61
|
+
protectedResourceMetadata: {
|
|
62
|
+
path: '/.well-known/oauth-protected-resource',
|
|
63
|
+
method: 'GET',
|
|
64
|
+
},
|
|
65
|
+
authServerMetadata: {
|
|
66
|
+
path: '/.well-known/oauth-authorization-server',
|
|
67
|
+
method: 'GET',
|
|
68
|
+
},
|
|
69
|
+
openIdConfiguration: {
|
|
70
|
+
path: '/.well-known/openid-configuration',
|
|
71
|
+
method: 'GET',
|
|
72
|
+
},
|
|
73
|
+
register: { path: '/register', method: 'POST' },
|
|
74
|
+
authorize: { path: '/authorize', method: 'GET' },
|
|
75
|
+
token: { path: '/oauth/token', method: 'POST' },
|
|
76
|
+
mcp: { path: '/mcp', method: 'ANY' },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Resolve a handler's rolldown bundle output directory, relative to this
|
|
80
|
+
// compiled construct file, back to the workspace root.
|
|
81
|
+
const asset = (bundlePathFromRoot: string): lambda.Code =>
|
|
82
|
+
lambda.Code.fromAsset(
|
|
83
|
+
url.fileURLToPath(
|
|
84
|
+
new URL(
|
|
85
|
+
`../../../../../../../${bundlePathFromRoot}`,
|
|
86
|
+
<% if (esm) { %>import.meta.url<% } else { %>require('url').pathToFileURL(__filename).href<% } %>,
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* OAuth Dynamic Client Registration (DCR) proxy for Amazon Cognito.
|
|
93
|
+
*
|
|
94
|
+
* Lets MCP clients (Claude Code, Kiro CLI, MCP Inspector, ...) authenticate
|
|
95
|
+
* against an Amazon Cognito User Pool while keeping the Hosted UI flow intact,
|
|
96
|
+
* supporting RFC 7591 Dynamic Client Registration, and never exposing the
|
|
97
|
+
* Cognito App Client secret to the client.
|
|
98
|
+
*/
|
|
99
|
+
export class <%- nameClassName %> extends Construct {
|
|
100
|
+
readonly httpApi: HttpApi<
|
|
101
|
+
<%- nameClassName %>Operation,
|
|
102
|
+
Record<<%- nameClassName %>Operation, HttpApiIntegration>
|
|
103
|
+
>;
|
|
104
|
+
readonly proxyUrl: string;
|
|
105
|
+
readonly mcpUrl: string;
|
|
106
|
+
readonly metadataUrl: string;
|
|
107
|
+
readonly authServerMetadataUrl: string;
|
|
108
|
+
readonly tokenEndpoint: string;
|
|
109
|
+
readonly registrationEndpoint: string;
|
|
110
|
+
|
|
111
|
+
constructor(scope: Construct, id: string, props: <%- nameClassName %>Props) {
|
|
112
|
+
super(scope, id);
|
|
113
|
+
|
|
114
|
+
const region = Stack.of(this).region;
|
|
115
|
+
|
|
116
|
+
const clientSecret = secretsmanager.Secret.fromSecretCompleteArn(
|
|
117
|
+
this,
|
|
118
|
+
'CognitoClientSecret',
|
|
119
|
+
props.cognitoClientSecretArn,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const cognitoIssuer = `https://cognito-idp.${region}.amazonaws.com/${props.userPoolId}`;
|
|
123
|
+
|
|
124
|
+
const baseEnv: Record<string, string> = {
|
|
125
|
+
COGNITO_CLIENT_ID: props.userPoolClientId,
|
|
126
|
+
COGNITO_CLIENT_SECRET_ARN: clientSecret.secretArn,
|
|
127
|
+
COGNITO_ISSUER: cognitoIssuer,
|
|
128
|
+
COGNITO_HOSTED_UI_BASE: props.cognitoHostedUiBase,
|
|
129
|
+
COGNITO_REGION: region,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Lambda functions use their auto-created log group; access logging for the
|
|
133
|
+
// API is handled by the shared HttpApi construct's KMS-encrypted log group.
|
|
134
|
+
const makeFn = (
|
|
135
|
+
id_: string,
|
|
136
|
+
bundlePathFromRoot: string,
|
|
137
|
+
overrides?: Partial<lambda.FunctionProps>,
|
|
138
|
+
): lambda.Function =>
|
|
139
|
+
new lambda.Function(this, id_, {
|
|
140
|
+
code: asset(bundlePathFromRoot),
|
|
141
|
+
handler: 'index.handler',
|
|
142
|
+
runtime: lambda.Runtime.NODEJS_22_X,
|
|
143
|
+
architecture: lambda.Architecture.ARM_64,
|
|
144
|
+
timeout: Duration.seconds(10),
|
|
145
|
+
memorySize: 256,
|
|
146
|
+
environment: baseEnv,
|
|
147
|
+
tracing: lambda.Tracing.ACTIVE,
|
|
148
|
+
...overrides,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const protectedResourceFn = makeFn(
|
|
152
|
+
'ProtectedResourceFn',
|
|
153
|
+
'<%- bundlePathsFromRoot['protected-resource-metadata'] %>',
|
|
154
|
+
);
|
|
155
|
+
const authServerFn = makeFn(
|
|
156
|
+
'AuthServerFn',
|
|
157
|
+
'<%- bundlePathsFromRoot['authorization-server-metadata'] %>',
|
|
158
|
+
);
|
|
159
|
+
const registerFn = makeFn(
|
|
160
|
+
'RegisterFn',
|
|
161
|
+
'<%- bundlePathsFromRoot['register'] %>',
|
|
162
|
+
);
|
|
163
|
+
const authorizeFn = makeFn(
|
|
164
|
+
'AuthorizeFn',
|
|
165
|
+
'<%- bundlePathsFromRoot['authorize'] %>',
|
|
166
|
+
);
|
|
167
|
+
const tokenFn = makeFn('TokenFn', '<%- bundlePathsFromRoot['token'] %>');
|
|
168
|
+
|
|
169
|
+
clientSecret.grantRead(tokenFn);
|
|
170
|
+
|
|
171
|
+
// The MCP proxy Lambda buffers upstream responses, so it is given more time
|
|
172
|
+
// (up to the API Gateway HTTP API integration limit) and memory.
|
|
173
|
+
const mcpProxyFn = makeFn(
|
|
174
|
+
'McpProxyFn',
|
|
175
|
+
'<%- bundlePathsFromRoot['mcp-proxy'] %>',
|
|
176
|
+
{
|
|
177
|
+
timeout: Duration.seconds(29),
|
|
178
|
+
memorySize: 512,
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const int = (id_: string, fn: lambda.Function): HttpApiIntegration => ({
|
|
183
|
+
integration: new integrations.HttpLambdaIntegration(id_, fn),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.httpApi = new HttpApi(this, 'HttpApi', {
|
|
187
|
+
apiName: `${id}-mcp-auth-proxy`,
|
|
188
|
+
description: 'MCP-compatible OAuth proxy for Amazon Cognito',
|
|
189
|
+
corsPreflight: {
|
|
190
|
+
allowOrigins: ['*'],
|
|
191
|
+
allowMethods: [
|
|
192
|
+
apigwv2.CorsHttpMethod.GET,
|
|
193
|
+
apigwv2.CorsHttpMethod.POST,
|
|
194
|
+
apigwv2.CorsHttpMethod.DELETE,
|
|
195
|
+
apigwv2.CorsHttpMethod.OPTIONS,
|
|
196
|
+
],
|
|
197
|
+
allowHeaders: ['authorization', 'content-type', 'mcp-protocol-version'],
|
|
198
|
+
maxAge: Duration.minutes(10),
|
|
199
|
+
},
|
|
200
|
+
operations: OPERATIONS,
|
|
201
|
+
integrations: {
|
|
202
|
+
protectedResourceMetadata: int(
|
|
203
|
+
'ProtectedResourceInt',
|
|
204
|
+
protectedResourceFn,
|
|
205
|
+
),
|
|
206
|
+
authServerMetadata: int('AuthServerInt', authServerFn),
|
|
207
|
+
openIdConfiguration: int('OpenIdConfigInt', authServerFn),
|
|
208
|
+
register: int('RegisterInt', registerFn),
|
|
209
|
+
authorize: int('AuthorizeInt', authorizeFn),
|
|
210
|
+
token: int('TokenInt', tokenFn),
|
|
211
|
+
mcp: int('McpProxyInt', mcpProxyFn),
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
this.proxyUrl = this.httpApi.api.apiEndpoint;
|
|
216
|
+
this.mcpUrl = `${this.proxyUrl}/mcp`;
|
|
217
|
+
this.metadataUrl = `${this.proxyUrl}/.well-known/oauth-protected-resource`;
|
|
218
|
+
this.authServerMetadataUrl = `${this.proxyUrl}/.well-known/oauth-authorization-server`;
|
|
219
|
+
this.tokenEndpoint = `${this.proxyUrl}/oauth/token`;
|
|
220
|
+
this.registrationEndpoint = `${this.proxyUrl}/register`;
|
|
221
|
+
|
|
222
|
+
// Wire the MCP proxy handler's upstream + metadata URLs after the API is
|
|
223
|
+
// created so its endpoint is resolvable.
|
|
224
|
+
mcpProxyFn.addEnvironment('UPSTREAM_URL', props.upstreamUrl);
|
|
225
|
+
mcpProxyFn.addEnvironment('PROXY_METADATA_URL', this.metadataUrl);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { APIGatewayProxyHandlerV2 } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
const COGNITO_ISSUER = process.env.COGNITO_ISSUER!;
|
|
4
|
+
|
|
5
|
+
let cached: Record<string, unknown> | undefined;
|
|
6
|
+
|
|
7
|
+
async function fetchUpstream(): Promise<Record<string, unknown>> {
|
|
8
|
+
if (cached) return cached;
|
|
9
|
+
const res = await fetch(`${COGNITO_ISSUER}/.well-known/openid-configuration`);
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
throw new Error(`Cognito discovery failed: ${res.status} ${res.statusText}`);
|
|
12
|
+
}
|
|
13
|
+
cached = (await res.json()) as Record<string, unknown>;
|
|
14
|
+
return cached;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
|
|
18
|
+
const proxyUrl = `https://${event.requestContext.domainName}`;
|
|
19
|
+
const upstream = await fetchUpstream();
|
|
20
|
+
|
|
21
|
+
// Override the proxy-handled endpoints.
|
|
22
|
+
// jwks_uri is kept as Cognito's so JWT verification works directly against
|
|
23
|
+
// Cognito. issuer is kept as Cognito's so JWT.iss validation matches.
|
|
24
|
+
// authorization_endpoint points to the proxy so we can strip the RFC 8707
|
|
25
|
+
// `resource` parameter that some MCP clients send (Cognito rejects token
|
|
26
|
+
// exchange for unregistered resource server URLs).
|
|
27
|
+
const metadata = {
|
|
28
|
+
...upstream,
|
|
29
|
+
authorization_endpoint: `${proxyUrl}/authorize`,
|
|
30
|
+
token_endpoint: `${proxyUrl}/oauth/token`,
|
|
31
|
+
registration_endpoint: `${proxyUrl}/register`,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
statusCode: 200,
|
|
36
|
+
headers: { 'content-type': 'application/json' },
|
|
37
|
+
body: JSON.stringify(metadata),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { APIGatewayProxyHandlerV2 } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
const COGNITO_HOSTED_UI_BASE = process.env.COGNITO_HOSTED_UI_BASE!;
|
|
4
|
+
|
|
5
|
+
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
|
|
6
|
+
const params = new URLSearchParams(event.rawQueryString ?? '');
|
|
7
|
+
|
|
8
|
+
console.log('[authorize]', {
|
|
9
|
+
scope: params.get('scope'),
|
|
10
|
+
redirect_uri: params.get('redirect_uri'),
|
|
11
|
+
has_resource: params.has('resource'),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// RFC 8707 `resource` parameter binds the access token's `aud` claim to
|
|
15
|
+
// the given URL. Cognito accepts it at /authorize but rejects the token
|
|
16
|
+
// exchange with `invalid_grant` when the URL is not registered as a
|
|
17
|
+
// resource server. MCP clients send this unconditionally, so strip it.
|
|
18
|
+
params.delete('resource');
|
|
19
|
+
|
|
20
|
+
const target = `${COGNITO_HOSTED_UI_BASE}/oauth2/authorize?${params.toString()}`;
|
|
21
|
+
return {
|
|
22
|
+
statusCode: 302,
|
|
23
|
+
headers: { location: target },
|
|
24
|
+
body: '',
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
APIGatewayProxyHandlerV2,
|
|
3
|
+
APIGatewayProxyStructuredResultV2,
|
|
4
|
+
} from 'aws-lambda';
|
|
5
|
+
|
|
6
|
+
const UPSTREAM_URL = process.env.UPSTREAM_URL!;
|
|
7
|
+
const PROXY_METADATA_URL = process.env.PROXY_METADATA_URL!;
|
|
8
|
+
|
|
9
|
+
// Headers stripped when relaying requests/responses through the proxy:
|
|
10
|
+
// RFC 7230 hop-by-hop headers (scoped to a single connection, not the next
|
|
11
|
+
// hop), plus entity headers that fetch() re-derives from the actual body -
|
|
12
|
+
// forwarding stale values would corrupt the response.
|
|
13
|
+
const HOP_BY_HOP = new Set([
|
|
14
|
+
'connection',
|
|
15
|
+
'keep-alive',
|
|
16
|
+
'proxy-authenticate',
|
|
17
|
+
'proxy-authorization',
|
|
18
|
+
'te',
|
|
19
|
+
'trailers',
|
|
20
|
+
'transfer-encoding',
|
|
21
|
+
'upgrade',
|
|
22
|
+
'content-length',
|
|
23
|
+
'content-encoding',
|
|
24
|
+
'host',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const handler: APIGatewayProxyHandlerV2 = async (
|
|
28
|
+
event,
|
|
29
|
+
): Promise<APIGatewayProxyStructuredResultV2> => {
|
|
30
|
+
const method = event.requestContext.http.method;
|
|
31
|
+
const qs = event.rawQueryString ? `?${event.rawQueryString}` : '';
|
|
32
|
+
const url = `${UPSTREAM_URL}${qs}`;
|
|
33
|
+
|
|
34
|
+
const fwdHeaders = new Headers();
|
|
35
|
+
for (const [k, v] of Object.entries(event.headers ?? {})) {
|
|
36
|
+
if (!v) continue;
|
|
37
|
+
if (HOP_BY_HOP.has(k.toLowerCase())) continue;
|
|
38
|
+
fwdHeaders.set(k, v);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const reqBody = event.body
|
|
42
|
+
? event.isBase64Encoded
|
|
43
|
+
? Buffer.from(event.body, 'base64')
|
|
44
|
+
: event.body
|
|
45
|
+
: undefined;
|
|
46
|
+
|
|
47
|
+
const upstream = await fetch(url, {
|
|
48
|
+
method,
|
|
49
|
+
headers: fwdHeaders,
|
|
50
|
+
body: reqBody as unknown as ArrayBuffer | string | undefined,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const respHeaders: Record<string, string> = {};
|
|
54
|
+
upstream.headers.forEach((v, k) => {
|
|
55
|
+
if (HOP_BY_HOP.has(k.toLowerCase())) return;
|
|
56
|
+
respHeaders[k] = v;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Rewrite WWW-Authenticate so MCP clients discover this proxy
|
|
60
|
+
// as the protected-resource metadata host instead of the upstream.
|
|
61
|
+
const authKey = Object.keys(respHeaders).find(
|
|
62
|
+
(k) => k.toLowerCase() === 'www-authenticate',
|
|
63
|
+
);
|
|
64
|
+
if (authKey) {
|
|
65
|
+
respHeaders[authKey] = respHeaders[authKey].replace(
|
|
66
|
+
/resource_metadata="[^"]*"/i,
|
|
67
|
+
`resource_metadata="${PROXY_METADATA_URL}"`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const buf = Buffer.from(await upstream.arrayBuffer());
|
|
72
|
+
const contentType = (
|
|
73
|
+
respHeaders['content-type'] ??
|
|
74
|
+
respHeaders['Content-Type'] ??
|
|
75
|
+
''
|
|
76
|
+
).toLowerCase();
|
|
77
|
+
const isText =
|
|
78
|
+
contentType.includes('json') ||
|
|
79
|
+
contentType.includes('text') ||
|
|
80
|
+
contentType.includes('xml') ||
|
|
81
|
+
contentType.includes('event-stream');
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
statusCode: upstream.status,
|
|
85
|
+
headers: respHeaders,
|
|
86
|
+
body: isText ? buf.toString('utf-8') : buf.toString('base64'),
|
|
87
|
+
isBase64Encoded: !isText,
|
|
88
|
+
};
|
|
89
|
+
};
|