@aws/nx-plugin-mcp 1.0.0-rc.7 → 1.0.0-rc.71

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.
Files changed (195) hide show
  1. package/bin/aws-nx-mcp.js +12317 -10933
  2. package/docs/get_started/building-with-ai.mdx +116 -0
  3. package/docs/get_started/concepts.mdx +67 -0
  4. package/docs/get_started/existing-project.mdx +180 -0
  5. package/docs/get_started/graph-builder.mdx +39 -0
  6. package/docs/get_started/quick-start.mdx +277 -0
  7. package/docs/get_started/tutorials/contribute-generator.mdx +408 -0
  8. package/docs/get_started/tutorials/dungeon-game/1.mdx +1301 -0
  9. package/docs/get_started/tutorials/dungeon-game/2.mdx +237 -0
  10. package/docs/get_started/tutorials/dungeon-game/3.mdx +76 -0
  11. package/docs/get_started/tutorials/dungeon-game/4.mdx +164 -0
  12. package/docs/get_started/tutorials/dungeon-game/overview.mdx +145 -0
  13. package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +41 -0
  14. package/docs/get_started/tutorials/existing-project.mdx +4 -0
  15. package/docs/get_started/upgrading.mdx +147 -0
  16. package/docs/guides/agentcore-gateway.mdx +490 -0
  17. package/docs/guides/agentcore-harness.mdx +275 -0
  18. package/docs/guides/astro-docs.mdx +8 -0
  19. package/docs/guides/connection/agentcore-gateway-agent.mdx +222 -0
  20. package/docs/guides/connection/agentcore-gateway-gateway.mdx +154 -0
  21. package/docs/guides/connection/agentcore-gateway-mcp.mdx +134 -0
  22. package/docs/guides/connection/py-agent-a2a.mdx +48 -16
  23. package/docs/guides/connection/py-agent-dynamodb.mdx +116 -0
  24. package/docs/guides/connection/py-agent-gateway.mdx +178 -0
  25. package/docs/guides/connection/py-agent-mcp.mdx +43 -14
  26. package/docs/guides/connection/py-agent-rdb.mdx +178 -0
  27. package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
  28. package/docs/guides/connection/py-fast-api-rdb.mdx +184 -0
  29. package/docs/guides/connection/py-mcp-server-dynamodb.mdx +116 -0
  30. package/docs/guides/connection/py-mcp-server-rdb.mdx +187 -0
  31. package/docs/guides/connection/react-agentcore-gateway.mdx +112 -0
  32. package/docs/guides/connection/react-agui.mdx +13 -13
  33. package/docs/guides/connection/react-fastapi.mdx +38 -2
  34. package/docs/guides/connection/react-py-agent.mdx +9 -15
  35. package/docs/guides/connection/react-smithy.mdx +3 -3
  36. package/docs/guides/connection/react-trpc.mdx +1 -1
  37. package/docs/guides/connection/react-ts-agent.mdx +8 -8
  38. package/docs/guides/connection/smithy-dynamodb.mdx +5 -5
  39. package/docs/guides/connection/smithy-rdb.mdx +9 -9
  40. package/docs/guides/connection/trpc-dynamodb.mdx +5 -5
  41. package/docs/guides/connection/trpc-rdb.mdx +6 -6
  42. package/docs/guides/connection/ts-agent-a2a.mdx +14 -11
  43. package/docs/guides/connection/ts-agent-dynamodb.mdx +5 -5
  44. package/docs/guides/connection/ts-agent-gateway.mdx +143 -0
  45. package/docs/guides/connection/ts-agent-mcp.mdx +12 -9
  46. package/docs/guides/connection/ts-agent-rdb.mdx +70 -25
  47. package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +5 -5
  48. package/docs/guides/connection/ts-mcp-server-rdb.mdx +68 -18
  49. package/docs/guides/connection.mdx +122 -5
  50. package/docs/guides/docker-bundling.mdx +69 -12
  51. package/docs/guides/fastapi.mdx +249 -9
  52. package/docs/guides/local-development.mdx +87 -0
  53. package/docs/guides/nx-generator.mdx +4 -3
  54. package/docs/guides/nx-migration.mdx +165 -0
  55. package/docs/guides/py-agent.mdx +264 -49
  56. package/docs/guides/py-dynamodb.mdx +476 -0
  57. package/docs/guides/py-mcp-server.mdx +61 -2
  58. package/docs/guides/py-rdb.mdx +265 -0
  59. package/docs/guides/python-lambda-function.mdx +1 -1
  60. package/docs/guides/react-website-auth.mdx +65 -4
  61. package/docs/guides/react-website.mdx +149 -30
  62. package/docs/guides/runtime-config.mdx +1 -1
  63. package/docs/guides/security.mdx +75 -0
  64. package/docs/guides/smithy-project.mdx +167 -0
  65. package/docs/guides/terraform-project.mdx +2 -2
  66. package/docs/guides/trpc.mdx +53 -16
  67. package/docs/guides/ts-agent.mdx +183 -10
  68. package/docs/guides/ts-dcr-proxy.mdx +569 -0
  69. package/docs/guides/ts-dynamodb.mdx +66 -242
  70. package/docs/guides/ts-lambda-function.mdx +1 -1
  71. package/docs/guides/ts-mcp-server.mdx +109 -29
  72. package/docs/guides/ts-nx-plugin.mdx +3 -3
  73. package/docs/guides/ts-rdb.mdx +113 -467
  74. package/docs/guides/ts-smithy-api.mdx +258 -18
  75. package/docs/guides/typescript-infrastructure.mdx +46 -24
  76. package/docs/guides/typescript-project.mdx +134 -27
  77. package/docs/guides/workspace.mdx +10 -3
  78. package/docs/snippets/agent/architecture.mdx +1 -1
  79. package/docs/snippets/agent/bedrock-deployment.mdx +9 -5
  80. package/docs/snippets/agent/runtime-arn.mdx +23 -2
  81. package/docs/snippets/agent/securing-your-agent.mdx +39 -0
  82. package/docs/snippets/api/access-logging.mdx +33 -0
  83. package/docs/snippets/api/cors-configuration-cdk-note.mdx +1 -1
  84. package/docs/snippets/api/cors-configuration-terraform-note.mdx +1 -1
  85. package/docs/snippets/api/type-safe-api-integrations.mdx +33 -2
  86. package/docs/snippets/api/waf-configuration.mdx +3 -3
  87. package/docs/snippets/connection/a2a-infrastructure.mdx +1 -1
  88. package/docs/snippets/connection/dynamodb-local-development.mdx +2 -2
  89. package/docs/snippets/connection/lambda-dynamodb-access.mdx +1 -1
  90. package/docs/snippets/connection/py-dynamodb-local-development.mdx +7 -0
  91. package/docs/snippets/connection/py-lambda-rdb-ssl-requirements.mdx +7 -0
  92. package/docs/snippets/connection/rdb-api-infrastructure.mdx +51 -19
  93. package/docs/snippets/dynamodb/deploying-table.mdx +166 -0
  94. package/docs/snippets/dynamodb/gsi-config.mdx +38 -0
  95. package/docs/snippets/dynamodb/infrastructure.mdx +33 -0
  96. package/docs/snippets/dynamodb/local-dev-start.mdx +13 -0
  97. package/docs/snippets/dynamodb/local-dev-windows.mdx +15 -0
  98. package/docs/snippets/lambda-function/deploying-your-function.mdx +3 -3
  99. package/docs/snippets/mcp/architecture.mdx +1 -1
  100. package/docs/snippets/mcp/bedrock-deployment.mdx +9 -5
  101. package/docs/snippets/mcp/config.mdx +3 -2
  102. package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +7 -5
  103. package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +29 -10
  104. package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +4 -4
  105. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +5 -111
  106. package/docs/snippets/prerequisites.mdx +1 -4
  107. package/docs/snippets/rdb/architecture.mdx +38 -0
  108. package/docs/snippets/rdb/cluster-instances.mdx +31 -0
  109. package/docs/snippets/rdb/deletion-protection.mdx +34 -0
  110. package/docs/snippets/rdb/deploying.mdx +187 -0
  111. package/docs/snippets/rdb/encryption-key-rotation.mdx +30 -0
  112. package/docs/snippets/rdb/engine-version.mdx +63 -0
  113. package/docs/snippets/rdb/infrastructure.mdx +35 -0
  114. package/docs/snippets/rdb/logging-mysql.mdx +5 -0
  115. package/docs/snippets/rdb/logging-postgres.mdx +5 -0
  116. package/docs/snippets/rdb/performance-insights.mdx +34 -0
  117. package/docs/snippets/rdb/rds-proxy.mdx +50 -0
  118. package/docs/snippets/rdb/removal-policy.mdx +57 -0
  119. package/docs/snippets/rdb/serverless-capacity.mdx +32 -0
  120. package/docs/snippets/recommended-prerequisites.mdx +10 -0
  121. package/docs/snippets/required-prerequisites.mdx +1 -4
  122. package/docs/snippets/runtime-config-app-id-note.mdx +8 -0
  123. package/docs/snippets/shared-constructs.mdx +1 -1
  124. package/docs/snippets/trivy-image-scan.mdx +37 -0
  125. package/generators.json +152 -10
  126. package/package.json +1 -1
  127. package/src/agentcore-gateway/agent-connection/schema.json +31 -0
  128. package/src/agentcore-gateway/gateway-connection/schema.json +31 -0
  129. package/src/agentcore-gateway/mcp-connection/schema.json +31 -0
  130. package/src/agentcore-gateway/react-connection/schema.json +31 -0
  131. package/src/agentcore-gateway/schema.json +72 -0
  132. package/src/agentcore-harness/schema.json +53 -0
  133. package/src/connection/schema.json +5 -0
  134. package/src/infra/app/schema.json +5 -0
  135. package/src/init/schema.json +35 -0
  136. package/src/internal/test-matrix/schema.json +21 -0
  137. package/src/license/schema.json +5 -0
  138. package/src/preset/schema.json +16 -5
  139. package/src/py/agent/a2a-connection/schema.json +5 -0
  140. package/src/py/agent/gateway-connection/schema.json +31 -0
  141. package/src/py/agent/mcp-connection/schema.json +5 -0
  142. package/src/py/agent/react-connection/schema.json +5 -0
  143. package/src/py/agent/schema.json +15 -1
  144. package/src/py/api/schema.json +5 -0
  145. package/src/py/dynamodb/agent-connection/schema.json +27 -0
  146. package/src/py/dynamodb/fast-api-connection/schema.json +23 -0
  147. package/src/py/dynamodb/mcp-server-connection/schema.json +27 -0
  148. package/src/py/dynamodb/schema.json +76 -0
  149. package/src/py/fast-api/react/schema.json +5 -0
  150. package/src/py/fast-api/schema.json +6 -0
  151. package/src/py/lambda-function/schema.json +5 -0
  152. package/src/py/mcp-server/schema.json +6 -0
  153. package/src/py/project/schema.json +5 -0
  154. package/src/py/rdb/agent-connection/schema.json +27 -0
  155. package/src/py/rdb/fast-api-connection/schema.json +23 -0
  156. package/src/py/rdb/mcp-server-connection/schema.json +27 -0
  157. package/src/py/rdb/schema.json +78 -0
  158. package/src/smithy/project/schema.json +28 -1
  159. package/src/smithy/react-connection/schema.json +5 -0
  160. package/src/smithy/ts/api/schema.json +6 -0
  161. package/src/terraform/project/schema.json +5 -0
  162. package/src/trpc/backend/schema.json +6 -0
  163. package/src/trpc/react/schema.json +5 -0
  164. package/src/ts/agent/a2a-connection/schema.json +5 -0
  165. package/src/ts/agent/gateway-connection/schema.json +31 -0
  166. package/src/ts/agent/mcp-connection/schema.json +5 -0
  167. package/src/ts/agent/react-connection/schema.json +5 -0
  168. package/src/ts/agent/schema.json +14 -0
  169. package/src/ts/api/schema.json +5 -0
  170. package/src/ts/astro-docs/schema.json +3 -3
  171. package/src/ts/dcr-proxy/schema.json +44 -0
  172. package/src/ts/docs/schema.json +3 -3
  173. package/src/ts/dynamodb/agent-connection/schema.json +5 -0
  174. package/src/ts/dynamodb/mcp-server-connection/schema.json +5 -0
  175. package/src/ts/dynamodb/schema.json +26 -2
  176. package/src/ts/dynamodb/smithy-connection/schema.json +5 -0
  177. package/src/ts/dynamodb/trpc-connection/schema.json +5 -0
  178. package/src/ts/lambda-function/schema.json +5 -0
  179. package/src/ts/lib/schema.json +5 -0
  180. package/src/ts/mcp-server/schema.json +6 -0
  181. package/src/ts/nx-generator/schema.json +5 -0
  182. package/src/ts/nx-migration/schema.json +63 -0
  183. package/src/ts/nx-plugin/schema.json +5 -0
  184. package/src/ts/rdb/agent-connection/schema.json +5 -0
  185. package/src/ts/rdb/mcp-server-connection/schema.json +5 -0
  186. package/src/ts/rdb/schema.json +7 -1
  187. package/src/ts/rdb/smithy-connection/schema.json +5 -0
  188. package/src/ts/rdb/trpc-connection/schema.json +5 -0
  189. package/src/ts/react-website/app/schema.json +12 -6
  190. package/src/ts/react-website/cognito-auth/schema.json +5 -0
  191. package/src/ts/react-website/runtime-config/schema.json +5 -0
  192. package/src/ts/website/app/schema.json +11 -6
  193. package/src/ts/website/auth/schema.json +5 -0
  194. /package/docs/snippets/connection/{lambda-rdb-ssl-requirements.mdx → ts-lambda-rdb-ssl-requirements.mdx} +0 -0
  195. /package/docs/snippets/connection/{mcp-server-rdb-ssl-requirements.mdx → ts-mcp-server-rdb-ssl-requirements.mdx} +0 -0
@@ -0,0 +1,1301 @@
1
+ ---
2
+ title: Set up a monorepo
3
+ description: "A walkthrough of how to build an agentic AI-powered dungeon adventure game using the @aws/nx-plugin."
4
+ ---
5
+
6
+ import { Aside, Code, FileTree, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
7
+ import { Image } from 'astro:assets';
8
+ import Link from '@components/link.astro';
9
+ import Drawer from '@components/drawer.astro';
10
+ import RunGenerator from '@components/run-generator.astro';
11
+ import NxCommands from '@components/nx-commands.astro';
12
+ import InstallCommand from '@components/install-command.astro';
13
+ import CreateNxWorkspaceCommand from '@components/create-nx-workspace-command.astro';
14
+ import PackageManagerShortCommand from '@components/package-manager-short-command.astro';
15
+ import EmbeddedGraph from '@components/embedded-graph.astro';
16
+ import E2EDiff from '@components/e2e-diff.astro';
17
+
18
+ import dungeonAdventureArchitecturePng from '@assets/dungeon-game-architecture.png'
19
+ import baselineWebsitePng from '@assets/baseline-website.png'
20
+ import baselineGamePng from '@assets/baseline-game.png'
21
+ import nxGraphPng from '@assets/nx-graph.png'
22
+ import gameSelectPng from '@assets/game-select.png'
23
+ import gameConversationPng from '@assets/game-conversation.png'
24
+
25
+ ## Task 1: Create a monorepo
26
+
27
+ To create a new monorepo, from within your desired directory, run the following command:
28
+
29
+ <CreateNxWorkspaceCommand workspace="dungeon-adventure" iac="cdk" />
30
+
31
+ :::note[CDK as IaC Provider]
32
+ We use `--iac=cdk` as we will use CDK for infrastructure as code in this tutorial. The Nx Plugin for AWS also supports `terraform`.
33
+ :::
34
+
35
+ This will set up a NX monorepo within the `dungeon-adventure` directory. When you open the directory in VSCode, you will see this file structure:
36
+
37
+ <FileTree>
38
+ - .nx/
39
+ - .vscode/
40
+ - node_modules/
41
+ - packages/ this is where your sub-projects will reside
42
+ - .gitignore
43
+ - biome.json configures Biome for linting and formatting
44
+ - nx.json configures the Nx CLI and monorepo defaults
45
+ - package.json all node dependencies are defined here
46
+ - pnpm-lock.yaml or bun.lock, yarn.lock, package-lock.json depending on package manager
47
+ - pnpm-workspace.yaml if using pnpm
48
+ - README.md
49
+ - tsconfig.base.json all node based sub-projects extend this
50
+ - tsconfig.json
51
+ - aws-nx-plugin.config.mts configuraton for the Nx Plugin for AWS
52
+ </FileTree>
53
+
54
+ <Aside type="tip" title="Commit Often">It is best practice to ensure all your unstaged files are committed in Git before running any generators. This allows you to see what has changed after running your generator via `git diff`.</Aside>
55
+
56
+ ## Task 2: Scaffold the Dungeon Adventure Game
57
+
58
+ With the workspace in place, we scaffold the game's sub-projects — the Game API, Story Agent, Inventory MCP server, game database, and website — along with the connections that wire them together. There are two ways to do this:
59
+
60
+ - **Quick** — copy the commands straight from the diagram below and run them. The fastest way to reach the same starting point.
61
+ - **Step By Step** — expand the section below to run each generator yourself and examine exactly what each one produces.
62
+
63
+ The diagram below _is_ the Dungeon Adventure workspace: every project, component, and connection you'll build in this module. Hit **Copy commands** to take the whole series, then run them from within the `dungeon-adventure` directory you created in Task 1.
64
+
65
+ <EmbeddedGraph preset="dungeon-adventure" workspace="dungeon-adventure" iac="cdk" skipWorkspace />
66
+
67
+ <Drawer title="Step By Step" trigger="Step By Step: run each generator yourself and see what it produces">
68
+
69
+ Rather than copying the commands all at once, you can run each generator individually. This is the best way to understand what each generator adds to your workspace. Run each generator in turn, from within the `dungeon-adventure` directory you created in Task 1.
70
+
71
+ ##### Create a Game API
72
+
73
+ First, let's create our Game API. To do this, create a tRPC API called `GameApi` using these steps:
74
+
75
+ <RunGenerator generator="ts#api" requiredParameters={{ name: "GameApi", framework: "trpc" }} noInteractive />
76
+
77
+ <br />
78
+
79
+ You will see some new files appear in your file tree.
80
+
81
+ <Aside title="Root Dependencies">
82
+ The root `package.json` is now configured with a `type` of `module`, which means ESM is the default module type for all node based sub-projects vended by the `@aws/nx-plugin`.
83
+ For more details on working with TypeScript projects, refer to the <Link path="guides/typescript-project">ts#project generator guide</Link>.
84
+ </Aside>
85
+
86
+ <details>
87
+ <summary>Examine the generated `ts#api` files in detail</summary>
88
+
89
+ Below is a list of all files which have been generated by the `ts#api` generator. We are going to examine some of the key files highlighted in the file tree:
90
+ <FileTree>
91
+ - packages/
92
+ - common/
93
+ - constructs/
94
+ - src/
95
+ - app/ app specific cdk constructs
96
+ - apis/
97
+ - **game-api.ts** cdk construct to create your tRPC API
98
+ - index.ts
99
+ - ...
100
+ - index.ts
101
+ - core/ generic cdk constructs
102
+ - api/
103
+ - rest-api.ts base cdk construct for an API Gateway Rest API
104
+ - trpc-utils.ts utilities for trpc API CDK constructs
105
+ - utils.ts utilities for API constructs
106
+ - index.ts
107
+ - runtime-config.ts
108
+ - index.ts
109
+ - project.json
110
+ - ...
111
+ - game-api/ tRPC API
112
+ - src/
113
+ - client/ vanilla client typically used for ts machine to machine calls
114
+ - index.ts
115
+ - middleware/ powertools instrumentation
116
+ - error.ts
117
+ - index.ts
118
+ - logger.ts
119
+ - metrics.ts
120
+ - tracer.ts
121
+ - schema/ definitions of inputs and outputs for your API
122
+ - index.ts
123
+ - **echo.ts** sample input and output schema
124
+ - z-async-iterable.ts wrapper Zod schema for tRPC subscription output
125
+ - procedures/ specific implementations for your API procedures/routes
126
+ - **echo.ts** sample procedure implementation
127
+ - index.ts
128
+ - init.ts sets up context and middleware
129
+ - handler.ts Lambda handler entrypoint (uses response streaming for REST APIs)
130
+ - local-server.ts used when running the tRPC server locally
131
+ - **router.ts** defines the tRPC router and all procedures
132
+ - project.json
133
+ - ...
134
+ - vitest.workspace.ts
135
+ </FileTree>
136
+
137
+ Let us look at these key files:
138
+
139
+ ```ts {7}
140
+ // packages/game-api/src/router.ts
141
+ import { echo } from './procedures/echo.js';
142
+ import { t } from './init.js';
143
+
144
+ export const router = t.router;
145
+
146
+ export const appRouter = router({
147
+ echo,
148
+ });
149
+
150
+ export type AppRouter = typeof appRouter;
151
+ ```
152
+ The router defines the tRPC router for your API and is the place where you will declare all of your API methods. As you can see above, we have a method called `echo` with it's implementation in the `./procedures/echo.ts` file. The Lambda handler entrypoint is in `handler.ts`, which is configured automatically by the generator.
153
+
154
+ ```ts {7-10}
155
+ // packages/game-api/src/procedures/echo.ts
156
+ import { publicProcedure } from '../init.js';
157
+ import {
158
+ EchoInputSchema,
159
+ EchoOutputSchema,
160
+ } from '../schema/echo.js';
161
+
162
+ export const echo = publicProcedure
163
+ .input(EchoInputSchema)
164
+ .output(EchoOutputSchema)
165
+ .query((opts) => ({ message: opts.input.message }));
166
+ ```
167
+
168
+ This file is the implementation of the `echo` method and as you can see is strongly typed by declaring its input and output data structures.
169
+
170
+ ```ts
171
+ // packages/game-api/src/schema/echo.ts
172
+ import { z } from 'zod';
173
+
174
+ export const EchoInputSchema = z.object({
175
+ message: z.string().max(1024),
176
+ });
177
+
178
+ export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
179
+
180
+ export const EchoOutputSchema = z.object({
181
+ message: z.string().max(1024),
182
+ });
183
+
184
+ export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
185
+ ```
186
+
187
+ All tRPC schema definitions are defined using [Zod](https://zod.dev/) and are exported as typescript types via the `z.TypeOf` syntax.
188
+
189
+ ```ts
190
+ // packages/common/constructs/src/app/apis/game-api.ts
191
+ import { Construct } from 'constructs';
192
+ import * as url from 'url';
193
+ import { Distribution } from 'aws-cdk-lib/aws-cloudfront';
194
+ import {
195
+ Code,
196
+ Runtime,
197
+ Function,
198
+ FunctionProps,
199
+ Tracing,
200
+ } from 'aws-cdk-lib/aws-lambda';
201
+ import {
202
+ AuthorizationType,
203
+ LambdaIntegration,
204
+ ResponseTransferMode,
205
+ } from 'aws-cdk-lib/aws-apigateway';
206
+ import { Aspects, Duration } from 'aws-cdk-lib';
207
+ import {
208
+ PolicyDocument,
209
+ PolicyStatement,
210
+ Effect,
211
+ AnyPrincipal,
212
+ IGrantable,
213
+ Grant,
214
+ } from 'aws-cdk-lib/aws-iam';
215
+ import {
216
+ IntegrationBuilder,
217
+ RestApiIntegration,
218
+ } from '../../core/api/utils.js';
219
+ import { findCloudFrontDomainNames } from '../../core/cloudfront.js';
220
+ import { AddCorsPreflightAspect, RestApi } from '../../core/api/rest-api.js';
221
+ import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';
222
+ import { AppRouter, appRouter } from '@dungeon-adventure/game-api';
223
+
224
+ // String union type for all API operation names
225
+ type Operations = Procedures<AppRouter>;
226
+
227
+ /**
228
+ * Properties for creating a GameApi construct
229
+ *
230
+ * @template TIntegrations - Map of operation names to their integrations
231
+ */
232
+ export interface GameApiProps<
233
+ TIntegrations extends Record<Operations, RestApiIntegration>,
234
+ > {
235
+ /**
236
+ * Map of operation names to their API Gateway integrations
237
+ */
238
+ integrations: TIntegrations;
239
+ }
240
+
241
+ /**
242
+ * A CDK construct that creates and configures an AWS API Gateway REST API
243
+ * specifically for GameApi.
244
+ * @template TIntegrations - Map of operation names to their integrations
245
+ */
246
+ export class GameApi<
247
+ TIntegrations extends Record<Operations, RestApiIntegration>,
248
+ > extends RestApi<Operations, TIntegrations> {
249
+ private allowedOrigins: readonly string[] = ['*'];
250
+
251
+ /**
252
+ * Creates default integrations for all operations, which implement each operation as
253
+ * its own individual lambda function.
254
+ *
255
+ * @param scope - The CDK construct scope
256
+ * @returns An IntegrationBuilder with default lambda integrations
257
+ */
258
+ public static defaultIntegrations = (scope: Construct) => {
259
+ return IntegrationBuilder.rest({
260
+ operations: routerToOperations(appRouter),
261
+ defaultIntegrationOptions: {
262
+ runtime: Runtime.NODEJS_LATEST,
263
+ handler: 'index.handler',
264
+ code: Code.fromAsset(
265
+ url.fileURLToPath(
266
+ new URL(
267
+ '../../../../../../dist/packages/game-api/bundle',
268
+ import.meta.url,
269
+ ),
270
+ ),
271
+ ),
272
+ timeout: Duration.seconds(30),
273
+ tracing: Tracing.ACTIVE,
274
+ } as FunctionProps,
275
+ buildDefaultIntegration: (op, props: FunctionProps) => {
276
+ const handler = new Function(scope, `GameApi${op}Handler`, props);
277
+ return {
278
+ handler,
279
+ integration: new LambdaIntegration(handler, {
280
+ responseTransferMode: ResponseTransferMode.STREAM,
281
+ }),
282
+ };
283
+ },
284
+ });
285
+ };
286
+
287
+ constructor(
288
+ scope: Construct,
289
+ id: string,
290
+ props: GameApiProps<TIntegrations>,
291
+ ) {
292
+ super(scope, id, {
293
+ apiName: 'GameApi',
294
+ defaultMethodOptions: {
295
+ authorizationType: AuthorizationType.IAM,
296
+ },
297
+ deployOptions: {
298
+ tracingEnabled: true,
299
+ },
300
+ policy: new PolicyDocument({
301
+ statements: [
302
+ // Open up OPTIONS to allow browsers to make unauthenticated preflight requests
303
+ new PolicyStatement({
304
+ effect: Effect.ALLOW,
305
+ principals: [new AnyPrincipal()],
306
+ actions: ['execute-api:Invoke'],
307
+ resources: ['execute-api:/*/OPTIONS/*'],
308
+ }),
309
+ ],
310
+ }),
311
+ operations: routerToOperations(appRouter),
312
+ ...props,
313
+ });
314
+ Aspects.of(this).add(new AddCorsPreflightAspect(() => this.allowedOrigins));
315
+ }
316
+
317
+ /**
318
+ * Restricts CORS to the provided origins
319
+ *
320
+ * Configures the CloudFront distribution domains or origin strings
321
+ * as the only permitted CORS origins in API Gateway preflight responses and the AWS
322
+ * Lambda integrations. Any custom domain names (aliases) configured on a CloudFront
323
+ * distribution are included automatically alongside its default `*.cloudfront.net`
324
+ * domain.
325
+ *
326
+ * @param origins - The origin strings, CloudFront distributions, or objects containing a CloudFront distribution to grant CORS from
327
+ */
328
+ public restrictCorsTo(
329
+ ...origins: (string | Distribution | { cloudFrontDistribution: Distribution })[]
330
+ ) {
331
+ const allowedOrigins = origins.flatMap((origin) =>
332
+ typeof origin === 'string'
333
+ ? [origin]
334
+ : findCloudFrontDomainNames(
335
+ 'cloudFrontDistribution' in origin
336
+ ? origin.cloudFrontDistribution
337
+ : origin,
338
+ ).map((domain) => `https://${domain}`),
339
+ );
340
+
341
+ this.allowedOrigins = allowedOrigins;
342
+
343
+ // Set ALLOWED_ORIGINS environment variable for all Lambda integrations
344
+ Object.values(this.integrations).forEach((integration) => {
345
+ if ('handler' in integration && integration.handler instanceof Function) {
346
+ integration.handler.addEnvironment(
347
+ 'ALLOWED_ORIGINS',
348
+ allowedOrigins.join(','),
349
+ );
350
+ }
351
+ });
352
+ }
353
+
354
+ /**
355
+ * Grants IAM permissions to invoke any method on this API.
356
+ *
357
+ * @param grantee - The IAM principal to grant permissions to
358
+ */
359
+ public grantInvokeAccess(grantee: IGrantable) {
360
+ // Here we grant grantee permission to call the api.
361
+ // Machine to machine fine-grained access can be defined here using more specific principals (eg roles or
362
+ // users) and resources (eg which api paths may be invoked by which principal) if required.
363
+ this.api.addToResourcePolicy(
364
+ new PolicyStatement({
365
+ effect: Effect.ALLOW,
366
+ principals: [grantee.grantPrincipal],
367
+ actions: ['execute-api:Invoke'],
368
+ resources: ['execute-api:/*'],
369
+ }),
370
+ );
371
+
372
+ Grant.addToPrincipal({
373
+ grantee,
374
+ actions: ['execute-api:Invoke'],
375
+ resourceArns: [this.api.arnForExecuteApi('*', '/*', '*')],
376
+ });
377
+ }
378
+ }
379
+ ```
380
+
381
+ This is the CDK construct that defines our `GameApi`. It provides a `defaultIntegrations` method which automatically creates a Lambda function for each procedure in our tRPC API, pointing to the bundled API implementation. This means that at `cdk synth` time, bundling does not occur (opposed to using [NodeJsFunction](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html)) as we have already bundled it as part of the backend project's build target.
382
+
383
+ </details>
384
+
385
+ ##### Create the Story Agent
386
+
387
+ Now let's create our Story Agent.
388
+
389
+ ###### Story agent: Python project
390
+
391
+ To create a Python project:
392
+
393
+ <RunGenerator generator="py#project" requiredParameters={{name:"story"}} noInteractive />
394
+
395
+ You will see some new files appear in your file tree.
396
+ <details>
397
+ <summary>Examine the generated `py#project` files in detail</summary>
398
+
399
+ The `py#project` generates these files:
400
+
401
+ <FileTree>
402
+ - .venv/ single virtual env for monorepo
403
+ - packages/
404
+ - story/
405
+ - dungeon_adventure_story/ python module
406
+ - tests/
407
+ - .python-version
408
+ - pyproject.toml
409
+ - project.json
410
+ - .python-version pinned uv python version
411
+ - pyproject.toml
412
+ - uv.lock
413
+ </FileTree>
414
+
415
+ This has configured a Python project and [UV Workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/) with shared virtual environment.
416
+
417
+ </details>
418
+
419
+ ###### Story agent
420
+
421
+ To add a Strands agent to the project with the `py#agent` generator:
422
+
423
+ <RunGenerator generator="py#agent" requiredParameters={{project:"story", auth:"cognito", protocol:"ag-ui"}} noInteractive />
424
+
425
+ :::note[AG-UI protocol]
426
+ We choose `--protocol=ag-ui` so the agent speaks the [Agent-User Interaction protocol](https://docs.copilotkit.ai/aws-strands/protocol) — this lets our React website talk to it directly via [CopilotKit](https://docs.copilotkit.ai/), with streaming, tool calls, and conversation history handled by the protocol instead of a hand-rolled HTTP client.
427
+ :::
428
+
429
+ You will see some new files appear in your file tree.
430
+ <details>
431
+ <summary>Examine the generated `py#agent` files in detail</summary>
432
+
433
+ The `py#agent` generates these files:
434
+
435
+ <FileTree>
436
+ - packages/
437
+ - story/
438
+ - dungeon_adventure_story/ python module
439
+ - agent/
440
+ - main.py entrypoint for your agent in Bedrock AgentCore Runtime
441
+ - agent.py defines an example agent and tools
442
+ - Dockerfile defines the docker image for deployment to AgentCore Runtime
443
+ - common/constructs/
444
+ - src
445
+ - app/agents/story-agent/
446
+ - story-agent.ts construct for deploying your Story agent to AgentCore Runtime
447
+ </FileTree>
448
+
449
+ Let's take a look at some of the files in detail:
450
+
451
+ ```python
452
+ # agent/agent.py
453
+ from contextlib import contextmanager
454
+
455
+ from strands import Agent, tool
456
+ from strands_tools import current_time
457
+
458
+
459
+ @tool
460
+ def subtract(a: int, b: int) -> int:
461
+ return a - b
462
+
463
+
464
+ @contextmanager
465
+ def get_agent():
466
+ yield Agent(
467
+ name="StoryAgent",
468
+ description="StoryAgent Agent",
469
+ system_prompt="""
470
+ You are a mathematical wizard.
471
+ Use your tools for mathematical tasks.
472
+ Refer to tools as your 'spellbook'.
473
+ """,
474
+ tools=[subtract, current_time],
475
+ )
476
+ ```
477
+
478
+ This creates an example Strands agent and defines a subtraction tool.
479
+
480
+ ```python
481
+ # agent/main.py
482
+ import logging
483
+ import uuid
484
+ from contextlib import asynccontextmanager
485
+
486
+ from ag_ui.core import EventType, RunAgentInput, RunErrorEvent
487
+ from ag_ui.encoder import EventEncoder
488
+ from ag_ui_strands import StrandsAgent
489
+ from dungeon_adventure_agent_connection import get_current_session_id, session_id_context
490
+ from fastapi import FastAPI, Request
491
+ from fastapi.middleware.cors import CORSMiddleware
492
+ from fastapi.responses import StreamingResponse
493
+ from starlette.middleware.base import BaseHTTPMiddleware
494
+
495
+ from .agent import get_agent
496
+
497
+ logging.basicConfig(level=logging.INFO)
498
+
499
+ SESSION_ID_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id"
500
+
501
+
502
+ @asynccontextmanager
503
+ async def lifespan(app: FastAPI):
504
+ with get_agent() as agent:
505
+ app.state.agui_agent = StrandsAgent(
506
+ agent=agent,
507
+ name="StoryAgent",
508
+ description="A Strands Agent exposed via the AG-UI protocol.",
509
+ )
510
+ yield
511
+
512
+
513
+ class _SessionIdMiddleware(BaseHTTPMiddleware):
514
+ """Bind the session ID for this request so downstream MCP / A2A clients forward it on outbound calls."""
515
+
516
+ async def dispatch(self, request: Request, call_next):
517
+ session_id = request.headers.get(SESSION_ID_HEADER) or str(uuid.uuid4())
518
+ with session_id_context(session_id):
519
+ return await call_next(request)
520
+
521
+
522
+ app = FastAPI(title="AWS Strands - StoryAgent", lifespan=lifespan)
523
+ app.add_middleware(
524
+ CORSMiddleware,
525
+ allow_origins=["*"],
526
+ allow_credentials=True,
527
+ allow_methods=["*"],
528
+ allow_headers=["*"],
529
+ )
530
+ app.add_middleware(_SessionIdMiddleware)
531
+
532
+
533
+ @app.post("/invocations")
534
+ async def invocations(request: Request):
535
+ # Validate the body manually since AgentCore may omit Content-Type.
536
+ encoder = EventEncoder(accept=request.headers.get("accept") or "")
537
+ raw = await request.body()
538
+ try:
539
+ input_data = RunAgentInput.model_validate_json(raw)
540
+ except Exception as exc:
541
+ message = f"Invalid RunAgentInput: {str(exc)[:200]}"
542
+
543
+ async def _bad():
544
+ yield encoder.encode(RunErrorEvent(type=EventType.RUN_ERROR, message=message, code="BAD_REQUEST"))
545
+
546
+ return StreamingResponse(_bad(), media_type=encoder.get_content_type())
547
+
548
+ session_id = request.headers.get(SESSION_ID_HEADER) or get_current_session_id()
549
+
550
+ async def event_generator():
551
+ # Re-bind the session: the streaming body runs outside the middleware.
552
+ with session_id_context(session_id or str(uuid.uuid4())):
553
+ async for event in request.app.state.agui_agent.run(input_data):
554
+ try:
555
+ yield encoder.encode(event)
556
+ except Exception as e:
557
+ error_event = RunErrorEvent(
558
+ type=EventType.RUN_ERROR,
559
+ message=f"Encoding error: {e}",
560
+ code="ENCODING_ERROR",
561
+ )
562
+ yield encoder.encode(error_event)
563
+ break
564
+
565
+ return StreamingResponse(event_generator(), media_type=encoder.get_content_type())
566
+
567
+
568
+ @app.get("/ping")
569
+ async def ping():
570
+ return {"status": "healthy"}
571
+ ```
572
+
573
+ This is the entrypoint for the agent. Because we selected `--protocol=ag-ui`, the generator wraps our Strands `Agent` with `StrandsAgent` from [`ag_ui_strands`](https://docs.copilotkit.ai/aws-strands/integration) and mounts it on a FastAPI app that speaks the [AG-UI protocol](https://docs.copilotkit.ai/aws-strands/protocol) — this is what CopilotKit will talk to from the React website. The agent is built inside a `lifespan` handler — not at import time — so container startup, not module import, owns construction, and each AgentCore session gets its own container. The `_SessionIdMiddleware` binds the inbound AgentCore runtime session ID onto a `ContextVar` so any downstream MCP/A2A client we wire up later (e.g. the Inventory MCP server in <Link path="get_started/tutorials/dungeon-game/2">Module 2</Link>) automatically forwards it on its outbound calls. In <Link path="get_started/tutorials/dungeon-game/3">Module 3</Link> we'll also add a `session_manager_provider` so each thread id gets its own `S3SessionManager` and conversation history persists across turns.
574
+
575
+ ```ts
576
+ // common/constructs/src/app/agents/story-agent.ts
577
+ import { Lazy, Names } from 'aws-cdk-lib';
578
+ import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
579
+ import { Construct } from 'constructs';
580
+ import { execSync } from 'child_process';
581
+ import * as path from 'path';
582
+ import * as url from 'url';
583
+ import {
584
+ AgentRuntimeArtifact,
585
+ ProtocolType,
586
+ Runtime,
587
+ RuntimeProps,
588
+ } from 'aws-cdk-lib/aws-bedrockagentcore';
589
+ import { IGrantable, IPrincipal } from 'aws-cdk-lib/aws-iam';
590
+
591
+ export type StoryAgentProps = Omit<
592
+ RuntimeProps,
593
+ 'runtimeName' | 'protocolConfiguration' | 'agentRuntimeArtifact'
594
+ >;
595
+
596
+ export class StoryAgent extends Construct implements IGrantable {
597
+ public readonly dockerImage: AgentRuntimeArtifact;
598
+ public readonly agentCoreRuntime: Runtime;
599
+
600
+ constructor(scope: Construct, id: string, props?: StoryAgentProps) {
601
+ super(scope, id);
602
+
603
+ this.dockerImage = AgentRuntimeArtifact.fromAsset(
604
+ path.dirname(url.fileURLToPath(new URL(import.meta.url))),
605
+ {
606
+ platform: Platform.LINUX_ARM64,
607
+ extraHash: execSync(
608
+ `docker inspect dungeon-adventure-story-agent:latest --format '{{.Id}}'`,
609
+ { encoding: 'utf-8' },
610
+ ).trim(),
611
+ },
612
+ );
613
+
614
+ this.agentCoreRuntime = new Runtime(this, 'StoryAgent', {
615
+ runtimeName: Lazy.string({
616
+ produce: () =>
617
+ Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }),
618
+ }),
619
+ protocolConfiguration: ProtocolType.HTTP,
620
+ agentRuntimeArtifact: this.dockerImage,
621
+ ...props,
622
+ });
623
+ }
624
+
625
+ public get grantPrincipal(): IPrincipal {
626
+ return this.agentCoreRuntime.grantPrincipal;
627
+ }
628
+ }
629
+ ```
630
+
631
+ This configures a CDK `AgentRuntimeArtifact` which uploads your agent Docker image to ECR, and hosts it using AgentCore Runtime.
632
+
633
+ You may notice an extra `Dockerfile`, that references the Docker image from the `story` project, allowing us to co-locate the Dockerfile and agent source code.
634
+
635
+ </details>
636
+
637
+ ##### Set up the Inventory tools
638
+
639
+ ###### Inventory: TypeScript project
640
+
641
+ Let us create an MCP server to provide tools for our Story Agent to manage a player's inventory.
642
+
643
+ First, we create a TypeScript project:
644
+
645
+ <RunGenerator generator="ts#project" requiredParameters={{name:"inventory"}} noInteractive />
646
+
647
+ This will create an empty TypeScript project.
648
+
649
+ <details>
650
+ <summary>Examine the generated `ts#project` files in detail</summary>
651
+
652
+ The `ts#project` generator generates these files.
653
+
654
+ <FileTree>
655
+ - packages/
656
+ - inventory/
657
+ - src/
658
+ - index.ts entry point with example function
659
+ - project.json project configuration
660
+ - vitest.config.mts test configuration
661
+ - tsconfig.json base typescript configuration for the project
662
+ - tsconfig.lib.json typescript configuration for the project targeted for compilation and bundling
663
+ - tsconfig.spec.json typescript configuration for tests
664
+ - tsconfig.base.json updated to configure an alias for other projects to reference this
665
+ </FileTree>
666
+
667
+ </details>
668
+
669
+ ###### Inventory: MCP server
670
+
671
+ Next, we'll add an MCP server to our TypeScript project:
672
+
673
+ <RunGenerator generator="ts#mcp-server" requiredParameters={{project:"inventory"}} noInteractive />
674
+
675
+ This will add an MCP server.
676
+ <details>
677
+ <summary>Examine the generated `ts#mcp-server` files in detail</summary>
678
+
679
+ The `ts#mcp-server` generator generates these files.
680
+
681
+ <FileTree>
682
+ - packages/
683
+ - inventory/
684
+ - src/mcp-server/
685
+ - index.ts barrel export
686
+ - server.ts creates the MCP server
687
+ - tools/
688
+ - divide.ts example tool
689
+ - resources/
690
+ - sample-guidance.ts example resource
691
+ - stdio.ts entry point for MCP with STDIO transport
692
+ - http.ts entry point for MCP with Streamable HTTP transport
693
+ - Dockerfile builds the image for AgentCore Runtime
694
+ - rolldown.config.ts configuration for bundling the MCP server for deployment to AgentCore
695
+ - common/constructs/
696
+ - src
697
+ - app/mcp-servers/inventory-mcp-server/
698
+ - inventory-mcp-server.ts construct for deploying your inventory MCP server to AgentCore Runtime
699
+ </FileTree>
700
+
701
+ </details>
702
+
703
+ ##### Create the game database
704
+
705
+ Our game state — saved games and each player's inventory — lives in [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). Create a DynamoDB project called `DungeonDb` with the `ts#dynamodb` generator:
706
+
707
+ <RunGenerator generator="ts#dynamodb" requiredParameters={{name:"DungeonDb"}} noInteractive />
708
+
709
+ :::tip[Local-first development]
710
+ The `ts#dynamodb` generator vends a `dev` target that runs [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html) in a container. Combined with the `connection` generator (which we run below), this means **the entire game runs on your machine without a deployment** until the end of the tutorial.
711
+ :::
712
+
713
+ You will see some new files appear in your file tree.
714
+ <details>
715
+ <summary>Examine the generated `ts#dynamodb` files in detail</summary>
716
+
717
+ The `ts#dynamodb` generator generates these files.
718
+
719
+ <FileTree>
720
+ - packages/
721
+ - dungeon-db/
722
+ - config.json DynamoDB configuration including port, table name, container settings and Global Secondary Indexes
723
+ - src/
724
+ - index.ts entry point and exports
725
+ - client.ts DynamoDB client singleton and table name resolution
726
+ - entities/
727
+ - example.ts example ElectroDB entity (we will replace this)
728
+ - index.ts entity exports
729
+ - project.json adds the `dev` and `pull-image` targets
730
+ - common/
731
+ - scripts/
732
+ - src/
733
+ - dynamodb/
734
+ - create-local-table.ts creates the table in DynamoDB Local
735
+ - pull-image.ts pulls the DynamoDB Local image
736
+ - start-container.ts starts the DynamoDB Local container
737
+ - constructs/
738
+ - src/
739
+ - app/dynamodb/
740
+ - dungeon-db.ts construct for provisioning your table
741
+ - core/
742
+ - dynamodb.ts generic DynamoDB table construct
743
+ </FileTree>
744
+
745
+ The generated `src/client.ts` exports `getDynamoDBClient()` and `resolveTableName()`. When `LOCAL_DEV=true` (set automatically by the `dev` targets) these connect to DynamoDB Local; otherwise they connect to AWS and resolve the deployed table name from <Link path="guides/runtime-config">Runtime Configuration</Link>. We'll model our `Game` and `Inventory` entities in this project in <Link path="get_started/tutorials/dungeon-game/2">Module 2</Link>.
746
+
747
+ For more details, refer to the <Link path="guides/ts-dynamodb">ts#dynamodb generator guide</Link>.
748
+
749
+ </details>
750
+
751
+ ##### Create the User Interface (UI)
752
+
753
+ Next, we will create the UI which will allow you to interact with the game.
754
+
755
+ ###### Game UI: Website
756
+
757
+ To create the UI, create a website called `GameUI` using these steps:
758
+
759
+ <RunGenerator generator="ts#website" requiredParameters={{name:"GameUI", ux:"shadcn"}} noInteractive />
760
+
761
+ :::note[Shadcn UI]
762
+ We select `--ux=shadcn` so the generated website uses [shadcn/ui](https://ui.shadcn.com/) components styled with Tailwind — all our route code uses shadcn primitives like `Card`, `Button`, and `Input`, and the `connection` generator later wires a matching shadcn-themed [CopilotKit](https://docs.copilotkit.ai/) chat surface.
763
+ :::
764
+
765
+ You will see some new files appear in your file tree.
766
+
767
+ <details>
768
+ <summary>Examine the generated `ts#website` files in detail</summary>
769
+
770
+ The `ts#website` generates these files. Let us examine some of the key files highlighted in the file tree:
771
+
772
+ <FileTree>
773
+ - packages/
774
+ - common/
775
+ - constructs/
776
+ - src/
777
+ - app/ app specific cdk constructs
778
+ - static-websites/
779
+ - **game-ui.ts** cdk construct to create your Game UI
780
+ - core/
781
+ - static-website.ts generic static website construct
782
+ - game-ui/
783
+ - public/
784
+ - src/
785
+ - components/
786
+ - AppLayout/
787
+ - index.tsx overall page layout using shadcn `SidebarProvider` + header
788
+ - app-sidebar.tsx default shadcn sidebar with nav items
789
+ - alert.tsx, spinner.tsx shadcn-wrapped feedback primitives
790
+ - routes/ @tanstack/react-router file based routes
791
+ - **index.tsx** root '/' page
792
+ - __root.tsx all pages use this component as a base
793
+ - config.ts
794
+ - **main.tsx** React entrypoint
795
+ - routeTree.gen.ts this is automatically updated by @tanstack/react-router
796
+ - styles.css imports shared shadcn globals (Tailwind v4)
797
+ - index.html
798
+ - project.json
799
+ - vite.config.mts
800
+ - ...
801
+ - common/
802
+ - shadcn/ shared shadcn/ui library (theme tokens, `Button`, `Card`, `Input`, `Sidebar`, …) imported by every `ux=shadcn` website
803
+ - src/components/ui/*
804
+ - src/styles/globals.css Tailwind + shadcn design tokens
805
+ - ...
806
+ </FileTree>
807
+
808
+ ```ts
809
+ // packages/common/constructs/src/app/static-websites/game-ui.ts
810
+ import * as url from 'url';
811
+ import { Construct } from 'constructs';
812
+ import { StaticWebsite } from '../../core/index.js';
813
+
814
+ export class GameUI extends StaticWebsite {
815
+ constructor(scope: Construct, id: string) {
816
+ super(scope, id, {
817
+ websiteName: 'GameUI',
818
+ websiteFilePath: url.fileURLToPath(
819
+ new URL(
820
+ '../../../../../../dist/packages/game-ui/bundle',
821
+ import.meta.url,
822
+ ),
823
+ ),
824
+ });
825
+ }
826
+ }
827
+ ```
828
+
829
+ This is the CDK construct that defines our GameUI. It has already configured the file path to the generated bundle for our Vite based UI. This means that at `build` time, bundling occurs within the game-ui project's build target and the output is used here.
830
+
831
+ ```tsx
832
+ // packages/game-ui/src/main.tsx
833
+ import React from 'react';
834
+ import { createRoot } from 'react-dom/client';
835
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
836
+ import { routeTree } from './routeTree.gen';
837
+ import './styles.css';
838
+
839
+ export type RouterProviderContext = {};
840
+
841
+ const router = createRouter({ routeTree, context: {} });
842
+
843
+ declare module '@tanstack/react-router' {
844
+ interface Register {
845
+ router: typeof router;
846
+ }
847
+ }
848
+
849
+ const App = () => <RouterProvider router={router} context={{}} />;
850
+
851
+ const root = document.getElementById('root');
852
+ root &&
853
+ createRoot(root).render(
854
+ <React.StrictMode>
855
+ <App />
856
+ </React.StrictMode>,
857
+ );
858
+ ```
859
+
860
+ This is the entry point where React is mounted. Styling comes from Tailwind v4 tokens imported via `styles.css`. `@tanstack/react-router` is configured in [file-based routing](https://tanstack.com/router/v1/docs/framework/react/routing/file-based-routing) mode: as long as the dev server is running, any file you create under `routes/` is picked up automatically and the route tree is regenerated. Later generators (auth, connection) will AST-patch this file to wrap `<App />` in additional providers.
861
+
862
+ ```tsx
863
+ // packages/game-ui/src/routes/index.tsx
864
+ import { createFileRoute } from '@tanstack/react-router';
865
+
866
+ export const Route = createFileRoute('/')({
867
+ component: RouteComponent,
868
+ });
869
+
870
+ function RouteComponent() {
871
+ return (
872
+ <div className="text-center">
873
+ <header>
874
+ <h1>Welcome</h1>
875
+ <p>Welcome to your new React website!</p>
876
+ </header>
877
+ </div>
878
+ );
879
+ }
880
+ ```
881
+
882
+ A component will be rendered when navigating to the `/` route. `@tanstack/react-router` will manage the `Route` for you whenever you create/move this file (as long as the dev server is running).
883
+
884
+ </details>
885
+
886
+ ###### Game UI: Auth
887
+
888
+ Let us configure our Game UI to require authenticated access via Amazon Cognito using these steps:
889
+
890
+ <RunGenerator generator="ts#website#auth" requiredParameters={{cognitoDomain:"game-ui", project:"@dungeon-adventure/game-ui", allowSignup:true}} noInteractive />
891
+
892
+ You will see some new files appear/change in your file tree.
893
+
894
+ <details>
895
+ <summary>Examine the generated `ts#website#auth` files in detail</summary>
896
+
897
+ The `ts#website#auth` generator updates/generates these files. Let us examine some of the key files highlighted in the file tree:
898
+
899
+ <FileTree>
900
+ - packages/
901
+ - common/
902
+ - constructs/
903
+ - src/
904
+ - core/
905
+ - user-identity.ts cdk construct for creating user/identity pools
906
+ - game-ui/
907
+ - src/
908
+ - components/
909
+ - AppLayout/
910
+ - index.tsx adds the logged in user/logout to the header
911
+ - CognitoAuth/
912
+ - index.tsx manages logging into Cognito
913
+ - RuntimeConfig/
914
+ - index.tsx fetches the `runtime-config.json` and provides it to children via context
915
+ - hooks/
916
+ - useRuntimeConfig.tsx
917
+ - **main.tsx** Updated to add Cognito
918
+ </FileTree>
919
+
920
+ ```diff lang="tsx"
921
+ // packages/game-ui/src/main.tsx
922
+ +import { useAuth } from 'react-oidc-context';
923
+ +import CognitoAuth from './components/CognitoAuth';
924
+ +import { useRuntimeConfig } from './hooks/useRuntimeConfig';
925
+ +import RuntimeConfigProvider from './components/RuntimeConfig';
926
+ import React from 'react';
927
+ import { createRoot } from 'react-dom/client';
928
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
929
+ import { routeTree } from './routeTree.gen';
930
+ import './styles.css';
931
+ -export type RouterProviderContext = {};
932
+ +export type RouterProviderContext = {
933
+ + runtimeConfig?: ReturnType<typeof useRuntimeConfig>;
934
+ + auth?: ReturnType<typeof useAuth>;
935
+ +};
936
+ -const router = createRouter({ routeTree, context: {} });
937
+ +const router = createRouter({
938
+ + routeTree,
939
+ + context: { runtimeConfig: undefined, auth: undefined },
940
+ +});
941
+ // Register the router instance for type safety
942
+ declare module '@tanstack/react-router' {
943
+ interface Register {
944
+ router: typeof router;
945
+ }
946
+ }
947
+ -const App = () => <RouterProvider router={router} context={{}} />;
948
+ +const App = () => {
949
+ + const auth = useAuth();
950
+ + const runtimeConfig = useRuntimeConfig();
951
+ + return <RouterProvider router={router} context={{ runtimeConfig, auth }} />;
952
+ +};
953
+ const root = document.getElementById('root');
954
+ root &&
955
+ createRoot(root).render(
956
+ <React.StrictMode>
957
+ + <RuntimeConfigProvider>
958
+ + <CognitoAuth>
959
+ <App />
960
+ + </CognitoAuth>
961
+ + </RuntimeConfigProvider>
962
+ </React.StrictMode>,
963
+ );
964
+ ```
965
+
966
+ The `RuntimeConfigProvider` and `CognitoAuth` components have been added to the `main.tsx` file via an AST transform. This allows the `CognitoAuth` component to authenticate with Amazon Cognito by fetching the `runtime-config.json` which contains the required cognito connection configuration in order to make the backend calls to the correct destination.
967
+
968
+ </details>
969
+
970
+ ###### Game UI: Connect to Game API
971
+
972
+ Let us configure our Game UI to connect to our previously created Game API.
973
+
974
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/game-ui", targetProject:"@dungeon-adventure/game-api"}} noInteractive />
975
+
976
+ You will see some new files have appear/change in your file tree.
977
+
978
+ <details>
979
+ <summary>Examine the UI → tRPC connection files</summary>
980
+
981
+ The `connection` generator generates/updates these files. Let us examine some of the key files highlighted in the file tree:
982
+
983
+ <FileTree>
984
+ - packages/
985
+ - game-ui/
986
+ - src/
987
+ - components/
988
+ - GameApiClientProvider.tsx sets up the GameAPI client
989
+ - hooks/
990
+ - **useGameApi.tsx** hooks to call the GameApi
991
+ - **main.tsx** injects the trpc client providers
992
+ - package.json
993
+
994
+ </FileTree>
995
+
996
+ ```tsx
997
+ // packages/game-ui/src/hooks/useGameApi.tsx
998
+ import { useContext } from 'react';
999
+ import { GameApiTRPCContext } from '../components/GameApiClientProvider';
1000
+
1001
+ export const useGameApi = () => {
1002
+ const container = useContext(GameApiTRPCContext);
1003
+ if (!container) {
1004
+ throw new Error('useGameApi must be used within GameApiClientProvider');
1005
+ }
1006
+ return container.optionsProxy;
1007
+ };
1008
+
1009
+ export const useGameApiClient = () => {
1010
+ const container = useContext(GameApiTRPCContext);
1011
+ if (!container) {
1012
+ throw new Error(
1013
+ 'useGameApiClient must be used within GameApiClientProvider',
1014
+ );
1015
+ }
1016
+ return container.client;
1017
+ };
1018
+ ```
1019
+
1020
+ This hook provides access to the tRPC client for calling the GameApi. For examples on how to call tRPC APIs, refer to the <Link path="guides/connection/react-trpc#using-the-generated-code">using the tRPC hook guide</Link>.
1021
+
1022
+ <Aside title="Type-Safe Hooks">
1023
+ Thanks to tRPC's [Typescript inference](https://trpc.io/docs/concepts), `useGameApi` doesn't need a build step for backend changes to reach the frontend — edits to the router or any procedure are picked up by the type checker instantly.
1024
+ </Aside>
1025
+
1026
+ ```diff lang="tsx"
1027
+ // packages/game-ui/src/main.tsx
1028
+ +import GameApiClientProvider from './components/GameApiClientProvider';
1029
+ +import QueryClientProvider from './components/QueryClientProvider';
1030
+ import { useAuth } from 'react-oidc-context';
1031
+ import CognitoAuth from './components/CognitoAuth';
1032
+ import { useRuntimeConfig } from './hooks/useRuntimeConfig';
1033
+ import RuntimeConfigProvider from './components/RuntimeConfig';
1034
+ import React from 'react';
1035
+ import { createRoot } from 'react-dom/client';
1036
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
1037
+ import { routeTree } from './routeTree.gen';
1038
+ import './styles.css';
1039
+ ...
1040
+ const root = document.getElementById('root');
1041
+ root &&
1042
+ createRoot(root).render(
1043
+ <React.StrictMode>
1044
+ <RuntimeConfigProvider>
1045
+ <CognitoAuth>
1046
+ + <QueryClientProvider>
1047
+ + <GameApiClientProvider>
1048
+ <App />
1049
+ + </GameApiClientProvider>
1050
+ + </QueryClientProvider>
1051
+ </CognitoAuth>
1052
+ </RuntimeConfigProvider>
1053
+ </React.StrictMode>,
1054
+ );
1055
+ ```
1056
+
1057
+ The `main.tsx` file has been updated via an AST transform to inject the tRPC providers.
1058
+
1059
+ </details>
1060
+
1061
+ ###### Story Agent: Connect to Inventory MCP Server
1062
+
1063
+ Let us connect our Story Agent to the Inventory MCP server so the agent can discover and invoke the MCP server's tools.
1064
+
1065
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"story", targetProject:"inventory"}} noInteractive />
1066
+
1067
+ <details>
1068
+ <summary>Examine the Story Agent → Inventory MCP connection files</summary>
1069
+
1070
+ The `connection` generator generates/updates these files:
1071
+
1072
+ <FileTree>
1073
+ - packages/
1074
+ - common/
1075
+ - agent\_connection/
1076
+ - dungeon\_adventure\_agent\_connection/
1077
+ - core/
1078
+ - **agentcore\_endpoints.py** Framework-agnostic ARN/URL resolution
1079
+ - **agentcore\_mcp\_transport.py** Framework-agnostic MCP transport
1080
+ - **agentcore\_mcp\_client\_strands.py** Strands MCP client wrapping the transport
1081
+ - auth/ Framework-agnostic SigV4 / session-forwarding `httpx.Auth`
1082
+ - app/
1083
+ - **inventory\_mcp\_server\_client\_strands.py** Strands client for connecting to the Inventory MCP server
1084
+ - **\_\_init\_\_.py** Re-exports per-connection clients
1085
+ - story/
1086
+ - dungeon\_adventure\_story/agent/
1087
+ - **agent.py** Modified to import and use the MCP client
1088
+
1089
+ </FileTree>
1090
+
1091
+ The generator:
1092
+ - Creates a shared `agent_connection` Python project (if it doesn't already exist) with the core `AgentCoreMCPClientStrands`
1093
+ - Generates an `InventoryMcpServerClientStrands` class that handles connecting to the MCP server both locally (direct HTTP) and when deployed (via AgentCore with IAM auth)
1094
+ - Transforms `agent.py` to import the client, create an instance, and wire the MCP server's tools into the agent
1095
+ - Adds the `agent_connection` project as a workspace dependency of the story project
1096
+ - Updates the `dev` target to automatically start the MCP server when running locally
1097
+
1098
+ For more details, refer to the <Link path="guides/connection/py-agent-mcp">Python Agent to MCP connection guide</Link>.
1099
+ </details>
1100
+
1101
+ ###### Game UI: Connect to Story Agent
1102
+
1103
+ Let us connect our Game UI to the Story Agent. Since the agent speaks AG-UI, the `connection` generator wires up [CopilotKit](https://docs.copilotkit.ai/): a themed chat component and an `@ag-ui/client` `HttpAgent` ready to render.
1104
+
1105
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/game-ui", targetProject:"story"}} noInteractive />
1106
+
1107
+ <details>
1108
+ <summary>Examine the UI → Story Agent connection files</summary>
1109
+
1110
+ The `connection` generator generates/updates these files:
1111
+
1112
+ <FileTree>
1113
+ - packages/
1114
+ - game-ui/
1115
+ - src/
1116
+ - components/
1117
+ - **AguiProvider.tsx** `CopilotKitProvider` with every connected AG-UI agent registered
1118
+ - copilot/
1119
+ - **index.tsx** Shadcn-themed `CopilotChat` / `CopilotSidebar` / `CopilotPopup`
1120
+ - ShadcnAssistantMessage.tsx, ShadcnUserMessage.tsx, ShadcnChatInput.tsx, ShadcnCursor.tsx, copilot.css
1121
+ - hooks/
1122
+ - **useAguiStoryAgent.tsx** Builds an `HttpAgent`, injects the Cognito bearer token, and pads `threadId` to AgentCore's 33-char session id
1123
+ - **main.tsx** Wraps `<App />` in `<AguiProvider>`
1124
+
1125
+ </FileTree>
1126
+
1127
+ The generator:
1128
+ - Detects the React website's `ux` (Shadcn here) and vends matching chat components.
1129
+ - Registers every connected agent on a single `CopilotKitProvider` — re-running for another agent just adds another hook.
1130
+ - Reads the agent's runtime ARN from Runtime Configuration, builds the AgentCore invocation URL, and attaches the Cognito bearer token plus the AgentCore session id header.
1131
+
1132
+ For more details, refer to the <Link path="guides/connection/react-agui">React to AG-UI connection guide</Link>.
1133
+ </details>
1134
+
1135
+ ###### Connect the Game API and Inventory MCP server to the database
1136
+
1137
+ Both the Game API and the Inventory MCP server read and write our DynamoDB table, so let us connect them to the `DungeonDb` project. The `connection` generator detects that the target is a `ts#dynamodb` project and wires each source project's `dev` target to start DynamoDB Local automatically.
1138
+
1139
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/game-api", targetProject:"@dungeon-adventure/dungeon-db"}} noInteractive />
1140
+
1141
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/inventory", targetProject:"@dungeon-adventure/dungeon-db"}} noInteractive />
1142
+
1143
+ <Aside type="tip" title="One command boots the whole stack locally">
1144
+ Because the Story Agent's `agent-dev` already depends on the Inventory MCP server's `mcp-server-dev` (via the `story → inventory` connection above), and both the Game API and MCP server now depend on `dungeon-db:dev`, running any one project's `dev` target starts every dependency it needs in the right order.
1145
+ </Aside>
1146
+
1147
+ ###### Game UI: Infrastructure
1148
+
1149
+ Let us create the final sub-project for the CDK infrastructure.
1150
+
1151
+ <RunGenerator generator="ts#infra" requiredParameters={{name:"infra"}} noInteractive />
1152
+
1153
+ You will see some new files have appear/change in your file tree.
1154
+
1155
+ <details>
1156
+ <summary>Examine the generated `ts#infra` files in detail</summary>
1157
+
1158
+ The `ts#infra` generator generates/updates these. Let us examine some of the key files highlighted in the file tree:
1159
+
1160
+ <FileTree>
1161
+ - packages/
1162
+ - common/
1163
+ - constructs/
1164
+ - src/
1165
+ - core/
1166
+ - checkov.ts
1167
+ - index.ts
1168
+ - infra
1169
+ - src/
1170
+ - stages/
1171
+ - **application-stage.ts** cdk stacks defined here
1172
+ - stacks/
1173
+ - **application-stack.ts** cdk resources defined here
1174
+ - **main.ts** entrypoint which defines all stages
1175
+ - cdk.json
1176
+ - checkov.yml
1177
+ - project.json
1178
+ - ...
1179
+ - package.json
1180
+ - tsconfig.json add references
1181
+ - tsconfig.base.json add alias
1182
+
1183
+ </FileTree>
1184
+
1185
+ ```ts
1186
+ // packages/infra/src/main.ts
1187
+ import { ApplicationStage } from './stages/application-stage.js';
1188
+ import { App } from '@dungeon-adventure/common-constructs';
1189
+
1190
+ const app = new App();
1191
+
1192
+ // Use this to deploy your own sandbox environment (assumes your CLI credentials)
1193
+ new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', {
1194
+ env: {
1195
+ account: process.env.CDK_DEFAULT_ACCOUNT,
1196
+ region: process.env.CDK_DEFAULT_REGION,
1197
+ },
1198
+ });
1199
+
1200
+ app.synth();
1201
+ ```
1202
+
1203
+ <Aside type="tip" title="Fixing Import Errors">If you see an import error within your IDE, this is because our infrastructure project does not have a typescript reference set up yet in the `tsconfig.json`. Nx has been [configured](https://nx.dev/nx-api/js/generators/typescript-sync) to create these references *dynamically* whenever a build/compile is run or if you run the `nx sync` command manually. For more information refer to the <Link path="guides/typescript-project#importing-your-library-code-in-other-projects">Typescript guide</Link>.</Aside>
1204
+
1205
+ This is the entry point for your CDK application.
1206
+
1207
+ ```ts
1208
+ // packages/infra/src/stacks/application-stack.ts
1209
+ import { Stack, StackProps } from 'aws-cdk-lib';
1210
+ import { Construct } from 'constructs';
1211
+
1212
+ export class ApplicationStack extends Stack {
1213
+ constructor(scope: Construct, id: string, props?: StackProps) {
1214
+ super(scope, id, props);
1215
+
1216
+ // The code that defines your stack goes here
1217
+ }
1218
+ }
1219
+ ```
1220
+
1221
+ Let us instantiate our CDK constructs to build our dungeon adventure game.
1222
+
1223
+ </details>
1224
+
1225
+ </Drawer>
1226
+
1227
+ ## Task 3: Update our infrastructure
1228
+
1229
+ Let's update `packages/infra/src/stacks/application-stack.ts` to instantiate some of our generated constructs:
1230
+
1231
+ <E2EDiff before="dungeon-adventure/1/application-stack.ts.original.template" after="dungeon-adventure/1/application-stack.ts.template" lang="ts" />
1232
+
1233
+ :::note[Default Integrations]
1234
+ We supply default integrations for our Game API. By default, each operation in our API is mapped to an individual Lambda function to handle that operation.
1235
+ :::
1236
+
1237
+ ## Task 4: Build the code
1238
+
1239
+ <Drawer title="Nx commands" trigger="Now it's time for us to build our code for the first time">
1240
+
1241
+ ###### Single vs Multiple targets
1242
+
1243
+ The `run-many` command will run a target on multiple listed subprojects (`--all` will target them all). This ensures dependencies are executed in the correct order.
1244
+
1245
+ You can also trigger a build (or any other task) for a single project target by running the target on the project directly. For example, to build the `@dungeon-adventure/infra` project, run the following command:
1246
+
1247
+ <NxCommands commands={['build infra']} />
1248
+
1249
+ You can also omit the scope, and use the Nx shorthand syntax if you prefer:
1250
+
1251
+ <NxCommands commands={['build infra']} />
1252
+
1253
+ ###### Visualizing your dependencies
1254
+
1255
+ To visualize your dependencies, run:
1256
+
1257
+ <NxCommands commands={['graph']} />
1258
+ <br/>
1259
+
1260
+ <Image src={nxGraphPng} alt="nx-graph.png" width="800" height="600" />
1261
+
1262
+ ###### Caching
1263
+
1264
+ Nx relies on [caching](https://nx.dev/concepts/how-caching-works) so that you can re-use artifacts from previous builds in order to speed up development. There is some configuration required to get this to work correctly and there may be cases where you want to perform a build **without using the cache**. To do that, simply append the `--skip-nx-cache` argument to your command. For example:
1265
+
1266
+ <NxCommands commands={['build infra --skip-nx-cache']} />
1267
+ If for whatever reason you ever wanted to clear your cache (stored in the `.nx` folder), you can run the following command:
1268
+
1269
+ <NxCommands commands={['reset']} />
1270
+
1271
+ </Drawer>
1272
+
1273
+ Using the command line, run the following command to fix any lint issues first:
1274
+
1275
+ <PackageManagerShortCommand commands={["lint"]} />
1276
+
1277
+ Then, run the following command for a full build:
1278
+
1279
+ <PackageManagerShortCommand commands={["build"]} />
1280
+
1281
+ You will be prompted with the following:
1282
+
1283
+ ```bash
1284
+ NX The workspace is out of sync
1285
+
1286
+ [@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
1287
+
1288
+ This will result in an error in CI.
1289
+
1290
+ ? Would you like to sync the identified changes to get your workspace up to date? …
1291
+ Yes, sync the changes and run the tasks
1292
+ No, run the tasks without syncing the changes
1293
+ ```
1294
+
1295
+ This message indicates that NX has detected some files which can be updated automatically for you. In this case, it is referring to the `tsconfig.json` files which do not have Typescript references set up on references projects.
1296
+
1297
+ Select the **Yes, sync the changes and run the tasks** option to proceed. You should notice all of you IDE related import errors get automatically resolved as the sync generator will add the missing typescript references automatically!
1298
+
1299
+ All built artifacts are now available within the `dist/ folder` located at the root of the monorepo. This is a standard pactice when using projects generated by the `@aws/nx-plugin` as it does not pollute your file-tree with generated files. In the event you want to clean your files, delete the `dist/` folder without worrying about build artifacts being littered throughout the file tree.
1300
+
1301
+ Congratulations! You've created all of the required sub-projects required to start implementing the core of our AI Dungeon Adventure game. 🎉🎉🎉