@aws/nx-plugin-mcp 1.0.0-rc.95 → 1.0.0-rc.97

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 (66) hide show
  1. package/bin/aws-nx-mcp.js +61 -14
  2. package/docs/get_started/existing-project.mdx +6 -3
  3. package/docs/get_started/quick-start.mdx +8 -0
  4. package/docs/get_started/tutorials/dungeon-game/1.mdx +12 -14
  5. package/docs/get_started/tutorials/dungeon-game/2.mdx +10 -2
  6. package/docs/get_started/tutorials/dungeon-game/3.mdx +4 -0
  7. package/docs/get_started/tutorials/dungeon-game/4.mdx +2 -2
  8. package/docs/guides/agentcore-gateway.mdx +4 -2
  9. package/docs/guides/agentcore-harness.mdx +2 -1
  10. package/docs/guides/astro-docs.mdx +25 -7
  11. package/docs/guides/connection/py-agent-a2a.mdx +2 -0
  12. package/docs/guides/connection/py-agent-dynamodb.mdx +1 -1
  13. package/docs/guides/connection/py-agent-gateway.mdx +3 -0
  14. package/docs/guides/connection/py-agent-mcp.mdx +18 -4
  15. package/docs/guides/connection/py-agent-rdb.mdx +5 -4
  16. package/docs/guides/connection/py-fast-api-dynamodb.mdx +1 -1
  17. package/docs/guides/connection/py-fast-api-rdb.mdx +7 -2
  18. package/docs/guides/connection/py-mcp-server-dynamodb.mdx +1 -1
  19. package/docs/guides/connection/py-mcp-server-rdb.mdx +6 -6
  20. package/docs/guides/connection/react-agui.mdx +34 -25
  21. package/docs/guides/connection/react-fastapi.mdx +114 -116
  22. package/docs/guides/connection/react-py-agent.mdx +4 -0
  23. package/docs/guides/connection/react-smithy.mdx +152 -98
  24. package/docs/guides/connection/react-trpc.mdx +13 -6
  25. package/docs/guides/connection/smithy-dynamodb.mdx +2 -8
  26. package/docs/guides/connection/smithy-rdb.mdx +3 -6
  27. package/docs/guides/connection/trpc-rdb.mdx +6 -6
  28. package/docs/guides/connection/ts-agent-a2a.mdx +4 -4
  29. package/docs/guides/connection/ts-agent-dynamodb.mdx +13 -11
  30. package/docs/guides/connection/ts-agent-gateway.mdx +2 -0
  31. package/docs/guides/connection/ts-agent-mcp.mdx +19 -7
  32. package/docs/guides/connection/ts-agent-rdb.mdx +2 -2
  33. package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +11 -7
  34. package/docs/guides/connection/ts-mcp-server-rdb.mdx +4 -3
  35. package/docs/guides/docker-bundling.mdx +23 -3
  36. package/docs/guides/fastapi.mdx +16 -5
  37. package/docs/guides/py-agent.mdx +129 -54
  38. package/docs/guides/py-mcp-server.mdx +3 -1
  39. package/docs/guides/py-rdb.mdx +13 -4
  40. package/docs/guides/python-lambda-function.mdx +8 -8
  41. package/docs/guides/python-project.mdx +28 -25
  42. package/docs/guides/react-website-auth.mdx +8 -8
  43. package/docs/guides/react-website.mdx +46 -27
  44. package/docs/guides/runtime-config.mdx +24 -4
  45. package/docs/guides/security.mdx +1 -1
  46. package/docs/guides/terraform-project.mdx +8 -2
  47. package/docs/guides/trpc.mdx +96 -12
  48. package/docs/guides/ts-agent.mdx +17 -3
  49. package/docs/guides/ts-dcr-proxy.mdx +24 -6
  50. package/docs/guides/ts-lambda-function.mdx +7 -1
  51. package/docs/guides/ts-mcp-server.mdx +45 -15
  52. package/docs/guides/ts-rdb.mdx +9 -2
  53. package/docs/guides/ts-smithy-api.mdx +76 -7
  54. package/docs/guides/typescript-infrastructure.mdx +24 -10
  55. package/docs/guides/typescript-project.mdx +12 -5
  56. package/docs/guides/workspace.mdx +21 -9
  57. package/docs/snippets/api/type-safe-api-integrations.mdx +2 -0
  58. package/docs/snippets/connection/a2a-infrastructure.mdx +3 -0
  59. package/docs/snippets/connection/infra-project-prerequisite.mdx +8 -0
  60. package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +1 -1
  61. package/docs/snippets/connection/ts-lambda-rdb-ssl-requirements.mdx +1 -1
  62. package/docs/snippets/connection/ts-mcp-server-rdb-ssl-requirements.mdx +1 -1
  63. package/docs/snippets/required-prerequisites.mdx +1 -1
  64. package/package.json +1 -1
  65. package/src/init/schema.json +5 -0
  66. package/src/py/project/schema.json +3 -1
@@ -163,15 +163,25 @@ You can edit `agent.py` to add tools, configure the model and customize the syst
163
163
 
164
164
  Tools are functions that the AI agent can call to perform actions. Both frameworks use a decorator-based approach for defining tools, derive the tool name and description from the function name and docstring, and generate the input schema from your type hints.
165
165
 
166
+ Define the tool, then add it to the `tools` list inside `get_agent()`:
167
+
166
168
  <Tabs syncKey="agent-framework">
167
169
  <TabItem label="Strands" _filter={{ framework: 'strands' }}>
168
- ```python
170
+ ```python title="packages/my-project/my_module/agent/agent.py" {16-20,32}
171
+ from contextlib import contextmanager
172
+
169
173
  from strands import Agent, tool
174
+ from strands.hooks import HookCallback, HookProvider
175
+ from strands_tools import current_time
176
+ from my_scope_agent_connection import log_model_errors, log_tool_errors
177
+
178
+ from .session import get_session_manager
179
+
170
180
 
171
181
  @tool
172
- def calculate_sum(numbers: list[int]) -> int:
173
- """Calculate the sum of a list of numbers"""
174
- return sum(numbers)
182
+ def subtract(a: int, b: int) -> int:
183
+ return a - b
184
+
175
185
 
176
186
  @tool
177
187
  def get_weather(city: str) -> str:
@@ -179,23 +189,41 @@ def get_weather(city: str) -> str:
179
189
  # Your weather API integration here
180
190
  return f"Weather in {city}: Sunny, 25°C"
181
191
 
182
- # Add tools to your agent
183
- agent = Agent(
184
- system_prompt="You are a helpful assistant with access to various tools.",
185
- tools=[calculate_sum, get_weather],
186
- )
192
+
193
+ AGENT_HOOKS: list[HookProvider | HookCallback] = [log_model_errors, log_tool_errors]
194
+
195
+
196
+ @contextmanager
197
+ def get_agent():
198
+ yield Agent(
199
+ name="MyAgent",
200
+ description="MyAgent Strands Agent",
201
+ system_prompt="You are a helpful assistant with access to various tools.",
202
+ tools=[subtract, current_time, get_weather],
203
+ hooks=AGENT_HOOKS,
204
+ session_manager=get_session_manager(),
205
+ )
187
206
  ```
188
207
  </TabItem>
189
208
  <TabItem label="LangChain" _filter={{ framework: 'langchain' }}>
190
- ```python
209
+ ```python title="packages/my-project/my_module/agent/agent.py" {19-23,30}
210
+ import os
211
+
191
212
  from langchain.agents import create_agent
192
213
  from langchain_aws import ChatBedrockConverse
193
214
  from langchain_core.tools import tool
194
215
 
216
+ from .session import get_checkpointer
217
+
218
+ REGION = os.environ.get("AWS_REGION", "us-east-1")
219
+ MODEL_ID = os.environ.get("MODEL_ID", "global.anthropic.claude-haiku-4-5-20251001-v1:0")
220
+
221
+
195
222
  @tool
196
- def calculate_sum(numbers: list[int]) -> int:
197
- """Calculate the sum of a list of numbers"""
198
- return sum(numbers)
223
+ def subtract(a: int, b: int) -> int:
224
+ """Subtract b from a."""
225
+ return a - b
226
+
199
227
 
200
228
  @tool
201
229
  def get_weather(city: str) -> str:
@@ -203,12 +231,15 @@ def get_weather(city: str) -> str:
203
231
  # Your weather API integration here
204
232
  return f"Weather in {city}: Sunny, 25°C"
205
233
 
206
- # Add tools to your agent
207
- agent = create_agent(
208
- model=ChatBedrockConverse(model=MODEL_ID, region_name=REGION),
209
- tools=[calculate_sum, get_weather],
210
- system_prompt="You are a helpful assistant with access to various tools.",
211
- )
234
+
235
+ def get_agent():
236
+ model = ChatBedrockConverse(model=MODEL_ID, region_name=REGION)
237
+ return create_agent(
238
+ model=model,
239
+ tools=[subtract, get_weather],
240
+ system_prompt="You are a helpful assistant with access to various tools.",
241
+ checkpointer=get_checkpointer(),
242
+ )
212
243
  ```
213
244
  </TabItem>
214
245
  </Tabs>
@@ -217,28 +248,45 @@ agent = create_agent(
217
248
 
218
249
  <Tabs syncKey="agent-framework">
219
250
  <TabItem label="Strands" _filter={{ framework: 'strands' }}>
220
- Strands provides a collection of pre-built tools through the `strands-tools` package:
221
-
222
- ```python
223
- from strands_tools import current_time, http_request, file_read
224
-
225
- agent = Agent(
226
- system_prompt="You are a helpful assistant.",
227
- tools=[current_time, http_request, file_read],
228
- )
251
+ Strands provides a collection of pre-built tools through the `strands-agents-tools` package, which the generator already adds to your project's `pyproject.toml`. Import the tools you want and add them to `get_agent()`:
252
+
253
+ ```python title="packages/my-project/my_module/agent/agent.py" {1,11}
254
+ from strands_tools import current_time, file_read, http_request
255
+
256
+ # ...
257
+
258
+ @contextmanager
259
+ def get_agent():
260
+ yield Agent(
261
+ name="MyAgent",
262
+ description="MyAgent Strands Agent",
263
+ system_prompt="You are a helpful assistant.",
264
+ tools=[current_time, file_read, http_request],
265
+ hooks=AGENT_HOOKS,
266
+ session_manager=get_session_manager(),
267
+ )
229
268
  ```
230
269
  </TabItem>
231
270
  <TabItem label="LangChain" _filter={{ framework: 'langchain' }}>
232
- LangChain provides a large ecosystem of [tools and integrations](https://docs.langchain.com/oss/python/integrations/tools). Install the relevant integration package, then pass the tools to `create_agent`:
271
+ LangChain provides a large ecosystem of [tools and integrations](https://docs.langchain.com/oss/python/integrations/tools), each distributed in its own package. The generator does not add these, so install the one you need first — for example `langchain-community`, which the search tool below comes from:
233
272
 
234
- ```python
273
+ <NxCommands commands={['run <project-name>:add langchain-community']} />
274
+
275
+ Then import the tools and add them to `get_agent()`:
276
+
277
+ ```python title="packages/my-project/my_module/agent/agent.py" {1,9}
235
278
  from langchain_community.tools import DuckDuckGoSearchRun
236
279
 
237
- agent = create_agent(
238
- model=ChatBedrockConverse(model=MODEL_ID, region_name=REGION),
239
- tools=[DuckDuckGoSearchRun()],
240
- system_prompt="You are a helpful assistant.",
241
- )
280
+ # ...
281
+
282
+ def get_agent():
283
+ model = ChatBedrockConverse(model=MODEL_ID, region_name=REGION)
284
+ return create_agent(
285
+ model=model,
286
+ tools=[DuckDuckGoSearchRun()],
287
+ system_prompt="You are a helpful assistant.",
288
+ checkpointer=get_checkpointer(),
289
+ )
242
290
  ```
243
291
  </TabItem>
244
292
  </Tabs>
@@ -247,33 +295,50 @@ agent = create_agent(
247
295
 
248
296
  <Tabs syncKey="agent-framework">
249
297
  <TabItem label="Strands" _filter={{ framework: 'strands' }}>
250
- By default, Strands agents use Claude Sonnet 4.6 on Amazon Bedrock, but you can customize the model provider. See the [Strands documentation on model providers](https://strandsagents.com/docs/user-guide/concepts/model-providers/) for configuration options:
298
+ The generated agent uses the default Strands model on Amazon Bedrock. To configure it, pass a `model` to the `Agent`. See the [Strands documentation on model providers](https://strandsagents.com/docs/user-guide/concepts/model-providers/) for the available providers and their options:
251
299
 
252
- ```python
253
- from strands import Agent
300
+ ```python title="packages/my-project/my_module/agent/agent.py" {1,5-9,14}
254
301
  from strands.models import BedrockModel
255
302
 
256
- # Create a BedrockModel
257
- bedrock_model = BedrockModel(
303
+ # ...
304
+
305
+ MODEL = BedrockModel(
258
306
  model_id="anthropic.claude-sonnet-4-20250514-v1:0",
259
307
  region_name="us-west-2",
260
308
  temperature=0.3,
261
309
  )
262
310
 
263
- agent = Agent(model=bedrock_model)
311
+ @contextmanager
312
+ def get_agent():
313
+ yield Agent(
314
+ model=MODEL,
315
+ name="MyAgent",
316
+ description="MyAgent Strands Agent",
317
+ system_prompt="You are a helpful assistant.",
318
+ tools=[subtract, current_time],
319
+ hooks=AGENT_HOOKS,
320
+ session_manager=get_session_manager(),
321
+ )
264
322
  ```
265
323
  </TabItem>
266
324
  <TabItem label="LangChain" _filter={{ framework: 'langchain' }}>
267
- LangChain agents use a [`ChatBedrockConverse`](https://docs.langchain.com/oss/python/integrations/chat/bedrock_converse) model. The generated agent reads the model id and region from the `MODEL_ID` and `AWS_REGION` environment variables, but you can configure the model directly in `agent.py`:
268
-
269
- ```python
270
- from langchain_aws import ChatBedrockConverse
271
-
272
- model = ChatBedrockConverse(
273
- model="anthropic.claude-sonnet-4-20250514-v1:0",
274
- region_name="us-west-2",
275
- temperature=0.3,
276
- )
325
+ LangChain agents use a [`ChatBedrockConverse`](https://docs.langchain.com/oss/python/integrations/chat/bedrock_converse) model. The generated agent reads the model id and region from the `MODEL_ID` and `AWS_REGION` environment variables, which the infrastructure sets for the deployed agent. To configure the model further, add arguments where it is constructed in `get_agent()`:
326
+
327
+ ```python title="packages/my-project/my_module/agent/agent.py" {4-8}
328
+ # ...
329
+
330
+ def get_agent():
331
+ model = ChatBedrockConverse(
332
+ model=MODEL_ID,
333
+ region_name=REGION,
334
+ temperature=0.3,
335
+ )
336
+ return create_agent(
337
+ model=model,
338
+ tools=[subtract],
339
+ system_prompt="You are a helpful assistant.",
340
+ checkpointer=get_checkpointer(),
341
+ )
277
342
  ```
278
343
  </TabItem>
279
344
  </Tabs>
@@ -425,6 +490,10 @@ If you have added multiple components to your project (agents, MCP servers, etc.
425
490
 
426
491
  This uses `uv run` to execute your Agent using the [Bedrock AgentCore Python SDK](https://github.com/aws/bedrock-agentcore-sdk-python).
427
492
 
493
+ The agent listens on a port assigned from the workspace pool when it was generated. Read it from `metadata.ports` in the project's `project.json`, or from the `--port` flag in the `<your-agent-name>-dev` target's command. The examples below use `8081`, the port a first HTTP agent is assigned in a fresh workspace.
494
+
495
+ A `<your-agent-name>-serve` target is also generated, which runs the agent against your deployed infrastructure and therefore requires `RUNTIME_CONFIG_APP_ID` to be set. See the <Link path="guides/local-development">Local Development</Link> guide for the difference between `dev` and `serve`.
496
+
428
497
  ### Chat with Your Agent
429
498
 
430
499
  The generator configures a `<your-agent-name>-chat` Nx target that drops you into an interactive terminal chat with your agent.
@@ -439,11 +508,13 @@ Then, in another terminal, start the chat:
439
508
 
440
509
  The generator emits a `scripts/<your-agent-name>/chat.ts` for every protocol. It connects to the local agent by default, or to your deployed agent when `RUNTIME_CONFIG_APP_ID` is set (see [Chat with your deployed agent](#chat-with-your-deployed-agent) below).
441
510
 
442
- For **HTTP** agents, the chat script uses a type-safe TypeScript client generated from the agent's OpenAPI spec. The generator also emits:
511
+ <OptionFilter when={{ protocol: 'http' }} description="OpenAPI client generation for the chat script">
512
+ For HTTP agents, the chat script uses a type-safe TypeScript client generated from the agent's OpenAPI spec. The generator also emits:
443
513
 
444
- - `scripts/<your-agent-name>_openapi.py` — a small script that exports the agent's OpenAPI spec
514
+ - `scripts/<your_agent_name>_openapi.py` — a small script that exports the agent's OpenAPI spec (named with your agent's name in `snake_case`)
445
515
  - An `<your-agent-name>-openapi` Nx target that runs it
446
516
  - An `<your-agent-name>-generate-client` Nx target that produces a type-safe TypeScript client under `scripts/<your-agent-name>/generated/`
517
+ </OptionFilter>
447
518
 
448
519
  When you customize the agent's input shape (e.g. add new fields to `InvokeInput`), update `chat.ts` to pass the new fields when invoking the agent and the rest works automatically.
449
520
 
@@ -567,7 +638,11 @@ When running locally (`LOCAL_DEV=true`, set automatically by the `-dev` target),
567
638
  <OptionFilter when={{ protocol: 'http' }} description="FastAPI HTTP invocation details">
568
639
  ### Invoke the Local Server
569
640
 
570
- To invoke an Agent running locally via the `<your-agent-name>-serve` target, you can send a simple POST request to `/invocations` on the port your local agent is running on. For example, with `curl`:
641
+ Start your agent with the `<your-agent-name>-dev` target:
642
+
643
+ <NxCommands commands={['agent-dev your-project']} />
644
+
645
+ Then send a POST request to `/invocations` on the port your local agent is running on. Substitute the port assigned to your agent — read it from `metadata.ports` in the project's `project.json`, or from the `--port` flag in the `<your-agent-name>-dev` target's command. A first HTTP agent in a fresh workspace is assigned `8081`:
571
646
 
572
647
  ```bash
573
648
  curl -N -X POST http://localhost:8081/invocations \
@@ -157,7 +157,9 @@ If you would like to run your MCP server locally using [Streamable HTTP transpor
157
157
 
158
158
  <NxCommands commands={['your-server-name-serve your-project']} />
159
159
 
160
- This command uses `uv run uvicorn --reload` to run your MCP server with HTTP transport (typically on port `8000`), and automatically restarts when files change.
160
+ This command uses `uv run uvicorn --reload` to run your MCP server with HTTP transport, and automatically restarts when files change.
161
+
162
+ Each MCP server is assigned its own port, starting at `8000`, so several servers can run side by side in the same workspace. Read the port your server listens on from its `<your-server-name>-serve` target in `project.json`.
161
163
 
162
164
  <OptionFilter when={{ infra: ['agentcore', 'agentcore-ecr'] }} description="Bedrock AgentCore Runtime deployment details">
163
165
  ## Deploying Your MCP Server to Bedrock AgentCore Runtime
@@ -33,22 +33,31 @@ The generator creates the following project structure in the `<directory>/<name>
33
33
 
34
34
  <FileTree>
35
35
  - \<name>
36
- - \_\_init\_\_.py Package exports (`get_engine`, `session_context`)
36
+ - \_\_init\_\_.py Package exports
37
37
  - connection.py Database engine and session factory with IAM authentication
38
38
  - utils.py Runtime config and local development helpers
39
39
  - migration_handler.py Lambda handler that runs Alembic migrations during deployment
40
40
  - create_db_user_handler.py Lambda handler that creates the application database user during deployment
41
41
  - models
42
+ - \_\_init\_\_.py Model exports, imported by Alembic to discover your tables
42
43
  - example.py Example SQLModel table definition
43
44
  - migrations
44
45
  - versions Alembic-generated migration scripts
45
46
  - env.py Alembic environment (connects to the database)
46
47
  - script.py.mako Alembic migration script template
48
+ - tests
49
+ - \_\_init\_\_.py Module initialisation
50
+ - conftest.py Test configuration
51
+ - test_noop.py Placeholder test
47
52
  - alembic.ini Alembic configuration
48
53
  - config.json Local development connection details and runtime config key
49
54
  - Dockerfile.migration Container image for the migration handler
50
55
  - Dockerfile.create-db-user Container image for the create-db-user handler
51
56
  - project.json Project configuration and build targets
57
+ - pyproject.toml Packaging configuration file used by UV
58
+ - README.md Project README
59
+ - .python-version Contains the project's Python version
60
+ - .gitignore Files excluded from version control
52
61
  </FileTree>
53
62
 
54
63
  Local development scripts are shared across all database projects and generated into `packages/common/scripts/`:
@@ -137,12 +146,12 @@ Import `session_context` from your database package and use it as an async conte
137
146
  ```python
138
147
  from sqlmodel import select
139
148
 
140
- from my_scope.my_db import session_context
141
- from my_scope.my_db.models.example import ExampleModel
149
+ from my_scope_my_db import session_context
150
+ from my_scope_my_db.models.example import ExampleModel
142
151
 
143
152
  async def example():
144
153
  async with session_context() as session:
145
- results = (await session.execute(select(ExampleModel))).all()
154
+ results = (await session.execute(select(ExampleModel))).scalars().all()
146
155
  ```
147
156
 
148
157
  The database client automatically:
@@ -90,7 +90,8 @@ metrics: Metrics = Metrics()
90
90
  tracer: Tracer = Tracer()
91
91
 
92
92
  @tracer.capture_lambda_handler
93
- @metrics.log_metrics
93
+ @logger.inject_lambda_context
94
+ @metrics.log_metrics(capture_cold_start_metric=True)
94
95
  @event_parser(model=EventBridgeModel)
95
96
  def lambda_handler(event: EventBridgeModel, context: LambdaContext):
96
97
  logger.info("Received event", extra={"event": event.model_dump() })
@@ -127,11 +128,10 @@ def lambda_handler(event: EventBridgeModel, context: LambdaContext):
127
128
  It is recommended to set the correlation id for all unique requests for easier debugging and monitoring. Please refer to the [aws powertools logger](https://docs.powertools.aws.dev/lambda/python/2.22.0/core/logger/#setting-a-correlation-id) documentation for correlation id best practices.
128
129
  :::
129
130
 
130
- The logger automatically includes:
131
+ The handler is decorated with [`@logger.inject_lambda_context`](https://docs.powertools.aws.dev/lambda/python/latest/core/logger/#inject-lambda-context), so every log record it emits also carries:
131
132
 
132
- - Event requests
133
- - Lambda context information
134
- - Cold start indicators
133
+ - `function_name`, `function_memory_size`, `function_arn` and `function_request_id` from the Lambda context
134
+ - `cold_start`, indicating whether the invocation initialised a new execution environment
135
135
 
136
136
  #### Tracing
137
137
 
@@ -157,9 +157,9 @@ def lambda_handler(event: EventBridgeModel, context: LambdaContext):
157
157
 
158
158
  Default metrics include:
159
159
 
160
- - Invocation counts
161
- - Success/failure counts
162
- - Cold start metrics
160
+ - `InvocationCount`, added on every invocation
161
+ - `SuccessCount` and `ErrorCount`, added by the handler's `try`/`except`
162
+ - `ColdStart`, emitted by [`@metrics.log_metrics(capture_cold_start_metric=True)`](https://docs.powertools.aws.dev/lambda/python/latest/core/metrics/#capturing-cold-start-metric) when the invocation initialises a new execution environment
163
163
 
164
164
  ### Type Safety
165
165
 
@@ -3,11 +3,35 @@ title: Python Projects
3
3
  description: Reference documentation for Python Projects
4
4
  generator: py#project
5
5
  ---
6
- import { FileTree } from '@astrojs/starlight/components';
6
+ import { Code, FileTree } from '@astrojs/starlight/components';
7
+ import Link from '@components/link.astro';
7
8
  import RunGenerator from '@components/run-generator.astro';
8
9
  import GeneratorParameters from '@components/generator-parameters.astro';
9
10
  import NxCommands from '@components/nx-commands.astro';
10
11
  import PackageManagerShortCommand from '@components/package-manager-short-command.astro';
12
+ import { LAMBDA_RUNTIME_VERSIONS } from '../../../../../../packages/nx-plugin/src/utils/versions';
13
+
14
+ export const bundleTarget = `{
15
+ "targets": {
16
+ "bundle": {
17
+ "dependsOn": ["bundle-x86"]
18
+ },
19
+ "bundle-x86": {
20
+ "cache": true,
21
+ "inputs": ["production", "^production"],
22
+ "executor": "nx:run-commands",
23
+ "outputs": ["{workspaceRoot}/dist/{projectRoot}/bundle-x86"],
24
+ "options": {
25
+ "commands": [
26
+ "uv export --frozen --no-dev --no-editable --project {projectRoot} --package my_scope.my_library -o dist/{projectRoot}/bundle-x86/requirements.txt",
27
+ "uv pip install -n --no-deps --no-installer-metadata --no-compile-bytecode --python-platform x86_64-manylinux_2_28 --python-version ${LAMBDA_RUNTIME_VERSIONS.python} --target dist/{projectRoot}/bundle-x86 -r dist/{projectRoot}/bundle-x86/requirements.txt"
28
+ ],
29
+ "parallel": false
30
+ },
31
+ "dependsOn": ["compile"]
32
+ }
33
+ }
34
+ }`;
11
35
 
12
36
  The Python project generator can be used to create a modern [Python](https://www.python.org/) library or application configured with best practices, managed with [UV](https://docs.astral.sh/uv/), a single lockfile and virtual environment in an [UV workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/), [pytest](https://docs.pytest.org/en/stable/) for running tests, [Ruff](https://docs.astral.sh/ruff/) for static analysis, and [ty](https://docs.astral.sh/ty/) for type checking.
13
37
 
@@ -87,30 +111,9 @@ This will add the dependency to your project's `pyproject.toml` file, and update
87
111
 
88
112
  #### Runtime Code
89
113
 
90
- When you use your Python project as runtime code (for example as the handler for an AWS lambda function), you will need to create a bundle of the source code and all its dependencies. You can achieve this by adding a target such as the following to your `project.json` file:
114
+ When you use your Python project as runtime code (for example as the handler for an AWS lambda function), you will need to create a bundle of the source code and all its dependencies. The <Link path="guides/python-lambda-function">`py#lambda-function`</Link>, <Link path="guides/fastapi">`py#api`</Link> and <Link path="guides/py-mcp-server">`py#mcp-server`</Link> generators add this for you. To add one by hand, add targets such as the following to your `project.json` file, matching the shape those generators vend:
91
115
 
92
- ```json title="project.json"
93
- {
94
- ...
95
- "targets": {
96
- ...
97
- "bundle": {
98
- "cache": true,
99
- "inputs": ["production", "^production"],
100
- "executor": "nx:run-commands",
101
- "outputs": ["{workspaceRoot}/dist/packages/my_library/bundle"],
102
- "options": {
103
- "commands": [
104
- "uv export --frozen --no-dev --no-editable --project packages/my_library --package my_scope.my_library -o dist/packages/my_library/bundle/requirements.txt",
105
- "uv pip install -n --no-deps --no-installer-metadata --no-compile-bytecode --python-platform x86_64-manylinux_2_28 --python `uv python pin` --target dist/packages/my_library/bundle -r dist/packages/my_library/bundle/requirements.txt"
106
- ],
107
- "parallel": false
108
- },
109
- "dependsOn": ["compile"]
110
- },
111
- },
112
- }
113
- ```
116
+ <Code lang="json" title="project.json" code={bundleTarget} />
114
117
 
115
118
  ### Installing
116
119
 
@@ -158,7 +161,7 @@ The deploy targets depend on `assemble`, so deploying builds only what it is abo
158
161
 
159
162
  ### Writing Tests
160
163
 
161
- Tests should be written in the `test` directory within your project, in python files prefixed with `test_`, for example:
164
+ Tests should be written in the `tests` directory within your project, in python files prefixed with `test_`, for example:
162
165
 
163
166
  <FileTree>
164
167
  - my_library
@@ -106,9 +106,9 @@ browser -> backend: IAM/Cognito
106
106
 
107
107
  #### Threat protection
108
108
 
109
- The User Pool is created on the Cognito [Plus feature plan](https://docs.aws.amazon.com/cognito/latest/developerguide/feature-plans-features-plus.html) with [threat protection](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-threat-protection.html) set to `AUDIT` mode for standard authentication. In audit mode, Cognito assigns a risk level to each sign-in and logs the assessment to CloudWatch without blocking users.
109
+ The User Pool is created on the Cognito [Plus feature plan](https://docs.aws.amazon.com/cognito/latest/developerguide/feature-plans-features-plus.html) with [threat protection](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-threat-protection.html) in **audit-only** enforcement for standard authentication. In audit-only, Cognito assigns a risk level to each sign-in and records the assessment without blocking users.
110
110
 
111
- Once you have observed the risk assessments for your users, you can switch to full-function enforcement to automatically respond to risky activity (for example requiring MFA or blocking sign-in):
111
+ Once you have observed the risk assessments for your users, you can switch to full-function enforcement to automatically respond to risky activity (for example requiring MFA or blocking sign-in), then configure the response per risk level via [adaptive authentication](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-adaptive-authentication.html):
112
112
 
113
113
  <Infrastructure>
114
114
  <Fragment slot="cdk">
@@ -187,13 +187,13 @@ This rule flags loopback addresses in query arguments as [SSRF](https://docs.aws
187
187
  You will need to add the user identity infrastructure to your stack, declaring it _before_ the website:
188
188
 
189
189
  ```ts title="packages/infra/src/stacks/application-stack.ts" {3,9}
190
- import { Stack } from 'aws-cdk-lib';
190
+ import { Stack, StackProps } from 'aws-cdk-lib';
191
191
  import { Construct } from 'constructs';
192
192
  import { MyWebsite, UserIdentity } from '@my-scope/common-constructs';
193
193
 
194
194
  export class ApplicationStack extends Stack {
195
- constructor(scope: Construct, id: string) {
196
- super(scope, id);
195
+ constructor(scope: Construct, id: string, props?: StackProps) {
196
+ super(scope, id, props);
197
197
 
198
198
  new UserIdentity(this, 'Identity');
199
199
 
@@ -256,13 +256,13 @@ In order to grant authenticated users access to perform certain actions, such as
256
256
  <Infrastructure>
257
257
  <Fragment slot="cdk">
258
258
  ```ts title="packages/infra/src/stacks/application-stack.ts" {12}
259
- import { Stack } from 'aws-cdk-lib';
259
+ import { Stack, StackProps } from 'aws-cdk-lib';
260
260
  import { Construct } from 'constructs';
261
261
  import { MyWebsite, UserIdentity, MyApi } from '@my-scope/common-constructs';
262
262
 
263
263
  export class ApplicationStack extends Stack {
264
- constructor(scope: Construct, id: string) {
265
- super(scope, id);
264
+ constructor(scope: Construct, id: string, props?: StackProps) {
265
+ super(scope, id, props);
266
266
 
267
267
  const identity = new UserIdentity(this, 'Identity');
268
268
  const api = new MyApi(this, 'MyApi', {