@aws/nx-plugin-mcp 1.0.0-rc.96 → 1.0.0-rc.98
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/bin/aws-nx-mcp.js +90 -45
- package/docs/get_started/existing-project.mdx +7 -4
- package/docs/get_started/quick-start.mdx +58 -5
- package/docs/get_started/tutorials/dungeon-game/1.mdx +23 -20
- package/docs/get_started/tutorials/dungeon-game/2.mdx +11 -3
- package/docs/get_started/tutorials/dungeon-game/3.mdx +4 -0
- package/docs/get_started/tutorials/dungeon-game/4.mdx +7 -2
- package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +10 -1
- package/docs/guides/agentcore-gateway.mdx +4 -2
- package/docs/guides/agentcore-harness.mdx +2 -1
- package/docs/guides/astro-docs.mdx +25 -7
- package/docs/guides/connection/py-agent-a2a.mdx +2 -0
- package/docs/guides/connection/py-agent-dynamodb.mdx +1 -1
- package/docs/guides/connection/py-agent-gateway.mdx +3 -0
- package/docs/guides/connection/py-agent-mcp.mdx +18 -4
- package/docs/guides/connection/py-agent-rdb.mdx +5 -4
- package/docs/guides/connection/py-fast-api-dynamodb.mdx +1 -1
- package/docs/guides/connection/py-fast-api-rdb.mdx +7 -2
- package/docs/guides/connection/py-mcp-server-dynamodb.mdx +1 -1
- package/docs/guides/connection/py-mcp-server-rdb.mdx +6 -6
- package/docs/guides/connection/react-agui.mdx +34 -25
- package/docs/guides/connection/react-fastapi.mdx +114 -116
- package/docs/guides/connection/react-py-agent.mdx +4 -0
- package/docs/guides/connection/react-smithy.mdx +152 -98
- package/docs/guides/connection/react-trpc.mdx +13 -6
- package/docs/guides/connection/smithy-dynamodb.mdx +2 -8
- package/docs/guides/connection/smithy-rdb.mdx +3 -6
- package/docs/guides/connection/trpc-rdb.mdx +6 -6
- package/docs/guides/connection/ts-agent-a2a.mdx +4 -4
- package/docs/guides/connection/ts-agent-dynamodb.mdx +13 -11
- package/docs/guides/connection/ts-agent-gateway.mdx +2 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +19 -7
- package/docs/guides/connection/ts-agent-rdb.mdx +2 -2
- package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +11 -7
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +4 -3
- package/docs/guides/docker-bundling.mdx +23 -3
- package/docs/guides/fastapi.mdx +16 -5
- package/docs/guides/license.mdx +30 -6
- package/docs/guides/nx-generator.mdx +11 -12
- package/docs/guides/py-agent.mdx +129 -54
- package/docs/guides/py-mcp-server.mdx +3 -1
- package/docs/guides/py-rdb.mdx +13 -4
- package/docs/guides/python-lambda-function.mdx +8 -8
- package/docs/guides/python-project.mdx +28 -25
- package/docs/guides/react-website-auth.mdx +8 -8
- package/docs/guides/react-website.mdx +46 -27
- package/docs/guides/runtime-config.mdx +24 -4
- package/docs/guides/security.mdx +1 -1
- package/docs/guides/terraform-project.mdx +8 -2
- package/docs/guides/trpc.mdx +96 -12
- package/docs/guides/ts-agent.mdx +17 -3
- package/docs/guides/ts-dcr-proxy.mdx +24 -6
- package/docs/guides/ts-lambda-function.mdx +7 -1
- package/docs/guides/ts-mcp-server.mdx +45 -15
- package/docs/guides/ts-nx-plugin.mdx +17 -7
- package/docs/guides/ts-rdb.mdx +9 -2
- package/docs/guides/ts-smithy-api.mdx +76 -7
- package/docs/guides/typescript-infrastructure.mdx +27 -11
- package/docs/guides/typescript-project.mdx +12 -5
- package/docs/guides/workspace.mdx +21 -9
- package/docs/snippets/api/type-safe-api-integrations.mdx +2 -0
- package/docs/snippets/connection/a2a-infrastructure.mdx +3 -0
- package/docs/snippets/connection/infra-project-prerequisite.mdx +8 -0
- package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +1 -1
- package/docs/snippets/connection/ts-lambda-rdb-ssl-requirements.mdx +1 -1
- package/docs/snippets/connection/ts-mcp-server-rdb-ssl-requirements.mdx +1 -1
- package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +1 -1
- package/docs/snippets/prerequisites.mdx +1 -1
- package/docs/snippets/required-prerequisites.mdx +1 -1
- package/package.json +1 -1
- package/src/init/schema.json +5 -0
- package/src/py/project/schema.json +3 -1
|
@@ -50,16 +50,23 @@ The generator will create the following project structure in the `<directory>/<n
|
|
|
50
50
|
- config.ts Application configuration (eg. logo)
|
|
51
51
|
- components
|
|
52
52
|
- AppLayout Components for the overall layout and navigation bar
|
|
53
|
+
- app-sidebar.tsx Sidebar navigation (shadcn only)
|
|
54
|
+
- alert.tsx Alert component wrapping your UX provider's own
|
|
55
|
+
- spinner.tsx Spinner component wrapping your UX provider's own
|
|
53
56
|
- hooks
|
|
54
57
|
- useAppLayout.tsx Hook for adjusting the AppLayout from nested components (Cloudscape only)
|
|
55
58
|
- routes
|
|
59
|
+
- \_\_root.tsx Root route, wrapping every page in the AppLayout
|
|
56
60
|
- index.tsx Example route (or page) for TanStack Router
|
|
61
|
+
- routeTree.gen.ts Route tree, generated and maintained by TanStack Router
|
|
57
62
|
- styles.css Global styles
|
|
58
63
|
- vite.config.mts Vite and Vitest configuration
|
|
64
|
+
- project.json Nx project defining the project's targets
|
|
59
65
|
- tsconfig.json Base TypeScript configuration for source and tests
|
|
60
66
|
- tsconfig.app.json TypeScript configuration for source code
|
|
61
67
|
- tsconfig.spec.json TypeScript configuration for tests
|
|
62
68
|
- package.json Project manifest defining the project's package name and dependencies
|
|
69
|
+
- README.md Project README
|
|
63
70
|
</FileTree>
|
|
64
71
|
|
|
65
72
|
:::note[Without TanStack Router]
|
|
@@ -157,27 +164,33 @@ Your website comes with [TanStack Router](https://tanstack.com/router/v1) config
|
|
|
157
164
|
|
|
158
165
|
You can use the `Link` component or `useNavigate` hook to navigate between pages:
|
|
159
166
|
|
|
160
|
-
```tsx {1,
|
|
167
|
+
```tsx {1, 8, 12-13, 18}
|
|
161
168
|
import { Link, useNavigate } from '@tanstack/react-router';
|
|
162
169
|
|
|
163
|
-
export const MyComponent = (
|
|
170
|
+
export const MyComponent = ({
|
|
171
|
+
createProduct,
|
|
172
|
+
}: {
|
|
173
|
+
createProduct: () => Promise<string>;
|
|
174
|
+
}) => {
|
|
164
175
|
const navigate = useNavigate();
|
|
165
176
|
|
|
166
177
|
const submit = async () => {
|
|
167
|
-
const id = await
|
|
178
|
+
const id = await createProduct();
|
|
168
179
|
// Use `navigate` for redirecting after some asynchronous action
|
|
169
|
-
navigate({ to: '/products/$id',
|
|
180
|
+
navigate({ to: '/products/$id', params: { id } });
|
|
170
181
|
};
|
|
171
182
|
|
|
172
183
|
return (
|
|
173
184
|
<>
|
|
174
185
|
<Link to="/products">Cancel</Link>
|
|
175
|
-
<
|
|
186
|
+
<button onClick={submit}>Submit</button>
|
|
176
187
|
</>
|
|
177
|
-
)
|
|
188
|
+
);
|
|
178
189
|
};
|
|
179
190
|
```
|
|
180
191
|
|
|
192
|
+
`$id` in the `to` path is a [path parameter](https://tanstack.com/router/latest/docs/framework/react/guide/path-params), supplied through `params`. Swap the plain `<button>` for your UX kit's own button component as needed.
|
|
193
|
+
|
|
181
194
|
For more details, check out the [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview) documentation.
|
|
182
195
|
|
|
183
196
|
## Deploying your Website
|
|
@@ -191,13 +204,13 @@ To deploy your website, we recommend using the <Link path="guides/typescript-inf
|
|
|
191
204
|
You can use the CDK construct generated for you in `packages/common/constructs` to deploy your website.
|
|
192
205
|
|
|
193
206
|
```ts title="packages/infra/src/stacks/application-stack.ts" {3, 9}
|
|
194
|
-
import { Stack } from 'aws-cdk-lib';
|
|
207
|
+
import { Stack, StackProps } from 'aws-cdk-lib';
|
|
195
208
|
import { Construct } from 'constructs';
|
|
196
209
|
import { MyWebsite } from '@my-scope/common-constructs';
|
|
197
210
|
|
|
198
211
|
export class ApplicationStack extends Stack {
|
|
199
|
-
constructor(scope: Construct, id: string) {
|
|
200
|
-
super(scope, id);
|
|
212
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
213
|
+
super(scope, id, props);
|
|
201
214
|
|
|
202
215
|
new MyWebsite(this, 'MyWebsite');
|
|
203
216
|
}
|
|
@@ -266,13 +279,13 @@ The CloudFront distribution is protected by an [AWS WAFv2](https://docs.aws.amaz
|
|
|
266
279
|
To opt out, set `enableWaf` to `false` when you create your website:
|
|
267
280
|
|
|
268
281
|
```ts title="packages/infra/src/stacks/application-stack.ts" {10,15-18}
|
|
269
|
-
import { Stack } from 'aws-cdk-lib';
|
|
282
|
+
import { Stack, StackProps } from 'aws-cdk-lib';
|
|
270
283
|
import { Construct } from 'constructs';
|
|
271
284
|
import { MyWebsite, suppressRules } from '@my-scope/common-constructs';
|
|
272
285
|
|
|
273
286
|
export class ApplicationStack extends Stack {
|
|
274
|
-
constructor(scope: Construct, id: string) {
|
|
275
|
-
super(scope, id);
|
|
287
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
288
|
+
super(scope, id, props);
|
|
276
289
|
|
|
277
290
|
const website = new MyWebsite(this, 'MyWebsite', {
|
|
278
291
|
enableWaf: false,
|
|
@@ -318,14 +331,14 @@ The website bucket, the CloudFront distribution log bucket, and the CloudWatch L
|
|
|
318
331
|
If you want to use a different encryption configuration, pass the `encryption`, `encryptionKey` and `enableKeyRotation` props when you create your website:
|
|
319
332
|
|
|
320
333
|
```ts title="packages/infra/src/stacks/application-stack.ts" {11}
|
|
321
|
-
import { Stack } from 'aws-cdk-lib';
|
|
334
|
+
import { Stack, StackProps } from 'aws-cdk-lib';
|
|
322
335
|
import { Construct } from 'constructs';
|
|
323
336
|
import { BucketEncryption } from 'aws-cdk-lib/aws-s3';
|
|
324
337
|
import { MyWebsite } from '@my-scope/common-constructs';
|
|
325
338
|
|
|
326
339
|
export class ApplicationStack extends Stack {
|
|
327
|
-
constructor(scope: Construct, id: string) {
|
|
328
|
-
super(scope, id);
|
|
340
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
341
|
+
super(scope, id, props);
|
|
329
342
|
|
|
330
343
|
new MyWebsite(this, 'MyWebsite', {
|
|
331
344
|
encryption: BucketEncryption.S3_MANAGED,
|
|
@@ -401,11 +414,12 @@ module "my_website" {
|
|
|
401
414
|
```
|
|
402
415
|
|
|
403
416
|
:::caution[Checkov: S3_MANAGED]
|
|
404
|
-
Choosing `S3_MANAGED` fails
|
|
417
|
+
Choosing `S3_MANAGED` fails your project's `checkov` target with `CKV_AWS_145` on the website and distribution log buckets. The target runs `checkov` against your project's own `checkov.yml`, so add the check to its `skip-check` list:
|
|
405
418
|
|
|
406
|
-
```yaml title="packages/infra/
|
|
419
|
+
```yaml title="packages/infra/checkov.yml" {3}
|
|
407
420
|
skip-check:
|
|
408
|
-
|
|
421
|
+
# ...
|
|
422
|
+
- CKV_AWS_145 # S3 buckets are not KMS encrypted when encryption is S3_MANAGED
|
|
409
423
|
```
|
|
410
424
|
:::
|
|
411
425
|
|
|
@@ -439,11 +453,12 @@ module "my_website" {
|
|
|
439
453
|
```
|
|
440
454
|
|
|
441
455
|
:::caution[Checkov: key rotation disabled]
|
|
442
|
-
Disabling key rotation on the automatically created key fails
|
|
456
|
+
Disabling key rotation on the automatically created key fails your project's `checkov` target with `CKV_AWS_7`. Add it to the same `skip-check` list in `packages/infra/checkov.yml`:
|
|
443
457
|
|
|
444
|
-
```yaml title="packages/infra/
|
|
458
|
+
```yaml title="packages/infra/checkov.yml" {3}
|
|
445
459
|
skip-check:
|
|
446
|
-
|
|
460
|
+
# ...
|
|
461
|
+
- CKV_AWS_7 # Key rotation is disabled on the website's KMS key
|
|
447
462
|
```
|
|
448
463
|
:::
|
|
449
464
|
</Fragment>
|
|
@@ -458,14 +473,14 @@ By default the distribution uses the default CloudFront domain name (`*.cloudfro
|
|
|
458
473
|
Pass the `certificate` and `domainNames` props when you create your website:
|
|
459
474
|
|
|
460
475
|
```ts title="packages/infra/src/stacks/application-stack.ts" {11-13}
|
|
461
|
-
import { Stack } from 'aws-cdk-lib';
|
|
476
|
+
import { Stack, StackProps } from 'aws-cdk-lib';
|
|
462
477
|
import { Construct } from 'constructs';
|
|
463
478
|
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
|
|
464
479
|
import { MyWebsite } from '@my-scope/common-constructs';
|
|
465
480
|
|
|
466
481
|
export class ApplicationStack extends Stack {
|
|
467
|
-
constructor(scope: Construct, id: string) {
|
|
468
|
-
super(scope, id);
|
|
482
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
483
|
+
super(scope, id, props);
|
|
469
484
|
|
|
470
485
|
new MyWebsite(this, 'MyWebsite', {
|
|
471
486
|
domainNames: ['www.example.com'],
|
|
@@ -508,13 +523,13 @@ The `RuntimeConfig` CDK construct can be used to add and retrieve configuration
|
|
|
508
523
|
Your website CDK construct will deploy the `connection` namespace of the runtime configuration as a `runtime-config.json` file to the root of your S3 bucket.
|
|
509
524
|
|
|
510
525
|
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
511
|
-
import { Stack } from 'aws-cdk-lib';
|
|
526
|
+
import { Stack, StackProps } from 'aws-cdk-lib';
|
|
512
527
|
import { Construct } from 'constructs';
|
|
513
528
|
import { MyWebsite, MyApi } from '@my-scope/common-constructs';
|
|
514
529
|
|
|
515
530
|
export class ApplicationStack extends Stack {
|
|
516
|
-
constructor(scope: Construct, id: string) {
|
|
517
|
-
super(scope, id);
|
|
531
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
532
|
+
super(scope, id, props);
|
|
518
533
|
|
|
519
534
|
// Website can be declared at any point, since runtime config is resolved lazily
|
|
520
535
|
new MyWebsite(this, 'MyWebsite');
|
|
@@ -582,6 +597,10 @@ const MyComponent = () => {
|
|
|
582
597
|
};
|
|
583
598
|
```
|
|
584
599
|
|
|
600
|
+
:::note[Added when you need it]
|
|
601
|
+
`src/hooks/useRuntimeConfig.tsx` and `src/components/RuntimeConfig` are added to your website by the <Link path="/guides/react-website-auth">`ts#website#auth`</Link> generator and the <Link path="guides/connection">`connection`</Link> generators, since those are what put values into the runtime configuration. A website with neither has no runtime configuration to read, so the hook is not vended until one has run.
|
|
602
|
+
:::
|
|
603
|
+
|
|
585
604
|
:::note[Runtime Configuration]
|
|
586
605
|
For details on how runtime configuration is stored in AWS AppConfig and how server-side consumers (Lambda functions, agents) can retrieve it, see the <Link path="guides/runtime-config">Runtime Configuration</Link> guide.
|
|
587
606
|
:::
|
|
@@ -253,15 +253,27 @@ All generated API and agent constructs are automatically configured with:
|
|
|
253
253
|
|
|
254
254
|
Use `getAppConfig` from [`@aws-lambda-powertools/parameters`](https://docs.aws.amazon.com/powertools/typescript/latest/features/parameters):
|
|
255
255
|
|
|
256
|
+
`getAppConfig` returns any valid JSON value, so declare the shape you expect and cast the result — otherwise the property accesses do not compile under the workspace's strict `tsconfig`:
|
|
257
|
+
|
|
256
258
|
```ts
|
|
257
259
|
import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig';
|
|
258
260
|
|
|
261
|
+
interface ConnectionConfig {
|
|
262
|
+
apis?: Record<string, string>;
|
|
263
|
+
cognitoProps?: {
|
|
264
|
+
region: string;
|
|
265
|
+
userPoolId: string;
|
|
266
|
+
userPoolWebClientId: string;
|
|
267
|
+
identityPoolId: string;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
259
271
|
// Retrieve the 'connection' namespace as a parsed JSON object
|
|
260
|
-
const config = await getAppConfig('connection', {
|
|
272
|
+
const config = (await getAppConfig('connection', {
|
|
261
273
|
application: process.env.RUNTIME_CONFIG_APP_ID!,
|
|
262
274
|
environment: 'default',
|
|
263
275
|
transform: 'json',
|
|
264
|
-
});
|
|
276
|
+
})) as ConnectionConfig;
|
|
265
277
|
|
|
266
278
|
// Access values
|
|
267
279
|
const apiUrl = config.apis?.MyApi;
|
|
@@ -271,16 +283,24 @@ const cognitoProps = config.cognitoProps;
|
|
|
271
283
|
You can also retrieve custom namespaces:
|
|
272
284
|
|
|
273
285
|
```ts
|
|
286
|
+
interface TablesConfig {
|
|
287
|
+
users?: { tableName: string; tableArn: string };
|
|
288
|
+
}
|
|
289
|
+
|
|
274
290
|
// Retrieve a custom 'tables' namespace
|
|
275
|
-
const tablesConfig = await getAppConfig('tables', {
|
|
291
|
+
const tablesConfig = (await getAppConfig('tables', {
|
|
276
292
|
application: process.env.RUNTIME_CONFIG_APP_ID!,
|
|
277
293
|
environment: 'default',
|
|
278
294
|
transform: 'json',
|
|
279
|
-
});
|
|
295
|
+
})) as TablesConfig;
|
|
280
296
|
|
|
281
297
|
const usersTableName = tablesConfig.users?.tableName;
|
|
282
298
|
```
|
|
283
299
|
|
|
300
|
+
:::tip[Validate at the boundary]
|
|
301
|
+
A cast tells the compiler the shape but does not check it at runtime. For configuration you don't control, parse it with a [Zod](https://zod.dev/) schema instead, so a malformed value fails loudly rather than surfacing as `undefined` later.
|
|
302
|
+
:::
|
|
303
|
+
|
|
284
304
|
</TabItem>
|
|
285
305
|
<TabItem label="Python" icon="seti:python">
|
|
286
306
|
|
package/docs/guides/security.mdx
CHANGED
|
@@ -28,7 +28,7 @@ Projects which build container images (for example agents, MCP servers, and data
|
|
|
28
28
|
|
|
29
29
|
### Credential Scanning
|
|
30
30
|
|
|
31
|
-
Workspaces include [git-secrets](https://github.com/awslabs/git-secrets) pre-commit hooks which scan staged files for AWS credential patterns, preventing accidental commits of access keys and other sensitive values. See the <Link path="/guides/workspace#git-secrets">Git Secrets</Link> section of the workspace guide.
|
|
31
|
+
Workspaces include [git-secrets](https://github.com/awslabs/git-secrets) pre-commit hooks which scan staged files for AWS credential patterns, preventing accidental commits of access keys and other sensitive values. Both creating a workspace and adopting the plugin into an existing one set these up, unless you opt out. See the <Link path="/guides/workspace#git-secrets">Git Secrets</Link> section of the workspace guide.
|
|
32
32
|
|
|
33
33
|
### Authentication
|
|
34
34
|
|
|
@@ -111,6 +111,8 @@ You can start writing your Terraform infrastructure inside `src/main.tf`, for ex
|
|
|
111
111
|
+}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
Note that the S3 bucket above would fail the Checkov security scan, which checks that the bucket has the appropriate security settings enabled.
|
|
115
|
+
|
|
114
116
|
### Cross project dependencies
|
|
115
117
|
|
|
116
118
|
If you wanted to execute a module from a separate project (lib), you could do so as follows:
|
|
@@ -134,8 +136,12 @@ To add new environments, create a new `src/env/<environment>.tfvars` file with t
|
|
|
134
136
|
```hcl
|
|
135
137
|
# Production environment variables
|
|
136
138
|
environment = "prod"
|
|
137
|
-
|
|
139
|
+
aws_region = "us-west-2"
|
|
138
140
|
```
|
|
141
|
+
|
|
142
|
+
:::note[Variable names]
|
|
143
|
+
Every name you set here must be declared in `src/variables.tf` — the generator declares `aws_region` and `environment`.
|
|
144
|
+
:::
|
|
139
145
|
</TabItem>
|
|
140
146
|
<TabItem label="project.json">
|
|
141
147
|
```diff
|
|
@@ -200,7 +206,7 @@ region = "us-west-2"
|
|
|
200
206
|
"command": "terraform plan -var-file=env/dev.tfvars -out=../../../dist/packages/infra/terraform/dev.tfplan"
|
|
201
207
|
},
|
|
202
208
|
+ "prod": {
|
|
203
|
-
+ "command": "terraform plan -var-file=env/
|
|
209
|
+
+ "command": "terraform plan -var-file=env/prod.tfvars -out=../../../dist/packages/infra/terraform/prod.tfplan"
|
|
204
210
|
+ }
|
|
205
211
|
},
|
|
206
212
|
"options": {
|
package/docs/guides/trpc.mdx
CHANGED
|
@@ -16,6 +16,8 @@ import NxCommands from '@components/nx-commands.astro';
|
|
|
16
16
|
import Infrastructure from '@components/infrastructure.astro';
|
|
17
17
|
import Snippet from '@components/snippet.astro';
|
|
18
18
|
import OptionFilter from '@components/option-filter.astro';
|
|
19
|
+
import InstallCommand from '@components/install-command.astro';
|
|
20
|
+
import { TS_VERSIONS } from '../../../../../../packages/nx-plugin/src/utils/versions';
|
|
19
21
|
|
|
20
22
|
[tRPC](https://trpc.io/) is a framework for building APIs in TypeScript with end-to-end type safety. Using tRPC, updates to API operation inputs and outputs are immediately reflected in client code and are visible in your IDE without the need to rebuild your project.
|
|
21
23
|
|
|
@@ -49,15 +51,18 @@ The generator will create the following project structure in the `<directory>/<a
|
|
|
49
51
|
|
|
50
52
|
<FileTree>
|
|
51
53
|
- src
|
|
54
|
+
- index.ts Package entrypoint re-exporting the router, context, client and schema
|
|
52
55
|
- init.ts Backend tRPC initialisation
|
|
53
56
|
- handler.ts Lambda handler entrypoint
|
|
54
57
|
- router.ts tRPC router definition
|
|
55
58
|
- schema Schema definitions using Zod
|
|
59
|
+
- index.ts Barrel re-exporting every schema
|
|
56
60
|
- echo.ts Example definitions for the input and output of the "echo" procedure
|
|
57
61
|
- z-async-iterable.ts Zod helper for subscriptions (REST API only)
|
|
58
62
|
- procedures Procedures (or operations) exposed by your API
|
|
59
63
|
- echo.ts Example procedure
|
|
60
64
|
- middleware
|
|
65
|
+
- index.ts Barrel re-exporting the middleware, and the procedure context type
|
|
61
66
|
- error.ts Middleware for error handling
|
|
62
67
|
- logger.ts middleware for configuring AWS Powertools for Lambda logging
|
|
63
68
|
- tracer.ts middleware for configuring AWS Powertools for Lambda tracing
|
|
@@ -65,9 +70,15 @@ The generator will create the following project structure in the `<directory>/<a
|
|
|
65
70
|
- local-server.ts tRPC standalone adapter entrypoint for local development server
|
|
66
71
|
- client
|
|
67
72
|
- index.ts Type-safe client for machine-to-machine API calls
|
|
73
|
+
- rolldown.config.ts Bundle configuration for the Lambda deployment package
|
|
68
74
|
- tsconfig.json TypeScript configuration
|
|
75
|
+
- tsconfig.lib.json TypeScript configuration for the library sources
|
|
76
|
+
- tsconfig.spec.json TypeScript configuration for the tests
|
|
77
|
+
- vitest.config.mts Vitest configuration
|
|
69
78
|
- package.json Project manifest defining the project's package name and dependencies
|
|
70
79
|
- project.json Project configuration and build targets
|
|
80
|
+
- README.md Project readme
|
|
81
|
+
- .gitignore Ignores the project's build output
|
|
71
82
|
|
|
72
83
|
</FileTree>
|
|
73
84
|
|
|
@@ -297,6 +308,10 @@ As an example, let's implement some middlware to extract some details about the
|
|
|
297
308
|
<OptionFilter when={{ auth: 'iam' }} description="Identity middleware example for IAM-authenticated APIs">
|
|
298
309
|
This example walks through identity middleware for `IAM` authentication. We look up the caller in Cognito using the sub extracted from the API Gateway event.
|
|
299
310
|
|
|
311
|
+
The lookup uses the Cognito Identity Provider client, which is not a dependency of a generated tRPC API. Install it into your API project first:
|
|
312
|
+
|
|
313
|
+
<InstallCommand pkg={`@aws-sdk/client-cognito-identity-provider@${TS_VERSIONS['@aws-sdk/client-cognito-identity-provider']}`} project="@my-scope/my-api" />
|
|
314
|
+
|
|
300
315
|
First, we define what we'll add to the context:
|
|
301
316
|
|
|
302
317
|
```ts
|
|
@@ -334,8 +349,8 @@ In our case, we want to extract details about the calling Cognito user. We'll do
|
|
|
334
349
|
```ts
|
|
335
350
|
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
|
|
336
351
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
337
|
-
import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
338
|
-
import { APIGatewayProxyEvent } from 'aws-lambda';
|
|
352
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
353
|
+
import type { APIGatewayProxyEvent } from 'aws-lambda';
|
|
339
354
|
|
|
340
355
|
export interface IIdentityContext {
|
|
341
356
|
identity?: {
|
|
@@ -345,12 +360,17 @@ export interface IIdentityContext {
|
|
|
345
360
|
}
|
|
346
361
|
|
|
347
362
|
export const createIdentityPlugin = () => {
|
|
348
|
-
const t = initTRPC
|
|
363
|
+
const t = initTRPC
|
|
364
|
+
.context<
|
|
365
|
+
IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>
|
|
366
|
+
>()
|
|
367
|
+
.create();
|
|
349
368
|
|
|
350
369
|
const cognito = new CognitoIdentityProvider();
|
|
351
370
|
|
|
352
371
|
return t.procedure.use(async (opts) => {
|
|
353
|
-
const cognitoAuthenticationProvider =
|
|
372
|
+
const cognitoAuthenticationProvider =
|
|
373
|
+
opts.ctx.event.requestContext?.identity?.cognitoAuthenticationProvider;
|
|
354
374
|
|
|
355
375
|
let sub: string | undefined = undefined;
|
|
356
376
|
if (cognitoAuthenticationProvider) {
|
|
@@ -397,8 +417,8 @@ export const createIdentityPlugin = () => {
|
|
|
397
417
|
```ts
|
|
398
418
|
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
|
|
399
419
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
400
|
-
import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
401
|
-
import { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
|
|
420
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
421
|
+
import type { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
|
|
402
422
|
|
|
403
423
|
export interface IIdentityContext {
|
|
404
424
|
identity?: {
|
|
@@ -408,7 +428,12 @@ export interface IIdentityContext {
|
|
|
408
428
|
}
|
|
409
429
|
|
|
410
430
|
export const createIdentityPlugin = () => {
|
|
411
|
-
const t = initTRPC
|
|
431
|
+
const t = initTRPC
|
|
432
|
+
.context<
|
|
433
|
+
IIdentityContext &
|
|
434
|
+
CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer>
|
|
435
|
+
>()
|
|
436
|
+
.create();
|
|
412
437
|
|
|
413
438
|
const cognito = new CognitoIdentityProvider();
|
|
414
439
|
|
|
@@ -481,12 +506,16 @@ export interface IIdentityContext {
|
|
|
481
506
|
|
|
482
507
|
Note that we define an additional _optional_ property on the context. tRPC manages ensuring that this is defined in procedures which have correctly configured this middleware.
|
|
483
508
|
|
|
484
|
-
Next, the middleware itself
|
|
509
|
+
Next, the middleware itself. The event type and the location of the claims differ between a REST API and an HTTP API, so the implementation depends on your selected `infra`:
|
|
510
|
+
|
|
511
|
+
<Tabs syncKey="http-rest">
|
|
512
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
513
|
+
A REST API's Cognito User Pools authorizer places the claims at `event.requestContext.authorizer.claims`:
|
|
485
514
|
|
|
486
515
|
```ts
|
|
487
516
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
488
|
-
import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
489
|
-
import { APIGatewayProxyEvent } from 'aws-lambda';
|
|
517
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
518
|
+
import type { APIGatewayProxyEvent } from 'aws-lambda';
|
|
490
519
|
|
|
491
520
|
export interface IIdentityContext {
|
|
492
521
|
identity?: {
|
|
@@ -497,7 +526,9 @@ export interface IIdentityContext {
|
|
|
497
526
|
|
|
498
527
|
export const createIdentityPlugin = () => {
|
|
499
528
|
const t = initTRPC
|
|
500
|
-
.context<
|
|
529
|
+
.context<
|
|
530
|
+
IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>
|
|
531
|
+
>()
|
|
501
532
|
.create();
|
|
502
533
|
|
|
503
534
|
return t.procedure.use(async (opts) => {
|
|
@@ -506,7 +537,7 @@ export const createIdentityPlugin = () => {
|
|
|
506
537
|
| undefined;
|
|
507
538
|
|
|
508
539
|
const sub = claims?.sub;
|
|
509
|
-
const username = claims?.username;
|
|
540
|
+
const username = claims?.username ?? claims?.['cognito:username'];
|
|
510
541
|
|
|
511
542
|
if (!sub || !username) {
|
|
512
543
|
throw new TRPCError({
|
|
@@ -527,6 +558,59 @@ export const createIdentityPlugin = () => {
|
|
|
527
558
|
});
|
|
528
559
|
};
|
|
529
560
|
```
|
|
561
|
+
</TabItem>
|
|
562
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
563
|
+
An HTTP API's JWT authorizer delivers a payload-v2 event whose claims sit one level deeper, at `event.requestContext.authorizer.jwt.claims`. The context must be typed on `APIGatewayProxyEventV2WithJWTAuthorizer` to match the one the generated `publicProcedure` uses — otherwise `.concat()` fails with tRPC's `Context mismatch` error:
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
import { initTRPC, TRPCError } from '@trpc/server';
|
|
567
|
+
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
|
|
568
|
+
import type { APIGatewayProxyEventV2WithJWTAuthorizer } from 'aws-lambda';
|
|
569
|
+
|
|
570
|
+
export interface IIdentityContext {
|
|
571
|
+
identity?: {
|
|
572
|
+
sub: string;
|
|
573
|
+
username: string;
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export const createIdentityPlugin = () => {
|
|
578
|
+
const t = initTRPC
|
|
579
|
+
.context<
|
|
580
|
+
IIdentityContext &
|
|
581
|
+
CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithJWTAuthorizer>
|
|
582
|
+
>()
|
|
583
|
+
.create();
|
|
584
|
+
|
|
585
|
+
return t.procedure.use(async (opts) => {
|
|
586
|
+
const claims = opts.ctx.event.requestContext?.authorizer?.jwt?.claims as
|
|
587
|
+
| Record<string, string>
|
|
588
|
+
| undefined;
|
|
589
|
+
|
|
590
|
+
const sub = claims?.sub;
|
|
591
|
+
const username = claims?.username ?? claims?.['cognito:username'];
|
|
592
|
+
|
|
593
|
+
if (!sub || !username) {
|
|
594
|
+
throw new TRPCError({
|
|
595
|
+
code: 'FORBIDDEN',
|
|
596
|
+
message: 'Unable to determine calling user',
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return await opts.next({
|
|
601
|
+
ctx: {
|
|
602
|
+
...opts.ctx,
|
|
603
|
+
identity: {
|
|
604
|
+
sub,
|
|
605
|
+
username,
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
```
|
|
612
|
+
</TabItem>
|
|
613
|
+
</Tabs>
|
|
530
614
|
|
|
531
615
|
You can then mix the plugin into any procedure that needs the caller's identity:
|
|
532
616
|
|
package/docs/guides/ts-agent.mdx
CHANGED
|
@@ -61,6 +61,8 @@ The generator will add the following files to your existing TypeScript project.
|
|
|
61
61
|
- router.ts tRPC router with agent procedures
|
|
62
62
|
- agent.ts Main agent definition with sample tools
|
|
63
63
|
- session.ts Resolves the SessionManager used to persist conversation state
|
|
64
|
+
- schema/
|
|
65
|
+
- z-async-iterable.ts Zod schema for the router's streamed responses
|
|
64
66
|
- client.ts Vended client for invoking your agent
|
|
65
67
|
- agent-core-trpc-client.ts Client factory for connecting to agents on AgentCore Runtime
|
|
66
68
|
- Dockerfile Container image definition (only when `infra` is `agentcore-ecr`)
|
|
@@ -81,6 +83,8 @@ The entry point uses the [Strands A2A Express Server](https://strandsagents.com/
|
|
|
81
83
|
- index.ts A2A Express server entry point
|
|
82
84
|
- agent.ts Main agent definition with sample tools
|
|
83
85
|
- session.ts Resolves the SessionManager used to persist conversation state
|
|
86
|
+
- middleware/
|
|
87
|
+
- session-id-middleware.ts Binds the inbound AgentCore session ID for the request
|
|
84
88
|
- Dockerfile Container image definition (only when `infra` is `agentcore-ecr`)
|
|
85
89
|
- package.json Updated with Strands and Express dependencies
|
|
86
90
|
- project.json Updated with agent serve targets
|
|
@@ -99,6 +103,8 @@ The entry point uses [`@ag-ui/aws-strands`](https://www.npmjs.com/package/@ag-ui
|
|
|
99
103
|
- index.ts AG-UI server entry point (Express + SSE)
|
|
100
104
|
- agent.ts Main agent definition with sample tools
|
|
101
105
|
- session.ts Resolves the SessionManager used to persist conversation state
|
|
106
|
+
- middleware/
|
|
107
|
+
- session-id-middleware.ts Binds the inbound AgentCore session ID for the request
|
|
102
108
|
- Dockerfile Container image definition (only when `infra` is `agentcore-ecr`)
|
|
103
109
|
- package.json Updated with Strands and AG-UI dependencies
|
|
104
110
|
- project.json Updated with agent serve targets
|
|
@@ -280,7 +286,9 @@ If you have added multiple components to your project (agents, MCP servers, etc.
|
|
|
280
286
|
|
|
281
287
|
<NxCommands commands={['agent-dev your-project']} />
|
|
282
288
|
|
|
283
|
-
This uses `tsx --watch` to automatically restart the server when files change. The agent will be available at `http://localhost:8081` (or the assigned port if you have multiple agents).
|
|
289
|
+
This uses `tsx --watch` to automatically restart the server when files change. The agent will be available at `http://localhost:8081` (or the assigned port if you have multiple agents — read it from `metadata.ports` in the project's `project.json`).
|
|
290
|
+
|
|
291
|
+
A `<your-agent-name>-serve` target is also generated, which runs the agent against your deployed infrastructure and therefore requires `RUNTIME_CONFIG_APP_ID` to be set. See the <Link path="guides/local-development">Local Development</Link> guide for the difference between `dev` and `serve`.
|
|
284
292
|
|
|
285
293
|
### Chat with Your Agent
|
|
286
294
|
|
|
@@ -400,12 +408,16 @@ Generate the session ID rather than deriving it from user-supplied values such a
|
|
|
400
408
|
|
|
401
409
|
## Invoking your Agent
|
|
402
410
|
|
|
411
|
+
<OptionFilter when={{ protocol: 'http' }} description="tRPC-over-WebSocket client factory invocation details">
|
|
403
412
|
Agent communication is transmitted via tRPC over WebSocket. As such, it's recommended to use the generated type-safe client factory in `client.ts`.
|
|
404
413
|
|
|
405
|
-
<OptionFilter when={{ protocol: 'http' }} description="tRPC-over-WebSocket client factory invocation details">
|
|
406
414
|
### Invoke the Local Server
|
|
407
415
|
|
|
408
|
-
|
|
416
|
+
Start your agent with the `<your-agent-name>-dev` target:
|
|
417
|
+
|
|
418
|
+
<NxCommands commands={['agent-dev your-project']} />
|
|
419
|
+
|
|
420
|
+
Then invoke it using the `.local` factory method from the client factory.
|
|
409
421
|
|
|
410
422
|
You can, for example create a file named `scripts/test.ts` in your workspace which imports the client:
|
|
411
423
|
|
|
@@ -420,6 +432,8 @@ const client = MyAgentClient.local({ url: 'http://localhost:8081/ws' });
|
|
|
420
432
|
client.invoke.subscribe({ prompt: 'what is 1 plus 1?' }, { onData: console.log });
|
|
421
433
|
```
|
|
422
434
|
|
|
435
|
+
Substitute the port assigned to your agent — read it from `metadata.ports` in the project's `project.json`.
|
|
436
|
+
|
|
423
437
|
:::tip[Quick Testing]
|
|
424
438
|
Run with `tsx` as a quick way to test out your agent.
|
|
425
439
|
|
|
@@ -41,6 +41,12 @@ The generator creates a standalone <Link path="/guides/typescript-project">TypeS
|
|
|
41
41
|
- authorize.ts Redirects to the Cognito Hosted UI
|
|
42
42
|
- token.ts Injects the App Client secret and exchanges the token
|
|
43
43
|
- mcp-proxy.ts Proxies `/mcp` requests to the upstream MCP server
|
|
44
|
+
- index.ts Project entry point
|
|
45
|
+
- rolldown.config.ts Bundle configuration, one entry per handler
|
|
46
|
+
- README.md Project README
|
|
47
|
+
- package.json Declares the handlers' dependencies
|
|
48
|
+
- project.json Adds a bundle target per handler
|
|
49
|
+
- tsconfig.json / tsconfig.lib.json / tsconfig.spec.json TypeScript configuration
|
|
44
50
|
|
|
45
51
|
</FileTree>
|
|
46
52
|
|
|
@@ -103,7 +109,7 @@ You provide:
|
|
|
103
109
|
Instantiate the generated construct in your stack, passing the required properties:
|
|
104
110
|
|
|
105
111
|
```typescript
|
|
106
|
-
import { DcrProxy } from '
|
|
112
|
+
import { DcrProxy } from '@my-scope/common-constructs';
|
|
107
113
|
|
|
108
114
|
new DcrProxy(this, 'DcrProxy', {
|
|
109
115
|
userPoolId: userPool.userPoolId,
|
|
@@ -155,8 +161,9 @@ import {
|
|
|
155
161
|
DcrProxy,
|
|
156
162
|
MyProjectMcpServer,
|
|
157
163
|
UserIdentity,
|
|
158
|
-
} from '
|
|
164
|
+
} from '@my-scope/common-constructs';
|
|
159
165
|
import { OAuthScope } from 'aws-cdk-lib/aws-cognito';
|
|
166
|
+
import * as kms from 'aws-cdk-lib/aws-kms';
|
|
160
167
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
161
168
|
|
|
162
169
|
const identity = new UserIdentity(this, 'Identity');
|
|
@@ -179,6 +186,9 @@ const proxyClient = identity.userPool.addClient('DcrProxyClient', {
|
|
|
179
186
|
// Store the App Client secret in Secrets Manager for the token handler to read
|
|
180
187
|
const clientSecret = new secretsmanager.Secret(this, 'ClientSecret', {
|
|
181
188
|
secretStringValue: proxyClient.userPoolClientSecret,
|
|
189
|
+
encryptionKey: new kms.Key(this, 'ClientSecretKey', {
|
|
190
|
+
enableKeyRotation: true,
|
|
191
|
+
}),
|
|
182
192
|
});
|
|
183
193
|
|
|
184
194
|
// The MCP server, authorizing JWTs issued for the same App Client
|
|
@@ -267,8 +277,9 @@ import {
|
|
|
267
277
|
DcrProxy,
|
|
268
278
|
MyGateway,
|
|
269
279
|
UserIdentity,
|
|
270
|
-
} from '
|
|
280
|
+
} from '@my-scope/common-constructs';
|
|
271
281
|
import { OAuthScope } from 'aws-cdk-lib/aws-cognito';
|
|
282
|
+
import * as kms from 'aws-cdk-lib/aws-kms';
|
|
272
283
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
273
284
|
|
|
274
285
|
const identity = new UserIdentity(this, 'Identity');
|
|
@@ -291,6 +302,9 @@ const proxyClient = identity.userPool.addClient('DcrProxyClient', {
|
|
|
291
302
|
// Store the App Client secret in Secrets Manager for the token handler to read
|
|
292
303
|
const clientSecret = new secretsmanager.Secret(this, 'ClientSecret', {
|
|
293
304
|
secretStringValue: proxyClient.userPoolClientSecret,
|
|
305
|
+
encryptionKey: new kms.Key(this, 'ClientSecretKey', {
|
|
306
|
+
enableKeyRotation: true,
|
|
307
|
+
}),
|
|
294
308
|
});
|
|
295
309
|
|
|
296
310
|
// The gateway, authorizing JWTs issued for the same App Client
|
|
@@ -307,7 +321,7 @@ new DcrProxy(this, 'DcrProxy', {
|
|
|
307
321
|
cognitoClientSecretArn: clientSecret.secretArn,
|
|
308
322
|
cognitoHostedUiBase: identity.userPoolDomain.baseUrl(),
|
|
309
323
|
// Use the gateway construct's URL rather than hardcoding it
|
|
310
|
-
upstreamUrl: gateway.
|
|
324
|
+
upstreamUrl: gateway.gatewayUrl,
|
|
311
325
|
});
|
|
312
326
|
```
|
|
313
327
|
</Fragment>
|
|
@@ -341,7 +355,7 @@ resource "aws_secretsmanager_secret_version" "client_secret" {
|
|
|
341
355
|
|
|
342
356
|
# The gateway, authorizing JWTs issued for the same App Client
|
|
343
357
|
module "my_gateway" {
|
|
344
|
-
source = "../../common/terraform/src/app/
|
|
358
|
+
source = "../../common/terraform/src/app/gateways/my-gateway"
|
|
345
359
|
|
|
346
360
|
user_pool_id = module.user_identity.user_pool_id
|
|
347
361
|
user_pool_client_ids = [aws_cognito_user_pool_client.dcr_proxy.id]
|
|
@@ -466,8 +480,9 @@ import {
|
|
|
466
480
|
DcrProxy,
|
|
467
481
|
MyProjectMcpServer,
|
|
468
482
|
UserIdentity,
|
|
469
|
-
} from '
|
|
483
|
+
} from '@my-scope/common-constructs';
|
|
470
484
|
import { OAuthScope, CfnManagedLoginBranding } from 'aws-cdk-lib/aws-cognito';
|
|
485
|
+
import * as kms from 'aws-cdk-lib/aws-kms';
|
|
471
486
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
472
487
|
|
|
473
488
|
// The user pool created by ts#website#auth for your website users
|
|
@@ -492,6 +507,9 @@ new CfnManagedLoginBranding(this, 'DcrProxyClientBranding', {
|
|
|
492
507
|
|
|
493
508
|
const clientSecret = new secretsmanager.Secret(this, 'ClientSecret', {
|
|
494
509
|
secretStringValue: proxyClient.userPoolClientSecret,
|
|
510
|
+
encryptionKey: new kms.Key(this, 'ClientSecretKey', {
|
|
511
|
+
enableKeyRotation: true,
|
|
512
|
+
}),
|
|
495
513
|
});
|
|
496
514
|
|
|
497
515
|
const mcpServer = new MyProjectMcpServer(this, 'MyProjectMcpServer', {
|