@aws/nx-plugin-mcp 1.0.0-rc.8 → 1.0.0-rc.80

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 (196) hide show
  1. package/bin/aws-nx-mcp.js +14210 -13550
  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 +1578 -0
  9. package/docs/get_started/tutorials/dungeon-game/2.mdx +245 -0
  10. package/docs/get_started/tutorials/dungeon-game/3.mdx +66 -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/upgrading.mdx +147 -0
  15. package/docs/guides/agentcore-gateway.mdx +490 -0
  16. package/docs/guides/agentcore-harness.mdx +275 -0
  17. package/docs/guides/astro-docs.mdx +8 -0
  18. package/docs/guides/connection/agentcore-gateway-agent.mdx +222 -0
  19. package/docs/guides/connection/agentcore-gateway-gateway.mdx +154 -0
  20. package/docs/guides/connection/agentcore-gateway-mcp.mdx +134 -0
  21. package/docs/guides/connection/py-agent-a2a.mdx +48 -16
  22. package/docs/guides/connection/py-agent-dynamodb.mdx +116 -0
  23. package/docs/guides/connection/py-agent-gateway.mdx +182 -0
  24. package/docs/guides/connection/py-agent-mcp.mdx +43 -14
  25. package/docs/guides/connection/py-agent-rdb.mdx +178 -0
  26. package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
  27. package/docs/guides/connection/py-fast-api-rdb.mdx +184 -0
  28. package/docs/guides/connection/py-mcp-server-dynamodb.mdx +116 -0
  29. package/docs/guides/connection/py-mcp-server-rdb.mdx +187 -0
  30. package/docs/guides/connection/react-agentcore-gateway.mdx +112 -0
  31. package/docs/guides/connection/react-agui.mdx +33 -14
  32. package/docs/guides/connection/react-fastapi.mdx +39 -3
  33. package/docs/guides/connection/react-py-agent.mdx +10 -16
  34. package/docs/guides/connection/react-smithy.mdx +5 -5
  35. package/docs/guides/connection/react-trpc.mdx +2 -2
  36. package/docs/guides/connection/react-ts-agent.mdx +9 -9
  37. package/docs/guides/connection/smithy-dynamodb.mdx +6 -6
  38. package/docs/guides/connection/smithy-rdb.mdx +10 -10
  39. package/docs/guides/connection/trpc-dynamodb.mdx +5 -5
  40. package/docs/guides/connection/trpc-rdb.mdx +6 -6
  41. package/docs/guides/connection/ts-agent-a2a.mdx +14 -11
  42. package/docs/guides/connection/ts-agent-dynamodb.mdx +5 -5
  43. package/docs/guides/connection/ts-agent-gateway.mdx +147 -0
  44. package/docs/guides/connection/ts-agent-mcp.mdx +12 -9
  45. package/docs/guides/connection/ts-agent-rdb.mdx +70 -25
  46. package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +5 -5
  47. package/docs/guides/connection/ts-mcp-server-rdb.mdx +68 -18
  48. package/docs/guides/connection.mdx +122 -5
  49. package/docs/guides/docker-bundling.mdx +81 -12
  50. package/docs/guides/fastapi.mdx +252 -15
  51. package/docs/guides/local-development.mdx +87 -0
  52. package/docs/guides/nx-generator.mdx +4 -3
  53. package/docs/guides/nx-migration.mdx +165 -0
  54. package/docs/guides/py-agent.mdx +332 -55
  55. package/docs/guides/py-dynamodb.mdx +476 -0
  56. package/docs/guides/py-mcp-server.mdx +57 -2
  57. package/docs/guides/py-rdb.mdx +265 -0
  58. package/docs/guides/python-lambda-function.mdx +1 -1
  59. package/docs/guides/react-website-auth.mdx +104 -5
  60. package/docs/guides/react-website.mdx +413 -99
  61. package/docs/guides/runtime-config.mdx +1 -1
  62. package/docs/guides/security.mdx +75 -0
  63. package/docs/guides/smithy-project.mdx +167 -0
  64. package/docs/guides/terraform-project.mdx +2 -2
  65. package/docs/guides/trpc.mdx +54 -17
  66. package/docs/guides/ts-agent.mdx +207 -23
  67. package/docs/guides/ts-dcr-proxy.mdx +569 -0
  68. package/docs/guides/ts-dynamodb.mdx +66 -242
  69. package/docs/guides/ts-lambda-function.mdx +1 -1
  70. package/docs/guides/ts-mcp-server.mdx +111 -29
  71. package/docs/guides/ts-nx-plugin.mdx +4 -4
  72. package/docs/guides/ts-rdb.mdx +114 -468
  73. package/docs/guides/ts-smithy-api.mdx +259 -19
  74. package/docs/guides/typescript-infrastructure.mdx +46 -24
  75. package/docs/guides/typescript-project.mdx +134 -27
  76. package/docs/guides/workspace.mdx +10 -3
  77. package/docs/snippets/agent/architecture.mdx +1 -1
  78. package/docs/snippets/agent/bedrock-deployment.mdx +9 -5
  79. package/docs/snippets/agent/runtime-arn.mdx +23 -2
  80. package/docs/snippets/agent/securing-your-agent.mdx +39 -0
  81. package/docs/snippets/api/access-logging.mdx +38 -0
  82. package/docs/snippets/api/cors-configuration-cdk-note.mdx +1 -1
  83. package/docs/snippets/api/cors-configuration-terraform-note.mdx +1 -1
  84. package/docs/snippets/api/type-safe-api-integrations.mdx +69 -50
  85. package/docs/snippets/api/waf-configuration.mdx +3 -3
  86. package/docs/snippets/connection/a2a-infrastructure.mdx +1 -1
  87. package/docs/snippets/connection/dynamodb-local-development.mdx +2 -2
  88. package/docs/snippets/connection/lambda-dynamodb-access.mdx +1 -1
  89. package/docs/snippets/connection/py-dynamodb-local-development.mdx +7 -0
  90. package/docs/snippets/connection/py-lambda-rdb-ssl-requirements.mdx +7 -0
  91. package/docs/snippets/connection/rdb-api-infrastructure.mdx +51 -19
  92. package/docs/snippets/dynamodb/deploying-table.mdx +145 -0
  93. package/docs/snippets/dynamodb/encryption-options.mdx +168 -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/mcp/shared-constructs.mdx +4 -5
  103. package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +7 -5
  104. package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +29 -10
  105. package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +4 -4
  106. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +5 -111
  107. package/docs/snippets/prerequisites.mdx +1 -4
  108. package/docs/snippets/rdb/architecture.mdx +38 -0
  109. package/docs/snippets/rdb/cluster-instances.mdx +31 -0
  110. package/docs/snippets/rdb/deletion-protection.mdx +34 -0
  111. package/docs/snippets/rdb/deploying.mdx +187 -0
  112. package/docs/snippets/rdb/encryption-key-rotation.mdx +30 -0
  113. package/docs/snippets/rdb/engine-version.mdx +63 -0
  114. package/docs/snippets/rdb/infrastructure.mdx +35 -0
  115. package/docs/snippets/rdb/logging-mysql.mdx +5 -0
  116. package/docs/snippets/rdb/logging-postgres.mdx +5 -0
  117. package/docs/snippets/rdb/performance-insights.mdx +34 -0
  118. package/docs/snippets/rdb/rds-proxy.mdx +50 -0
  119. package/docs/snippets/rdb/removal-policy.mdx +57 -0
  120. package/docs/snippets/rdb/serverless-capacity.mdx +32 -0
  121. package/docs/snippets/recommended-prerequisites.mdx +10 -0
  122. package/docs/snippets/required-prerequisites.mdx +1 -4
  123. package/docs/snippets/runtime-config-app-id-note.mdx +8 -0
  124. package/docs/snippets/shared-constructs.mdx +1 -1
  125. package/docs/snippets/trivy-image-scan.mdx +37 -0
  126. package/generators.json +162 -10
  127. package/package.json +1 -1
  128. package/src/agentcore-gateway/agent-connection/schema.json +31 -0
  129. package/src/agentcore-gateway/gateway-connection/schema.json +31 -0
  130. package/src/agentcore-gateway/mcp-connection/schema.json +31 -0
  131. package/src/agentcore-gateway/react-connection/schema.json +31 -0
  132. package/src/agentcore-gateway/schema.json +72 -0
  133. package/src/agentcore-harness/schema.json +53 -0
  134. package/src/connection/schema.json +5 -0
  135. package/src/infra/app/schema.json +5 -0
  136. package/src/init/schema.json +35 -0
  137. package/src/internal/test-matrix/schema.json +21 -0
  138. package/src/license/schema.json +5 -0
  139. package/src/preset/schema.json +16 -5
  140. package/src/py/agent/a2a-connection/schema.json +5 -0
  141. package/src/py/agent/gateway-connection/schema.json +31 -0
  142. package/src/py/agent/mcp-connection/schema.json +5 -0
  143. package/src/py/agent/react-connection/schema.json +5 -0
  144. package/src/py/agent/schema.json +15 -1
  145. package/src/py/api/schema.json +5 -0
  146. package/src/py/dynamodb/agent-connection/schema.json +27 -0
  147. package/src/py/dynamodb/fast-api-connection/schema.json +23 -0
  148. package/src/py/dynamodb/mcp-server-connection/schema.json +27 -0
  149. package/src/py/dynamodb/schema.json +76 -0
  150. package/src/py/fast-api/react/schema.json +5 -0
  151. package/src/py/fast-api/schema.json +6 -0
  152. package/src/py/lambda-function/schema.json +6 -1
  153. package/src/py/mcp-server/schema.json +6 -0
  154. package/src/py/project/schema.json +6 -0
  155. package/src/py/rdb/agent-connection/schema.json +27 -0
  156. package/src/py/rdb/fast-api-connection/schema.json +23 -0
  157. package/src/py/rdb/mcp-server-connection/schema.json +27 -0
  158. package/src/py/rdb/schema.json +78 -0
  159. package/src/smithy/project/schema.json +28 -1
  160. package/src/smithy/react-connection/schema.json +5 -0
  161. package/src/smithy/ts/api/schema.json +6 -0
  162. package/src/terraform/project/schema.json +5 -0
  163. package/src/trpc/backend/schema.json +6 -0
  164. package/src/trpc/react/schema.json +5 -0
  165. package/src/ts/agent/a2a-connection/schema.json +5 -0
  166. package/src/ts/agent/gateway-connection/schema.json +31 -0
  167. package/src/ts/agent/mcp-connection/schema.json +5 -0
  168. package/src/ts/agent/react-connection/schema.json +5 -0
  169. package/src/ts/agent/schema.json +14 -0
  170. package/src/ts/api/schema.json +5 -0
  171. package/src/ts/astro-docs/schema.json +3 -3
  172. package/src/ts/dcr-proxy/schema.json +44 -0
  173. package/src/ts/docs/schema.json +3 -3
  174. package/src/ts/dynamodb/agent-connection/schema.json +5 -0
  175. package/src/ts/dynamodb/mcp-server-connection/schema.json +5 -0
  176. package/src/ts/dynamodb/schema.json +26 -2
  177. package/src/ts/dynamodb/smithy-connection/schema.json +5 -0
  178. package/src/ts/dynamodb/trpc-connection/schema.json +5 -0
  179. package/src/ts/lambda-function/schema.json +5 -0
  180. package/src/ts/lib/schema.json +5 -0
  181. package/src/ts/mcp-server/schema.json +6 -0
  182. package/src/ts/nx-generator/schema.json +5 -0
  183. package/src/ts/nx-migration/schema.json +63 -0
  184. package/src/ts/nx-plugin/schema.json +5 -0
  185. package/src/ts/rdb/agent-connection/schema.json +5 -0
  186. package/src/ts/rdb/mcp-server-connection/schema.json +5 -0
  187. package/src/ts/rdb/schema.json +7 -1
  188. package/src/ts/rdb/smithy-connection/schema.json +5 -0
  189. package/src/ts/rdb/trpc-connection/schema.json +5 -0
  190. package/src/ts/react-website/app/schema.json +12 -6
  191. package/src/ts/react-website/cognito-auth/schema.json +5 -0
  192. package/src/ts/react-website/runtime-config/schema.json +5 -0
  193. package/src/ts/website/app/schema.json +11 -6
  194. package/src/ts/website/auth/schema.json +5 -0
  195. /package/docs/snippets/connection/{lambda-rdb-ssl-requirements.mdx → ts-lambda-rdb-ssl-requirements.mdx} +0 -0
  196. /package/docs/snippets/connection/{mcp-server-rdb-ssl-requirements.mdx → ts-mcp-server-rdb-ssl-requirements.mdx} +0 -0
@@ -0,0 +1,1578 @@
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 { RuntimeConfig } from '../../core/runtime-config.js';
202
+ import {
203
+ AuthorizationType,
204
+ LambdaIntegration,
205
+ ResponseTransferMode,
206
+ } from 'aws-cdk-lib/aws-apigateway';
207
+ import { Aspects, Duration } from 'aws-cdk-lib';
208
+ import {
209
+ PolicyDocument,
210
+ PolicyStatement,
211
+ Effect,
212
+ AnyPrincipal,
213
+ IGrantable,
214
+ Grant,
215
+ } from 'aws-cdk-lib/aws-iam';
216
+ import {
217
+ ApiIntegrations,
218
+ IntegrationBuilder,
219
+ RestApiIntegration,
220
+ } from '../../core/api/utils.js';
221
+ import { findCloudFrontDomainNames } from '../../core/cloudfront.js';
222
+ import { AddCorsPreflightAspect, RestApi } from '../../core/api/rest-api.js';
223
+ import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';
224
+ import { AppRouter, appRouter } from '@dungeon-adventure/game-api';
225
+
226
+ // String union type for all API operation names
227
+ type Operations = Procedures<AppRouter>;
228
+
229
+ /**
230
+ * Properties for creating a GameApi construct
231
+ *
232
+ * @template TIntegrations - Map of operation names to their integrations
233
+ */
234
+ export interface GameApiProps<
235
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
236
+ > {
237
+ /**
238
+ * Map of operation names to their API Gateway integrations
239
+ */
240
+ integrations: TIntegrations;
241
+ /**
242
+ * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage.
243
+ *
244
+ * @default true
245
+ */
246
+ enableWaf?: boolean;
247
+ }
248
+
249
+ /**
250
+ * A CDK construct that creates and configures an AWS API Gateway REST API
251
+ * specifically for GameApi.
252
+ * @template TIntegrations - Map of operation names to their integrations
253
+ */
254
+ export class GameApi<
255
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
256
+ > extends RestApi<Operations, TIntegrations> {
257
+ private allowedOrigins: readonly string[] = ['*'];
258
+
259
+ /**
260
+ * Creates default integrations for all operations, which implement each operation as
261
+ * its own individual lambda function.
262
+ *
263
+ * @param scope - The CDK construct scope
264
+ * @returns An IntegrationBuilder with default lambda integrations
265
+ */
266
+ public static defaultIntegrations = (scope: Construct) => {
267
+ const rc = RuntimeConfig.ensure(scope);
268
+ return IntegrationBuilder.rest({
269
+ pattern: 'isolated',
270
+ operations: routerToOperations(appRouter),
271
+ defaultIntegrationOptions: {
272
+ runtime: Runtime.NODEJS_LATEST,
273
+ handler: 'index.handler',
274
+ code: Code.fromAsset(
275
+ url.fileURLToPath(
276
+ new URL(
277
+ '../../../../../../dist/packages/game-api/bundle',
278
+ import.meta.url,
279
+ ),
280
+ ),
281
+ ),
282
+ timeout: Duration.seconds(30),
283
+ tracing: Tracing.ACTIVE,
284
+ } as FunctionProps,
285
+ buildDefaultIntegration: (op, props: FunctionProps) => {
286
+ const handler = new Function(scope, `GameApi${op}Handler`, props);
287
+ handler.addEnvironment('RUNTIME_CONFIG_APP_ID', rc.appConfigApplicationId);
288
+ rc.grantReadAppConfig(handler);
289
+ return {
290
+ handler,
291
+ integration: new LambdaIntegration(handler, {
292
+ responseTransferMode: ResponseTransferMode.STREAM,
293
+ }),
294
+ };
295
+ },
296
+ });
297
+ };
298
+
299
+ constructor(
300
+ scope: Construct,
301
+ id: string,
302
+ props: GameApiProps<TIntegrations>,
303
+ ) {
304
+ super(scope, id, {
305
+ apiName: 'GameApi',
306
+ defaultMethodOptions: {
307
+ authorizationType: AuthorizationType.IAM,
308
+ },
309
+ deployOptions: {
310
+ tracingEnabled: true,
311
+ },
312
+ policy: new PolicyDocument({
313
+ statements: [
314
+ // Open up OPTIONS to allow browsers to make unauthenticated preflight requests
315
+ new PolicyStatement({
316
+ effect: Effect.ALLOW,
317
+ principals: [new AnyPrincipal()],
318
+ actions: ['execute-api:Invoke'],
319
+ resources: ['execute-api:/*/OPTIONS/*'],
320
+ }),
321
+ ],
322
+ }),
323
+ operations: routerToOperations(appRouter),
324
+ ...props,
325
+ });
326
+ Aspects.of(this).add(new AddCorsPreflightAspect(() => this.allowedOrigins));
327
+ }
328
+
329
+ /**
330
+ * Restricts CORS to the provided origins
331
+ *
332
+ * Configures the CloudFront distribution domains or origin strings
333
+ * as the only permitted CORS origins in API Gateway preflight responses and the AWS
334
+ * Lambda integrations. Any custom domain names (aliases) configured on a CloudFront
335
+ * distribution are included automatically alongside its default `*.cloudfront.net`
336
+ * domain.
337
+ *
338
+ * @param origins - The origin strings, CloudFront distributions, or objects containing a CloudFront distribution to grant CORS from
339
+ */
340
+ public restrictCorsTo(
341
+ ...origins: (string | Distribution | { cloudFrontDistribution: Distribution })[]
342
+ ) {
343
+ const allowedOrigins = origins.flatMap((origin) =>
344
+ typeof origin === 'string'
345
+ ? [origin]
346
+ : findCloudFrontDomainNames(
347
+ 'cloudFrontDistribution' in origin
348
+ ? origin.cloudFrontDistribution
349
+ : origin,
350
+ ).map((domain) => `https://${domain}`),
351
+ );
352
+
353
+ this.allowedOrigins = allowedOrigins;
354
+
355
+ // Set ALLOWED_ORIGINS environment variable for all Lambda integrations
356
+ Object.values(this.integrations).forEach((integration) => {
357
+ if ('handler' in integration && integration.handler instanceof Function) {
358
+ integration.handler.addEnvironment(
359
+ 'ALLOWED_ORIGINS',
360
+ allowedOrigins.join(','),
361
+ );
362
+ }
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Grants IAM permissions to invoke any method on this API.
368
+ *
369
+ * @param grantee - The IAM principal to grant permissions to
370
+ */
371
+ public grantInvokeAccess(grantee: IGrantable) {
372
+ // Here we grant grantee permission to call the api.
373
+ // Machine to machine fine-grained access can be defined here using more specific principals (eg roles or
374
+ // users) and resources (eg which api paths may be invoked by which principal) if required.
375
+ this.api.addToResourcePolicy(
376
+ new PolicyStatement({
377
+ effect: Effect.ALLOW,
378
+ principals: [grantee.grantPrincipal],
379
+ actions: ['execute-api:Invoke'],
380
+ resources: ['execute-api:/*'],
381
+ }),
382
+ );
383
+
384
+ Grant.addToPrincipal({
385
+ grantee,
386
+ actions: ['execute-api:Invoke'],
387
+ resourceArns: [this.api.arnForExecuteApi('*', '/*', '*')],
388
+ });
389
+ }
390
+ }
391
+ ```
392
+
393
+ 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.
394
+
395
+ </details>
396
+
397
+ ##### Create the Story Agent
398
+
399
+ Now let's create our Story Agent.
400
+
401
+ ###### Story agent: Python project
402
+
403
+ To create a Python project:
404
+
405
+ <RunGenerator generator="py#project" requiredParameters={{name:"story"}} noInteractive />
406
+
407
+ You will see some new files appear in your file tree.
408
+ <details>
409
+ <summary>Examine the generated `py#project` files in detail</summary>
410
+
411
+ The `py#project` generates these files:
412
+
413
+ <FileTree>
414
+ - .venv/ single virtual env for monorepo
415
+ - packages/
416
+ - story/
417
+ - dungeon_adventure_story/ python module
418
+ - tests/
419
+ - .python-version
420
+ - pyproject.toml
421
+ - project.json
422
+ - .python-version pinned uv python version
423
+ - pyproject.toml
424
+ - uv.lock
425
+ </FileTree>
426
+
427
+ This has configured a Python project and [UV Workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/) with shared virtual environment.
428
+
429
+ </details>
430
+
431
+ ###### Story agent
432
+
433
+ To add a Strands agent to the project with the `py#agent` generator:
434
+
435
+ <RunGenerator generator="py#agent" requiredParameters={{project:"story", auth:"cognito", protocol:"ag-ui"}} noInteractive />
436
+
437
+ :::note[AG-UI protocol]
438
+ 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.
439
+ :::
440
+
441
+ You will see some new files appear in your file tree.
442
+ <details>
443
+ <summary>Examine the generated `py#agent` files in detail</summary>
444
+
445
+ The `py#agent` generates these files:
446
+
447
+ <FileTree>
448
+ - packages/
449
+ - story/
450
+ - dungeon_adventure_story/ python module
451
+ - agent/
452
+ - main.py entrypoint for your agent in Bedrock AgentCore Runtime
453
+ - agent.py defines an example agent and tools
454
+ - session.py resolves a SessionManager for persisting conversation state
455
+ - middleware/
456
+ - session_id_middleware.py binds the inbound AgentCore session ID for the request
457
+ - Dockerfile defines the docker image for deployment to AgentCore Runtime
458
+ - common/constructs/
459
+ - src
460
+ - app/agents/story-agent/
461
+ - story-agent.ts construct for deploying your Story agent to AgentCore Runtime
462
+ </FileTree>
463
+
464
+ Let's take a look at some of the files in detail:
465
+
466
+ ```python
467
+ # agent/agent.py
468
+ from contextlib import contextmanager
469
+
470
+ from strands import Agent, tool
471
+ from strands.hooks import HookCallback, HookProvider
472
+ from strands_tools import current_time
473
+ from dungeon_adventure_agent_connection import log_model_errors, log_tool_errors
474
+
475
+
476
+ @tool
477
+ def subtract(a: int, b: int) -> int:
478
+ return a - b
479
+
480
+
481
+ AGENT_HOOKS: list[HookProvider | HookCallback] = [log_model_errors, log_tool_errors]
482
+
483
+
484
+ @contextmanager
485
+ def get_agent():
486
+ yield Agent(
487
+ name="StoryAgent",
488
+ description="StoryAgent Strands Agent",
489
+ system_prompt="""
490
+ You are a mathematical wizard.
491
+ Use your tools for mathematical tasks.
492
+ Refer to tools as your 'spellbook'.
493
+ """,
494
+ tools=[subtract, current_time],
495
+ hooks=AGENT_HOOKS,
496
+ )
497
+ ```
498
+
499
+ This creates an example Strands agent and defines a subtraction tool. `log_model_errors` and `log_tool_errors` are hooks from the shared `dungeon_adventure_agent_connection` project that log model/tool failures instead of letting them fail silently.
500
+
501
+ ```python
502
+ # agent/middleware/session_id_middleware.py
503
+ import uuid
504
+
505
+ from fastapi import Request
506
+ from starlette.middleware.base import BaseHTTPMiddleware
507
+
508
+ from dungeon_adventure_agent_connection import session_id_context
509
+
510
+ SESSION_ID_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id"
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
+ The `SessionIdMiddleware` binds the inbound AgentCore runtime session ID onto a `ContextVar` for the duration of the request.
523
+
524
+ ```python
525
+ # agent/main.py
526
+ import logging
527
+ import uuid
528
+ from contextlib import asynccontextmanager
529
+
530
+ from ag_ui.core import EventType, RunAgentInput, RunErrorEvent
531
+ from ag_ui.encoder import EventEncoder
532
+ from ag_ui_strands import StrandsAgent, StrandsAgentConfig
533
+ from dungeon_adventure_agent_connection import get_current_session_id, session_id_context
534
+ from fastapi import FastAPI, Request
535
+ from fastapi.middleware.cors import CORSMiddleware
536
+ from fastapi.responses import StreamingResponse
537
+
538
+ from .agent import AGENT_HOOKS, get_agent
539
+ from .middleware.session_id_middleware import SESSION_ID_HEADER, SessionIdMiddleware
540
+ from .session import get_session_manager
541
+
542
+ logging.basicConfig(level=logging.INFO)
543
+
544
+
545
+ @asynccontextmanager
546
+ async def lifespan(app: FastAPI):
547
+ with get_agent() as agent:
548
+ app.state.agui_agent = StrandsAgent(
549
+ agent=agent,
550
+ name="StoryAgent",
551
+ description="A Strands Agent exposed via the AG-UI protocol.",
552
+ # A per-thread session manager, not the template Agent's own, since
553
+ # AG-UI caches one Strands agent per thread_id.
554
+ config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager()),
555
+ # Required as well as on the template Agent: AG-UI keeps only the
556
+ # built HookRegistry, so hooks it can't read back are never
557
+ # registered and model/tool failures go unreported.
558
+ hooks=AGENT_HOOKS,
559
+ )
560
+ yield
561
+
562
+
563
+ app = FastAPI(title="AWS Strands - StoryAgent", lifespan=lifespan)
564
+ app.add_middleware(
565
+ CORSMiddleware,
566
+ allow_origins=["*"],
567
+ allow_credentials=True,
568
+ allow_methods=["*"],
569
+ allow_headers=["*"],
570
+ )
571
+ app.add_middleware(SessionIdMiddleware)
572
+
573
+
574
+ @app.post("/invocations")
575
+ async def invocations(request: Request):
576
+ # Validate the body manually since AgentCore may omit Content-Type.
577
+ encoder = EventEncoder(accept=request.headers.get("accept") or "")
578
+ raw = await request.body()
579
+ try:
580
+ input_data = RunAgentInput.model_validate_json(raw)
581
+ except Exception as exc:
582
+ message = f"Invalid RunAgentInput: {str(exc)[:200]}"
583
+
584
+ async def _bad():
585
+ yield encoder.encode(RunErrorEvent(type=EventType.RUN_ERROR, message=message, code="BAD_REQUEST"))
586
+
587
+ return StreamingResponse(_bad(), media_type=encoder.get_content_type())
588
+
589
+ session_id = request.headers.get(SESSION_ID_HEADER) or get_current_session_id()
590
+
591
+ async def event_generator():
592
+ # Re-bind the session: the streaming body runs outside the middleware.
593
+ with session_id_context(session_id or str(uuid.uuid4())):
594
+ async for event in request.app.state.agui_agent.run(input_data):
595
+ try:
596
+ yield encoder.encode(event)
597
+ except Exception as e:
598
+ error_event = RunErrorEvent(
599
+ type=EventType.RUN_ERROR,
600
+ message=f"Encoding error: {e}",
601
+ code="ENCODING_ERROR",
602
+ )
603
+ yield encoder.encode(error_event)
604
+ break
605
+
606
+ return StreamingResponse(event_generator(), media_type=encoder.get_content_type())
607
+
608
+
609
+ @app.get("/ping")
610
+ async def ping():
611
+ return {"status": "healthy"}
612
+ ```
613
+
614
+ 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` we saw above forwards the inbound AgentCore runtime session ID 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. Since AG-UI caches one Strands agent per `thread_id`, we plug in a `session_manager_provider` rather than the template agent's own session manager — this gives each thread its own `SessionManager` so conversation history persists across turns. That `get_session_manager()` function comes from a generated `session.py` sibling: deployed, it returns a `strands.session.S3SessionManager` backed by an S3 bucket the generator provisions automatically; under `agent-dev` (`LOCAL_DEV=true`) it always returns a `FileSessionManager` writing to a local temp directory instead, regardless of deployed configuration.
615
+
616
+ ```ts
617
+ // common/constructs/src/app/agents/story-agent/story-agent.ts
618
+ import { Fn, Lazy, Names, RemovalPolicy, Stack } from 'aws-cdk-lib';
619
+ import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
620
+ import { Connections, IConnectable } from 'aws-cdk-lib/aws-ec2';
621
+ import {
622
+ BlockPublicAccess,
623
+ Bucket,
624
+ BucketEncryption,
625
+ } from 'aws-cdk-lib/aws-s3';
626
+ import { Key } from 'aws-cdk-lib/aws-kms';
627
+ import {
628
+ CfnDelivery,
629
+ CfnDeliveryDestination,
630
+ CfnDeliverySource,
631
+ LogGroup,
632
+ RetentionDays,
633
+ } from 'aws-cdk-lib/aws-logs';
634
+ import { Construct } from 'constructs';
635
+ import * as path from 'path';
636
+ import * as url from 'url';
637
+ import {
638
+ AgentRuntimeArtifact,
639
+ ProtocolType,
640
+ Runtime,
641
+ RuntimeProps,
642
+ RuntimeAuthorizerConfiguration,
643
+ } from 'aws-cdk-lib/aws-bedrockagentcore';
644
+ import {
645
+ PolicyStatement,
646
+ Effect,
647
+ ServicePrincipal,
648
+ IGrantable,
649
+ IPrincipal,
650
+ } from 'aws-cdk-lib/aws-iam';
651
+ import { IUserPool, IUserPoolClient } from 'aws-cdk-lib/aws-cognito';
652
+ import { suppressRules } from '../../../core/checkov.js';
653
+ import { RuntimeConfig } from '../../../core/runtime-config.js';
654
+ import { findWorkspaceRoot } from '../../../core/workspace.js';
655
+
656
+ export type StoryAgentProps = Omit<
657
+ RuntimeProps,
658
+ | 'runtimeName'
659
+ | 'protocolConfiguration'
660
+ | 'agentRuntimeArtifact'
661
+ | 'authorizerConfiguration'
662
+ > & {
663
+ /**
664
+ * Identity details for Cognito Authentication
665
+ */
666
+ identity: {
667
+ userPool: IUserPool;
668
+ userPoolClient: IUserPoolClient;
669
+ };
670
+ /**
671
+ * Removal policy for the session bucket holding the agent's conversation
672
+ * history. Defaults to retaining it so a stack `destroy` doesn't silently
673
+ * delete session data — set to `RemovalPolicy.DESTROY` for sandbox/CI teardown.
674
+ *
675
+ * @default RemovalPolicy.RETAIN
676
+ */
677
+ readonly sessionBucketRemovalPolicy?: RemovalPolicy;
678
+ };
679
+
680
+ export class StoryAgent extends Construct implements IGrantable, IConnectable {
681
+ public readonly dockerImage: AgentRuntimeArtifact;
682
+ public readonly agentCoreRuntime: Runtime;
683
+ /** Default Gateway target name for this agent. */
684
+ public readonly agentName = 'story-agent';
685
+ /** Inbound auth — a fronting Gateway uses this to pick its outbound credential. */
686
+ public readonly auth = 'cognito';
687
+
688
+ constructor(scope: Construct, id: string, props: StoryAgentProps) {
689
+ super(scope, id);
690
+
691
+ const rc = RuntimeConfig.ensure(this);
692
+
693
+ // Resolve the bundle output directory containing the Dockerfile and built artifacts
694
+ const bundleDir = path.join(
695
+ findWorkspaceRoot(url.fileURLToPath(new URL(import.meta.url))),
696
+ 'dist/packages/story/docker/story-agent',
697
+ );
698
+
699
+ this.dockerImage = AgentRuntimeArtifact.fromAsset(bundleDir, {
700
+ platform: Platform.LINUX_ARM64,
701
+ });
702
+
703
+ const {
704
+ identity,
705
+ sessionBucketRemovalPolicy = RemovalPolicy.RETAIN,
706
+ ...restProps
707
+ } = props ?? {};
708
+
709
+ const sessionKey = new Key(this, 'SessionKey', {
710
+ enableKeyRotation: true,
711
+ });
712
+
713
+ // Allow CloudWatch Logs to use the session key for server access log delivery.
714
+ const stack = Stack.of(this);
715
+ sessionKey.addToResourcePolicy(
716
+ new PolicyStatement({
717
+ effect: Effect.ALLOW,
718
+ principals: [
719
+ new ServicePrincipal(`logs.${stack.region}.amazonaws.com`),
720
+ ],
721
+ actions: [
722
+ 'kms:Encrypt',
723
+ 'kms:Decrypt',
724
+ 'kms:ReEncrypt*',
725
+ 'kms:GenerateDataKey*',
726
+ 'kms:DescribeKey',
727
+ ],
728
+ resources: ['*'],
729
+ conditions: {
730
+ ArnLike: {
731
+ 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${stack.region}:${stack.account}:log-group:*`,
732
+ },
733
+ },
734
+ }),
735
+ );
736
+
737
+ const sessionAccessLogs = new LogGroup(this, 'SessionAccessLogs', {
738
+ retention: RetentionDays.ONE_YEAR,
739
+ encryptionKey: sessionKey,
740
+ removalPolicy: RemovalPolicy.DESTROY,
741
+ });
742
+
743
+ const sessionBucket = new Bucket(this, 'SessionBucket', {
744
+ enforceSSL: true,
745
+ removalPolicy: sessionBucketRemovalPolicy,
746
+ encryption: BucketEncryption.KMS,
747
+ encryptionKey: sessionKey,
748
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
749
+ });
750
+ suppressRules(
751
+ sessionBucket,
752
+ ['CKV_AWS_21'],
753
+ 'Session data does not need versioning enabled',
754
+ );
755
+ suppressRules(
756
+ sessionBucket,
757
+ ['CKV2_AWS_61'],
758
+ 'Lifecycle configuration not required for session data',
759
+ );
760
+ suppressRules(
761
+ sessionBucket,
762
+ ['CKV_AWS_144'],
763
+ 'Cross-region replication not required for session data',
764
+ );
765
+ suppressRules(
766
+ sessionBucket,
767
+ ['CKV2_AWS_62'],
768
+ 'Event notifications not required for session data',
769
+ );
770
+ suppressRules(
771
+ sessionBucket,
772
+ ['CKV_AWS_18'],
773
+ 'Server access logs are delivered to CloudWatch Logs',
774
+ );
775
+
776
+ const sessionAccessLogsSource: CfnDeliverySource = new CfnDeliverySource(
777
+ this,
778
+ 'SessionAccessLogsSource',
779
+ {
780
+ name: Lazy.string({
781
+ produce: () =>
782
+ Names.uniqueResourceName(sessionAccessLogsSource, {
783
+ maxLength: 60,
784
+ }),
785
+ }),
786
+ logType: 'S3_SERVER_ACCESS_LOGS',
787
+ resourceArn: sessionBucket.bucketArn,
788
+ },
789
+ );
790
+ const sessionBucketPolicy = sessionBucket.policy;
791
+ if (sessionBucketPolicy) {
792
+ sessionAccessLogsSource.node.addDependency(sessionBucketPolicy);
793
+ }
794
+ const sessionAccessLogsDestination: CfnDeliveryDestination =
795
+ new CfnDeliveryDestination(this, 'SessionAccessLogsDestination', {
796
+ name: Lazy.string({
797
+ produce: () =>
798
+ Names.uniqueResourceName(sessionAccessLogsDestination, {
799
+ maxLength: 60,
800
+ }),
801
+ }),
802
+ destinationResourceArn: sessionAccessLogs.logGroupArn,
803
+ });
804
+ const sessionAccessLogsDelivery = new CfnDelivery(
805
+ this,
806
+ 'SessionAccessLogsDelivery',
807
+ {
808
+ deliverySourceName: sessionAccessLogsSource.name,
809
+ deliveryDestinationArn: sessionAccessLogsDestination.attrArn,
810
+ },
811
+ );
812
+ sessionAccessLogsDelivery.addDependency(sessionAccessLogsSource);
813
+
814
+ this.agentCoreRuntime = new Runtime(this, 'StoryAgent', {
815
+ runtimeName: Lazy.string({
816
+ produce: () =>
817
+ Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }),
818
+ }),
819
+ protocolConfiguration: ProtocolType.HTTP,
820
+ agentRuntimeArtifact: this.dockerImage,
821
+ authorizerConfiguration: RuntimeAuthorizerConfiguration.usingCognito(
822
+ identity.userPool,
823
+ [identity.userPoolClient],
824
+ ),
825
+ // Receive the caller's Authorization header (validated by the authorizer).
826
+ requestHeaderConfiguration: {
827
+ allowlistedHeaders: ['Authorization'],
828
+ },
829
+ ...restProps,
830
+ environmentVariables: {
831
+ RUNTIME_CONFIG_APP_ID: rc.appConfigApplicationId,
832
+ ...restProps?.environmentVariables,
833
+ },
834
+ });
835
+
836
+ // Grant access for the agent to invoke bedrock models
837
+ this.agentCoreRuntime.addToRolePolicy(
838
+ new PolicyStatement({
839
+ actions: [
840
+ 'bedrock:InvokeModel',
841
+ 'bedrock:InvokeModelWithResponseStream',
842
+ ],
843
+ resources: [
844
+ 'arn:aws:bedrock:*:*:foundation-model/*',
845
+ 'arn:aws:bedrock:*:*:inference-profile/*',
846
+ ],
847
+ }),
848
+ );
849
+
850
+ sessionBucket.grantReadWrite(this.agentCoreRuntime);
851
+
852
+ rc.grantReadAppConfig(this.agentCoreRuntime);
853
+
854
+ rc.set('agentcore', 'agentRuntimes', {
855
+ ...rc.get('agentcore').agentRuntimes,
856
+ StoryAgent: {
857
+ arn: this.agentCoreRuntime.agentRuntimeArn,
858
+ session: {
859
+ bucketName: sessionBucket.bucketName,
860
+ },
861
+ },
862
+ });
863
+
864
+ rc.set('connection', 'agentRuntimes', {
865
+ ...rc.get('connection').agentRuntimes,
866
+ StoryAgent: this.agentCoreRuntime.agentRuntimeArn,
867
+ });
868
+ }
869
+
870
+ /**
871
+ * The principal to grant permissions to.
872
+ */
873
+ public get grantPrincipal(): IPrincipal {
874
+ return this.agentCoreRuntime.grantPrincipal;
875
+ }
876
+
877
+ /**
878
+ * Network connections for this agent runtime.
879
+ */
880
+ public get connections(): Connections {
881
+ return this.agentCoreRuntime.connections;
882
+ }
883
+
884
+ /**
885
+ * The HTTPS invocation URL of the runtime.
886
+ */
887
+ public get invocationUrl(): string {
888
+ // The URL must URL-encode the runtime ARN (':' -> '%3A', '/' -> '%2F').
889
+ // The ARN is a CDK token, so encode at deploy time via Fn.join/Fn.split.
890
+ const encodedArn = Fn.join(
891
+ '%2F',
892
+ Fn.split(
893
+ '/',
894
+ Fn.join('%3A', Fn.split(':', this.agentCoreRuntime.agentRuntimeArn)),
895
+ ),
896
+ );
897
+ return `https://bedrock-agentcore.${Stack.of(this).region}.amazonaws.com/runtimes/${encodedArn}/invocations?qualifier=DEFAULT`;
898
+ }
899
+ }
900
+ ```
901
+
902
+ This configures a CDK `AgentRuntimeArtifact` which uploads your agent Docker image to ECR, and hosts it using AgentCore Runtime. Because we chose `--auth=cognito`, the construct requires the user pool/client identity and authorizes AgentCore Runtime invocations through Cognito, forwarding the caller's `Authorization` header. It also provisions the session bucket the Story Agent's `session.py` reads back at runtime — a KMS-encrypted S3 bucket with server access logs delivered to CloudWatch Logs — grants the agent read/write access to it, grants it access to invoke Bedrock models, and registers its ARN and bucket name in `RuntimeConfig` so both the agent (at runtime, via AppConfig) and the Game API (at synth time, via `invocationUrl`) can find it.
903
+
904
+ 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.
905
+
906
+ </details>
907
+
908
+ ##### Set up the Inventory tools
909
+
910
+ ###### Inventory: TypeScript project
911
+
912
+ Let us create an MCP server to provide tools for our Story Agent to manage a player's inventory.
913
+
914
+ First, we create a TypeScript project:
915
+
916
+ <RunGenerator generator="ts#project" requiredParameters={{name:"inventory"}} noInteractive />
917
+
918
+ This will create an empty TypeScript project.
919
+
920
+ <details>
921
+ <summary>Examine the generated `ts#project` files in detail</summary>
922
+
923
+ The `ts#project` generator generates these files.
924
+
925
+ <FileTree>
926
+ - packages/
927
+ - inventory/
928
+ - src/
929
+ - index.ts entry point with example function
930
+ - project.json project configuration
931
+ - vitest.config.mts test configuration
932
+ - tsconfig.json base typescript configuration for the project
933
+ - tsconfig.lib.json typescript configuration for the project targeted for compilation and bundling
934
+ - tsconfig.spec.json typescript configuration for tests
935
+ - tsconfig.base.json updated to configure an alias for other projects to reference this
936
+ </FileTree>
937
+
938
+ </details>
939
+
940
+ ###### Inventory: MCP server
941
+
942
+ Next, we'll add an MCP server to our TypeScript project:
943
+
944
+ <RunGenerator generator="ts#mcp-server" requiredParameters={{project:"inventory"}} noInteractive />
945
+
946
+ This will add an MCP server.
947
+ <details>
948
+ <summary>Examine the generated `ts#mcp-server` files in detail</summary>
949
+
950
+ The `ts#mcp-server` generator generates these files.
951
+
952
+ <FileTree>
953
+ - packages/
954
+ - inventory/
955
+ - src/mcp-server/
956
+ - index.ts barrel export
957
+ - server.ts creates the MCP server
958
+ - tools/
959
+ - divide.ts example tool
960
+ - resources/
961
+ - sample-guidance.ts example resource
962
+ - stdio.ts entry point for MCP with STDIO transport
963
+ - http.ts entry point for MCP with Streamable HTTP transport
964
+ - Dockerfile builds the image for AgentCore Runtime
965
+ - rolldown.config.ts configuration for bundling the MCP server for deployment to AgentCore
966
+ - common/constructs/
967
+ - src
968
+ - app/mcp-servers/inventory-mcp-server/
969
+ - inventory-mcp-server.ts construct for deploying your inventory MCP server to AgentCore Runtime
970
+ </FileTree>
971
+
972
+ </details>
973
+
974
+ ##### Create the game database
975
+
976
+ 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:
977
+
978
+ <RunGenerator generator="ts#dynamodb" requiredParameters={{name:"DungeonDb"}} noInteractive />
979
+
980
+ :::tip[Local-first development]
981
+ 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.
982
+ :::
983
+
984
+ You will see some new files appear in your file tree.
985
+ <details>
986
+ <summary>Examine the generated `ts#dynamodb` files in detail</summary>
987
+
988
+ The `ts#dynamodb` generator generates these files.
989
+
990
+ <FileTree>
991
+ - packages/
992
+ - dungeon-db/
993
+ - config.json DynamoDB configuration including port, table name, container settings and Global Secondary Indexes
994
+ - src/
995
+ - index.ts entry point and exports
996
+ - client.ts DynamoDB client singleton and table name resolution
997
+ - entities/
998
+ - example.ts example ElectroDB entity (we will replace this)
999
+ - index.ts entity exports
1000
+ - project.json adds the `dev` and `pull-image` targets
1001
+ - common/
1002
+ - scripts/
1003
+ - src/
1004
+ - dynamodb/
1005
+ - create-local-table.ts creates the table in DynamoDB Local
1006
+ - pull-image.ts pulls the DynamoDB Local image
1007
+ - start-container.ts starts the DynamoDB Local container
1008
+ - constructs/
1009
+ - src/
1010
+ - app/dynamodb/
1011
+ - dungeon-db.ts construct for provisioning your table
1012
+ - core/
1013
+ - dynamodb.ts generic DynamoDB table construct
1014
+ </FileTree>
1015
+
1016
+ 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>.
1017
+
1018
+ For more details, refer to the <Link path="guides/ts-dynamodb">ts#dynamodb generator guide</Link>.
1019
+
1020
+ </details>
1021
+
1022
+ ##### Create the User Interface (UI)
1023
+
1024
+ Next, we will create the UI which will allow you to interact with the game.
1025
+
1026
+ ###### Game UI: Website
1027
+
1028
+ To create the UI, create a website called `GameUI` using these steps:
1029
+
1030
+ <RunGenerator generator="ts#website" requiredParameters={{name:"GameUI", ux:"shadcn"}} noInteractive />
1031
+
1032
+ :::note[Shadcn UI]
1033
+ 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.
1034
+ :::
1035
+
1036
+ You will see some new files appear in your file tree.
1037
+
1038
+ <details>
1039
+ <summary>Examine the generated `ts#website` files in detail</summary>
1040
+
1041
+ The `ts#website` generates these files. Let us examine some of the key files highlighted in the file tree:
1042
+
1043
+ <FileTree>
1044
+ - packages/
1045
+ - common/
1046
+ - constructs/
1047
+ - src/
1048
+ - app/ app specific cdk constructs
1049
+ - static-websites/
1050
+ - **game-ui.ts** cdk construct to create your Game UI
1051
+ - core/
1052
+ - static-website.ts generic static website construct
1053
+ - game-ui/
1054
+ - public/
1055
+ - src/
1056
+ - components/
1057
+ - AppLayout/
1058
+ - index.tsx overall page layout using shadcn `SidebarProvider` + header
1059
+ - app-sidebar.tsx default shadcn sidebar with nav items
1060
+ - alert.tsx, spinner.tsx shadcn-wrapped feedback primitives
1061
+ - routes/ @tanstack/react-router file based routes
1062
+ - **index.tsx** root '/' page
1063
+ - __root.tsx all pages use this component as a base
1064
+ - config.ts
1065
+ - **main.tsx** React entrypoint
1066
+ - routeTree.gen.ts this is automatically updated by @tanstack/react-router
1067
+ - styles.css imports shared shadcn globals (Tailwind v4)
1068
+ - index.html
1069
+ - project.json
1070
+ - vite.config.mts
1071
+ - ...
1072
+ - common/
1073
+ - shadcn/ shared shadcn/ui library (theme tokens, `Button`, `Card`, `Input`, `Sidebar`, …) imported by every `ux=shadcn` website
1074
+ - src/components/ui/*
1075
+ - src/styles/globals.css Tailwind + shadcn design tokens
1076
+ - ...
1077
+ </FileTree>
1078
+
1079
+ ```ts
1080
+ // packages/common/constructs/src/app/static-websites/game-ui.ts
1081
+ import * as url from 'url';
1082
+ import { Construct } from 'constructs';
1083
+ import { StaticWebsite, StaticWebsiteProps } from '../../core/index.js';
1084
+
1085
+ export type GameUIProps = Omit<
1086
+ StaticWebsiteProps,
1087
+ 'websiteName' | 'websiteFilePath'
1088
+ >;
1089
+
1090
+ export class GameUI extends StaticWebsite {
1091
+ constructor(scope: Construct, id: string, props?: GameUIProps) {
1092
+ super(scope, id, {
1093
+ ...props,
1094
+ websiteName: 'GameUI',
1095
+ websiteFilePath: url.fileURLToPath(
1096
+ new URL(
1097
+ '../../../../../../dist/packages/game-ui/bundle',
1098
+ import.meta.url,
1099
+ ),
1100
+ ),
1101
+ });
1102
+ }
1103
+ }
1104
+ ```
1105
+
1106
+ 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.
1107
+
1108
+ ```tsx
1109
+ // packages/game-ui/src/main.tsx
1110
+ import React from 'react';
1111
+ import { createRoot } from 'react-dom/client';
1112
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
1113
+ import { routeTree } from './routeTree.gen';
1114
+ import './styles.css';
1115
+
1116
+ export type RouterProviderContext = {};
1117
+
1118
+ const router = createRouter({ routeTree, context: {} });
1119
+
1120
+ declare module '@tanstack/react-router' {
1121
+ interface Register {
1122
+ router: typeof router;
1123
+ }
1124
+ }
1125
+
1126
+ const App = () => <RouterProvider router={router} context={{}} />;
1127
+
1128
+ const root = document.getElementById('root');
1129
+ root &&
1130
+ createRoot(root).render(
1131
+ <React.StrictMode>
1132
+ <App />
1133
+ </React.StrictMode>,
1134
+ );
1135
+ ```
1136
+
1137
+ 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.
1138
+
1139
+ ```tsx
1140
+ // packages/game-ui/src/routes/index.tsx
1141
+ import { createFileRoute } from '@tanstack/react-router';
1142
+
1143
+ export const Route = createFileRoute('/')({
1144
+ component: RouteComponent,
1145
+ });
1146
+
1147
+ function RouteComponent() {
1148
+ return (
1149
+ <div className="text-center">
1150
+ <header>
1151
+ <h1>Welcome</h1>
1152
+ <p>Welcome to your new React website!</p>
1153
+ </header>
1154
+ </div>
1155
+ );
1156
+ }
1157
+ ```
1158
+
1159
+ 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).
1160
+
1161
+ </details>
1162
+
1163
+ ###### Game UI: Auth
1164
+
1165
+ Let us configure our Game UI to require authenticated access via Amazon Cognito using these steps:
1166
+
1167
+ <RunGenerator generator="ts#website#auth" requiredParameters={{cognitoDomain:"game-ui", project:"@dungeon-adventure/game-ui", allowSignup:true}} noInteractive />
1168
+
1169
+ You will see some new files appear/change in your file tree.
1170
+
1171
+ <details>
1172
+ <summary>Examine the generated `ts#website#auth` files in detail</summary>
1173
+
1174
+ The `ts#website#auth` generator updates/generates these files. Let us examine some of the key files highlighted in the file tree:
1175
+
1176
+ <FileTree>
1177
+ - packages/
1178
+ - common/
1179
+ - constructs/
1180
+ - src/
1181
+ - core/
1182
+ - user-identity.ts cdk construct for creating user/identity pools
1183
+ - game-ui/
1184
+ - src/
1185
+ - components/
1186
+ - AppLayout/
1187
+ - index.tsx adds the logged in user/logout to the header
1188
+ - CognitoAuth/
1189
+ - index.tsx manages logging into Cognito
1190
+ - RuntimeConfig/
1191
+ - index.tsx fetches the `runtime-config.json` and provides it to children via context
1192
+ - hooks/
1193
+ - useRuntimeConfig.tsx
1194
+ - **main.tsx** Updated to add Cognito
1195
+ </FileTree>
1196
+
1197
+ ```diff lang="tsx"
1198
+ // packages/game-ui/src/main.tsx
1199
+ +import { useAuth } from 'react-oidc-context';
1200
+ +import CognitoAuth from './components/CognitoAuth';
1201
+ +import { useRuntimeConfig } from './hooks/useRuntimeConfig';
1202
+ +import RuntimeConfigProvider from './components/RuntimeConfig';
1203
+ import React from 'react';
1204
+ import { createRoot } from 'react-dom/client';
1205
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
1206
+ import { routeTree } from './routeTree.gen';
1207
+ import './styles.css';
1208
+ -export type RouterProviderContext = {};
1209
+ +export type RouterProviderContext = {
1210
+ + runtimeConfig?: ReturnType<typeof useRuntimeConfig>;
1211
+ + auth?: ReturnType<typeof useAuth>;
1212
+ +};
1213
+ -const router = createRouter({ routeTree, context: {} });
1214
+ +const router = createRouter({
1215
+ + routeTree,
1216
+ + context: { runtimeConfig: undefined, auth: undefined },
1217
+ +});
1218
+ // Register the router instance for type safety
1219
+ declare module '@tanstack/react-router' {
1220
+ interface Register {
1221
+ router: typeof router;
1222
+ }
1223
+ }
1224
+ -const App = () => <RouterProvider router={router} context={{}} />;
1225
+ +const App = () => {
1226
+ + const auth = useAuth();
1227
+ + const runtimeConfig = useRuntimeConfig();
1228
+ + return <RouterProvider router={router} context={{ runtimeConfig, auth }} />;
1229
+ +};
1230
+ const root = document.getElementById('root');
1231
+ root &&
1232
+ createRoot(root).render(
1233
+ <React.StrictMode>
1234
+ + <RuntimeConfigProvider>
1235
+ + <CognitoAuth>
1236
+ <App />
1237
+ + </CognitoAuth>
1238
+ + </RuntimeConfigProvider>
1239
+ </React.StrictMode>,
1240
+ );
1241
+ ```
1242
+
1243
+ 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.
1244
+
1245
+ </details>
1246
+
1247
+ ###### Game UI: Connect to Game API
1248
+
1249
+ Let us configure our Game UI to connect to our previously created Game API.
1250
+
1251
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/game-ui", targetProject:"@dungeon-adventure/game-api"}} noInteractive />
1252
+
1253
+ You will see some new files have appear/change in your file tree.
1254
+
1255
+ <details>
1256
+ <summary>Examine the UI → tRPC connection files</summary>
1257
+
1258
+ The `connection` generator generates/updates these files. Let us examine some of the key files highlighted in the file tree:
1259
+
1260
+ <FileTree>
1261
+ - packages/
1262
+ - game-ui/
1263
+ - src/
1264
+ - components/
1265
+ - GameApiClientProvider.tsx sets up the GameAPI client
1266
+ - hooks/
1267
+ - **useGameApi.tsx** hooks to call the GameApi
1268
+ - **main.tsx** injects the trpc client providers
1269
+ - package.json
1270
+
1271
+ </FileTree>
1272
+
1273
+ ```tsx
1274
+ // packages/game-ui/src/hooks/useGameApi.tsx
1275
+ import { useContext } from 'react';
1276
+ import { GameApiTRPCContext } from '../components/GameApiClientProvider';
1277
+
1278
+ export const useGameApi = () => {
1279
+ const container = useContext(GameApiTRPCContext);
1280
+ if (!container) {
1281
+ throw new Error('useGameApi must be used within GameApiClientProvider');
1282
+ }
1283
+ return container.optionsProxy;
1284
+ };
1285
+
1286
+ export const useGameApiClient = () => {
1287
+ const container = useContext(GameApiTRPCContext);
1288
+ if (!container) {
1289
+ throw new Error(
1290
+ 'useGameApiClient must be used within GameApiClientProvider',
1291
+ );
1292
+ }
1293
+ return container.client;
1294
+ };
1295
+ ```
1296
+
1297
+ 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>.
1298
+
1299
+ <Aside title="Type-Safe Hooks">
1300
+ 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.
1301
+ </Aside>
1302
+
1303
+ ```diff lang="tsx"
1304
+ // packages/game-ui/src/main.tsx
1305
+ +import GameApiClientProvider from './components/GameApiClientProvider';
1306
+ +import QueryClientProvider from './components/QueryClientProvider';
1307
+ import { useAuth } from 'react-oidc-context';
1308
+ import CognitoAuth from './components/CognitoAuth';
1309
+ import { useRuntimeConfig } from './hooks/useRuntimeConfig';
1310
+ import RuntimeConfigProvider from './components/RuntimeConfig';
1311
+ import React from 'react';
1312
+ import { createRoot } from 'react-dom/client';
1313
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
1314
+ import { routeTree } from './routeTree.gen';
1315
+ import './styles.css';
1316
+ ...
1317
+ const root = document.getElementById('root');
1318
+ root &&
1319
+ createRoot(root).render(
1320
+ <React.StrictMode>
1321
+ <RuntimeConfigProvider>
1322
+ <CognitoAuth>
1323
+ + <QueryClientProvider>
1324
+ + <GameApiClientProvider>
1325
+ <App />
1326
+ + </GameApiClientProvider>
1327
+ + </QueryClientProvider>
1328
+ </CognitoAuth>
1329
+ </RuntimeConfigProvider>
1330
+ </React.StrictMode>,
1331
+ );
1332
+ ```
1333
+
1334
+ The `main.tsx` file has been updated via an AST transform to inject the tRPC providers.
1335
+
1336
+ </details>
1337
+
1338
+ ###### Story Agent: Connect to Inventory MCP Server
1339
+
1340
+ Let us connect our Story Agent to the Inventory MCP server so the agent can discover and invoke the MCP server's tools.
1341
+
1342
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"story", targetProject:"inventory"}} noInteractive />
1343
+
1344
+ <details>
1345
+ <summary>Examine the Story Agent → Inventory MCP connection files</summary>
1346
+
1347
+ The `connection` generator generates/updates these files:
1348
+
1349
+ <FileTree>
1350
+ - packages/
1351
+ - common/
1352
+ - agent\_connection/
1353
+ - dungeon\_adventure\_agent\_connection/
1354
+ - core/
1355
+ - **agentcore\_endpoints.py** Framework-agnostic ARN/URL resolution
1356
+ - **agentcore\_mcp\_transport.py** Framework-agnostic MCP transport
1357
+ - **agentcore\_mcp\_client\_strands.py** Strands MCP client wrapping the transport
1358
+ - auth/ Framework-agnostic SigV4 / session-forwarding `httpx.Auth`
1359
+ - app/
1360
+ - **inventory\_mcp\_server\_client\_strands.py** Strands client for connecting to the Inventory MCP server
1361
+ - **\_\_init\_\_.py** Re-exports per-connection clients
1362
+ - story/
1363
+ - dungeon\_adventure\_story/agent/
1364
+ - **agent.py** Modified to import and use the MCP client
1365
+
1366
+ </FileTree>
1367
+
1368
+ The generator:
1369
+ - Creates a shared `agent_connection` Python project (if it doesn't already exist) with the core `AgentCoreMCPClientStrands`
1370
+ - Generates an `InventoryMcpServerClientStrands` class that handles connecting to the MCP server both locally (direct HTTP) and when deployed (via AgentCore with IAM auth)
1371
+ - Transforms `agent.py` to import the client, create an instance, and wire the MCP server's tools into the agent
1372
+ - Adds the `agent_connection` project as a workspace dependency of the story project
1373
+ - Updates the `dev` target to automatically start the MCP server when running locally
1374
+
1375
+ For more details, refer to the <Link path="guides/connection/py-agent-mcp">Python Agent to MCP connection guide</Link>.
1376
+ </details>
1377
+
1378
+ ###### Game UI: Connect to Story Agent
1379
+
1380
+ 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.
1381
+
1382
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/game-ui", targetProject:"story"}} noInteractive />
1383
+
1384
+ <details>
1385
+ <summary>Examine the UI → Story Agent connection files</summary>
1386
+
1387
+ The `connection` generator generates/updates these files:
1388
+
1389
+ <FileTree>
1390
+ - packages/
1391
+ - game-ui/
1392
+ - src/
1393
+ - components/
1394
+ - **AguiProvider.tsx** `CopilotKitProvider` with every connected AG-UI agent registered
1395
+ - copilot/
1396
+ - **index.tsx** Shadcn-themed `CopilotChat` / `CopilotSidebar` / `CopilotPopup`
1397
+ - ShadcnAssistantMessage.tsx, ShadcnUserMessage.tsx, ShadcnChatInput.tsx, ShadcnCursor.tsx, copilot.css
1398
+ - hooks/
1399
+ - **useAguiStoryAgent.tsx** Builds an `HttpAgent`, injects the Cognito bearer token, and pads `threadId` to AgentCore's 33-char session id
1400
+ - **main.tsx** Wraps `<App />` in `<AguiProvider>`
1401
+
1402
+ </FileTree>
1403
+
1404
+ The generator:
1405
+ - Detects the React website's `ux` (Shadcn here) and vends matching chat components.
1406
+ - Registers every connected agent on a single `CopilotKitProvider` — re-running for another agent just adds another hook.
1407
+ - 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.
1408
+
1409
+ For more details, refer to the <Link path="guides/connection/react-agui">React to AG-UI connection guide</Link>.
1410
+ </details>
1411
+
1412
+ ###### Connect the Game API and Inventory MCP server to the database
1413
+
1414
+ 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.
1415
+
1416
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/game-api", targetProject:"@dungeon-adventure/dungeon-db"}} noInteractive />
1417
+
1418
+ <RunGenerator generator="connection" requiredParameters={{sourceProject:"@dungeon-adventure/inventory", targetProject:"@dungeon-adventure/dungeon-db"}} noInteractive />
1419
+
1420
+ <Aside type="tip" title="One command boots the whole stack locally">
1421
+ 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.
1422
+ </Aside>
1423
+
1424
+ ###### Game UI: Infrastructure
1425
+
1426
+ Let us create the final sub-project for the CDK infrastructure.
1427
+
1428
+ <RunGenerator generator="ts#infra" requiredParameters={{name:"infra"}} noInteractive />
1429
+
1430
+ You will see some new files have appear/change in your file tree.
1431
+
1432
+ <details>
1433
+ <summary>Examine the generated `ts#infra` files in detail</summary>
1434
+
1435
+ The `ts#infra` generator generates/updates these. Let us examine some of the key files highlighted in the file tree:
1436
+
1437
+ <FileTree>
1438
+ - packages/
1439
+ - common/
1440
+ - constructs/
1441
+ - src/
1442
+ - core/
1443
+ - checkov.ts
1444
+ - index.ts
1445
+ - infra
1446
+ - src/
1447
+ - stages/
1448
+ - **application-stage.ts** cdk stacks defined here
1449
+ - stacks/
1450
+ - **application-stack.ts** cdk resources defined here
1451
+ - **main.ts** entrypoint which defines all stages
1452
+ - cdk.json
1453
+ - checkov.yml
1454
+ - project.json
1455
+ - ...
1456
+ - package.json
1457
+ - tsconfig.json add references
1458
+ - tsconfig.base.json add alias
1459
+
1460
+ </FileTree>
1461
+
1462
+ ```ts
1463
+ // packages/infra/src/main.ts
1464
+ import { ApplicationStage } from './stages/application-stage.js';
1465
+ import { App } from '@dungeon-adventure/common-constructs';
1466
+
1467
+ const app = new App();
1468
+
1469
+ // Use this to deploy your own sandbox environment (assumes your CLI credentials)
1470
+ new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', {
1471
+ env: {
1472
+ account: process.env.CDK_DEFAULT_ACCOUNT,
1473
+ region: process.env.CDK_DEFAULT_REGION,
1474
+ },
1475
+ });
1476
+
1477
+ app.synth();
1478
+ ```
1479
+
1480
+ <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>
1481
+
1482
+ This is the entry point for your CDK application.
1483
+
1484
+ ```ts
1485
+ // packages/infra/src/stacks/application-stack.ts
1486
+ import { Stack, StackProps } from 'aws-cdk-lib';
1487
+ import { Construct } from 'constructs';
1488
+
1489
+ export class ApplicationStack extends Stack {
1490
+ constructor(scope: Construct, id: string, props?: StackProps) {
1491
+ super(scope, id, props);
1492
+
1493
+ // The code that defines your stack goes here
1494
+ }
1495
+ }
1496
+ ```
1497
+
1498
+ Let us instantiate our CDK constructs to build our dungeon adventure game.
1499
+
1500
+ </details>
1501
+
1502
+ </Drawer>
1503
+
1504
+ ## Task 3: Update our infrastructure
1505
+
1506
+ Let's update `packages/infra/src/stacks/application-stack.ts` to instantiate some of our generated constructs:
1507
+
1508
+ <E2EDiff before="dungeon-adventure/1/application-stack.ts.original.template" after="dungeon-adventure/1/application-stack.ts.template" lang="ts" />
1509
+
1510
+ :::note[Default Integrations]
1511
+ 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.
1512
+ :::
1513
+
1514
+ ## Task 4: Build the code
1515
+
1516
+ <Drawer title="Nx commands" trigger="Now it's time for us to build our code for the first time">
1517
+
1518
+ ###### Single vs Multiple targets
1519
+
1520
+ 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.
1521
+
1522
+ 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:
1523
+
1524
+ <NxCommands commands={['build infra']} />
1525
+
1526
+ You can also omit the scope, and use the Nx shorthand syntax if you prefer:
1527
+
1528
+ <NxCommands commands={['build infra']} />
1529
+
1530
+ ###### Visualizing your dependencies
1531
+
1532
+ To visualize your dependencies, run:
1533
+
1534
+ <NxCommands commands={['graph']} />
1535
+ <br/>
1536
+
1537
+ <Image src={nxGraphPng} alt="nx-graph.png" width="800" height="600" />
1538
+
1539
+ ###### Caching
1540
+
1541
+ 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:
1542
+
1543
+ <NxCommands commands={['build infra --skip-nx-cache']} />
1544
+ If for whatever reason you ever wanted to clear your cache (stored in the `.nx` folder), you can run the following command:
1545
+
1546
+ <NxCommands commands={['reset']} />
1547
+
1548
+ </Drawer>
1549
+
1550
+ Using the command line, run the following command to fix any lint issues first:
1551
+
1552
+ <PackageManagerShortCommand commands={["lint"]} />
1553
+
1554
+ Then, run the following command for a full build:
1555
+
1556
+ <PackageManagerShortCommand commands={["build"]} />
1557
+
1558
+ You will be prompted with the following:
1559
+
1560
+ ```bash
1561
+ NX The workspace is out of sync
1562
+
1563
+ [@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
1564
+
1565
+ This will result in an error in CI.
1566
+
1567
+ ? Would you like to sync the identified changes to get your workspace up to date? …
1568
+ Yes, sync the changes and run the tasks
1569
+ No, run the tasks without syncing the changes
1570
+ ```
1571
+
1572
+ 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.
1573
+
1574
+ 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!
1575
+
1576
+ 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.
1577
+
1578
+ Congratulations! You've created all of the required sub-projects required to start implementing the core of our AI Dungeon Adventure game. 🎉🎉🎉