@aws/nx-plugin-mcp 1.0.0-rc.37 → 1.0.0-rc.39

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