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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/bin/aws-nx-mcp.js +12317 -10933
  2. package/docs/get_started/building-with-ai.mdx +116 -0
  3. package/docs/get_started/concepts.mdx +67 -0
  4. package/docs/get_started/existing-project.mdx +180 -0
  5. package/docs/get_started/graph-builder.mdx +39 -0
  6. package/docs/get_started/quick-start.mdx +277 -0
  7. package/docs/get_started/tutorials/contribute-generator.mdx +408 -0
  8. package/docs/get_started/tutorials/dungeon-game/1.mdx +1301 -0
  9. package/docs/get_started/tutorials/dungeon-game/2.mdx +237 -0
  10. package/docs/get_started/tutorials/dungeon-game/3.mdx +76 -0
  11. package/docs/get_started/tutorials/dungeon-game/4.mdx +164 -0
  12. package/docs/get_started/tutorials/dungeon-game/overview.mdx +145 -0
  13. package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +41 -0
  14. package/docs/get_started/tutorials/existing-project.mdx +4 -0
  15. package/docs/get_started/upgrading.mdx +147 -0
  16. package/docs/guides/agentcore-gateway.mdx +490 -0
  17. package/docs/guides/agentcore-harness.mdx +275 -0
  18. package/docs/guides/astro-docs.mdx +8 -0
  19. package/docs/guides/connection/agentcore-gateway-agent.mdx +222 -0
  20. package/docs/guides/connection/agentcore-gateway-gateway.mdx +154 -0
  21. package/docs/guides/connection/agentcore-gateway-mcp.mdx +134 -0
  22. package/docs/guides/connection/py-agent-a2a.mdx +48 -16
  23. package/docs/guides/connection/py-agent-dynamodb.mdx +116 -0
  24. package/docs/guides/connection/py-agent-gateway.mdx +178 -0
  25. package/docs/guides/connection/py-agent-mcp.mdx +43 -14
  26. package/docs/guides/connection/py-agent-rdb.mdx +178 -0
  27. package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
  28. package/docs/guides/connection/py-fast-api-rdb.mdx +184 -0
  29. package/docs/guides/connection/py-mcp-server-dynamodb.mdx +116 -0
  30. package/docs/guides/connection/py-mcp-server-rdb.mdx +187 -0
  31. package/docs/guides/connection/react-agentcore-gateway.mdx +112 -0
  32. package/docs/guides/connection/react-agui.mdx +13 -13
  33. package/docs/guides/connection/react-fastapi.mdx +38 -2
  34. package/docs/guides/connection/react-py-agent.mdx +9 -15
  35. package/docs/guides/connection/react-smithy.mdx +3 -3
  36. package/docs/guides/connection/react-trpc.mdx +1 -1
  37. package/docs/guides/connection/react-ts-agent.mdx +8 -8
  38. package/docs/guides/connection/smithy-dynamodb.mdx +5 -5
  39. package/docs/guides/connection/smithy-rdb.mdx +9 -9
  40. package/docs/guides/connection/trpc-dynamodb.mdx +5 -5
  41. package/docs/guides/connection/trpc-rdb.mdx +6 -6
  42. package/docs/guides/connection/ts-agent-a2a.mdx +14 -11
  43. package/docs/guides/connection/ts-agent-dynamodb.mdx +5 -5
  44. package/docs/guides/connection/ts-agent-gateway.mdx +143 -0
  45. package/docs/guides/connection/ts-agent-mcp.mdx +12 -9
  46. package/docs/guides/connection/ts-agent-rdb.mdx +70 -25
  47. package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +5 -5
  48. package/docs/guides/connection/ts-mcp-server-rdb.mdx +68 -18
  49. package/docs/guides/connection.mdx +122 -5
  50. package/docs/guides/docker-bundling.mdx +69 -12
  51. package/docs/guides/fastapi.mdx +249 -9
  52. package/docs/guides/local-development.mdx +87 -0
  53. package/docs/guides/nx-generator.mdx +4 -3
  54. package/docs/guides/nx-migration.mdx +165 -0
  55. package/docs/guides/py-agent.mdx +264 -49
  56. package/docs/guides/py-dynamodb.mdx +476 -0
  57. package/docs/guides/py-mcp-server.mdx +61 -2
  58. package/docs/guides/py-rdb.mdx +265 -0
  59. package/docs/guides/python-lambda-function.mdx +1 -1
  60. package/docs/guides/react-website-auth.mdx +65 -4
  61. package/docs/guides/react-website.mdx +149 -30
  62. package/docs/guides/runtime-config.mdx +1 -1
  63. package/docs/guides/security.mdx +75 -0
  64. package/docs/guides/smithy-project.mdx +167 -0
  65. package/docs/guides/terraform-project.mdx +2 -2
  66. package/docs/guides/trpc.mdx +53 -16
  67. package/docs/guides/ts-agent.mdx +183 -10
  68. package/docs/guides/ts-dcr-proxy.mdx +569 -0
  69. package/docs/guides/ts-dynamodb.mdx +66 -242
  70. package/docs/guides/ts-lambda-function.mdx +1 -1
  71. package/docs/guides/ts-mcp-server.mdx +109 -29
  72. package/docs/guides/ts-nx-plugin.mdx +3 -3
  73. package/docs/guides/ts-rdb.mdx +113 -467
  74. package/docs/guides/ts-smithy-api.mdx +258 -18
  75. package/docs/guides/typescript-infrastructure.mdx +46 -24
  76. package/docs/guides/typescript-project.mdx +134 -27
  77. package/docs/guides/workspace.mdx +10 -3
  78. package/docs/snippets/agent/architecture.mdx +1 -1
  79. package/docs/snippets/agent/bedrock-deployment.mdx +9 -5
  80. package/docs/snippets/agent/runtime-arn.mdx +23 -2
  81. package/docs/snippets/agent/securing-your-agent.mdx +39 -0
  82. package/docs/snippets/api/access-logging.mdx +33 -0
  83. package/docs/snippets/api/cors-configuration-cdk-note.mdx +1 -1
  84. package/docs/snippets/api/cors-configuration-terraform-note.mdx +1 -1
  85. package/docs/snippets/api/type-safe-api-integrations.mdx +33 -2
  86. package/docs/snippets/api/waf-configuration.mdx +3 -3
  87. package/docs/snippets/connection/a2a-infrastructure.mdx +1 -1
  88. package/docs/snippets/connection/dynamodb-local-development.mdx +2 -2
  89. package/docs/snippets/connection/lambda-dynamodb-access.mdx +1 -1
  90. package/docs/snippets/connection/py-dynamodb-local-development.mdx +7 -0
  91. package/docs/snippets/connection/py-lambda-rdb-ssl-requirements.mdx +7 -0
  92. package/docs/snippets/connection/rdb-api-infrastructure.mdx +51 -19
  93. package/docs/snippets/dynamodb/deploying-table.mdx +166 -0
  94. package/docs/snippets/dynamodb/gsi-config.mdx +38 -0
  95. package/docs/snippets/dynamodb/infrastructure.mdx +33 -0
  96. package/docs/snippets/dynamodb/local-dev-start.mdx +13 -0
  97. package/docs/snippets/dynamodb/local-dev-windows.mdx +15 -0
  98. package/docs/snippets/lambda-function/deploying-your-function.mdx +3 -3
  99. package/docs/snippets/mcp/architecture.mdx +1 -1
  100. package/docs/snippets/mcp/bedrock-deployment.mdx +9 -5
  101. package/docs/snippets/mcp/config.mdx +3 -2
  102. package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +7 -5
  103. package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +29 -10
  104. package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +4 -4
  105. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +5 -111
  106. package/docs/snippets/prerequisites.mdx +1 -4
  107. package/docs/snippets/rdb/architecture.mdx +38 -0
  108. package/docs/snippets/rdb/cluster-instances.mdx +31 -0
  109. package/docs/snippets/rdb/deletion-protection.mdx +34 -0
  110. package/docs/snippets/rdb/deploying.mdx +187 -0
  111. package/docs/snippets/rdb/encryption-key-rotation.mdx +30 -0
  112. package/docs/snippets/rdb/engine-version.mdx +63 -0
  113. package/docs/snippets/rdb/infrastructure.mdx +35 -0
  114. package/docs/snippets/rdb/logging-mysql.mdx +5 -0
  115. package/docs/snippets/rdb/logging-postgres.mdx +5 -0
  116. package/docs/snippets/rdb/performance-insights.mdx +34 -0
  117. package/docs/snippets/rdb/rds-proxy.mdx +50 -0
  118. package/docs/snippets/rdb/removal-policy.mdx +57 -0
  119. package/docs/snippets/rdb/serverless-capacity.mdx +32 -0
  120. package/docs/snippets/recommended-prerequisites.mdx +10 -0
  121. package/docs/snippets/required-prerequisites.mdx +1 -4
  122. package/docs/snippets/runtime-config-app-id-note.mdx +8 -0
  123. package/docs/snippets/shared-constructs.mdx +1 -1
  124. package/docs/snippets/trivy-image-scan.mdx +37 -0
  125. package/generators.json +152 -10
  126. package/package.json +1 -1
  127. package/src/agentcore-gateway/agent-connection/schema.json +31 -0
  128. package/src/agentcore-gateway/gateway-connection/schema.json +31 -0
  129. package/src/agentcore-gateway/mcp-connection/schema.json +31 -0
  130. package/src/agentcore-gateway/react-connection/schema.json +31 -0
  131. package/src/agentcore-gateway/schema.json +72 -0
  132. package/src/agentcore-harness/schema.json +53 -0
  133. package/src/connection/schema.json +5 -0
  134. package/src/infra/app/schema.json +5 -0
  135. package/src/init/schema.json +35 -0
  136. package/src/internal/test-matrix/schema.json +21 -0
  137. package/src/license/schema.json +5 -0
  138. package/src/preset/schema.json +16 -5
  139. package/src/py/agent/a2a-connection/schema.json +5 -0
  140. package/src/py/agent/gateway-connection/schema.json +31 -0
  141. package/src/py/agent/mcp-connection/schema.json +5 -0
  142. package/src/py/agent/react-connection/schema.json +5 -0
  143. package/src/py/agent/schema.json +15 -1
  144. package/src/py/api/schema.json +5 -0
  145. package/src/py/dynamodb/agent-connection/schema.json +27 -0
  146. package/src/py/dynamodb/fast-api-connection/schema.json +23 -0
  147. package/src/py/dynamodb/mcp-server-connection/schema.json +27 -0
  148. package/src/py/dynamodb/schema.json +76 -0
  149. package/src/py/fast-api/react/schema.json +5 -0
  150. package/src/py/fast-api/schema.json +6 -0
  151. package/src/py/lambda-function/schema.json +5 -0
  152. package/src/py/mcp-server/schema.json +6 -0
  153. package/src/py/project/schema.json +5 -0
  154. package/src/py/rdb/agent-connection/schema.json +27 -0
  155. package/src/py/rdb/fast-api-connection/schema.json +23 -0
  156. package/src/py/rdb/mcp-server-connection/schema.json +27 -0
  157. package/src/py/rdb/schema.json +78 -0
  158. package/src/smithy/project/schema.json +28 -1
  159. package/src/smithy/react-connection/schema.json +5 -0
  160. package/src/smithy/ts/api/schema.json +6 -0
  161. package/src/terraform/project/schema.json +5 -0
  162. package/src/trpc/backend/schema.json +6 -0
  163. package/src/trpc/react/schema.json +5 -0
  164. package/src/ts/agent/a2a-connection/schema.json +5 -0
  165. package/src/ts/agent/gateway-connection/schema.json +31 -0
  166. package/src/ts/agent/mcp-connection/schema.json +5 -0
  167. package/src/ts/agent/react-connection/schema.json +5 -0
  168. package/src/ts/agent/schema.json +14 -0
  169. package/src/ts/api/schema.json +5 -0
  170. package/src/ts/astro-docs/schema.json +3 -3
  171. package/src/ts/dcr-proxy/schema.json +44 -0
  172. package/src/ts/docs/schema.json +3 -3
  173. package/src/ts/dynamodb/agent-connection/schema.json +5 -0
  174. package/src/ts/dynamodb/mcp-server-connection/schema.json +5 -0
  175. package/src/ts/dynamodb/schema.json +26 -2
  176. package/src/ts/dynamodb/smithy-connection/schema.json +5 -0
  177. package/src/ts/dynamodb/trpc-connection/schema.json +5 -0
  178. package/src/ts/lambda-function/schema.json +5 -0
  179. package/src/ts/lib/schema.json +5 -0
  180. package/src/ts/mcp-server/schema.json +6 -0
  181. package/src/ts/nx-generator/schema.json +5 -0
  182. package/src/ts/nx-migration/schema.json +63 -0
  183. package/src/ts/nx-plugin/schema.json +5 -0
  184. package/src/ts/rdb/agent-connection/schema.json +5 -0
  185. package/src/ts/rdb/mcp-server-connection/schema.json +5 -0
  186. package/src/ts/rdb/schema.json +7 -1
  187. package/src/ts/rdb/smithy-connection/schema.json +5 -0
  188. package/src/ts/rdb/trpc-connection/schema.json +5 -0
  189. package/src/ts/react-website/app/schema.json +12 -6
  190. package/src/ts/react-website/cognito-auth/schema.json +5 -0
  191. package/src/ts/react-website/runtime-config/schema.json +5 -0
  192. package/src/ts/website/app/schema.json +11 -6
  193. package/src/ts/website/auth/schema.json +5 -0
  194. /package/docs/snippets/connection/{lambda-rdb-ssl-requirements.mdx → ts-lambda-rdb-ssl-requirements.mdx} +0 -0
  195. /package/docs/snippets/connection/{mcp-server-rdb-ssl-requirements.mdx → ts-mcp-server-rdb-ssl-requirements.mdx} +0 -0
@@ -0,0 +1,476 @@
1
+ ---
2
+ title: Python DynamoDB
3
+ description: Create a Python DynamoDB project
4
+ generator: py#dynamodb
5
+ ---
6
+
7
+ import { FileTree, CardGrid } from '@astrojs/starlight/components';
8
+ import Astro from '@astrojs/react';
9
+ import ConnectionCard from '@components/connection-card.astro';
10
+ import Link from '@components/link.astro';
11
+ import RunGenerator from '@components/run-generator.astro';
12
+ import GeneratorParameters from '@components/generator-parameters.astro';
13
+ import Snippet from '@components/snippet.astro';
14
+
15
+ This generator creates a new Python project backed by [Amazon DynamoDB](https://aws.amazon.com/dynamodb/), using [PynamoDB](https://pynamodb.readthedocs.io/) for entity modelling. It generates the application code and infrastructure needed to provision and manage a DynamoDB table using AWS CDK or Terraform, with single-table design support and built-in local development via DynamoDB Local.
16
+
17
+ ## Usage
18
+
19
+ ### Generate a DynamoDB Project
20
+
21
+ <RunGenerator generator="py#dynamodb" />
22
+
23
+ ### Options
24
+
25
+ <GeneratorParameters generator="py#dynamodb" />
26
+
27
+ ## Generator Output
28
+
29
+ The generator creates the following project structure in the `<directory>/<name>` directory:
30
+
31
+ <FileTree>
32
+ - \<name>
33
+ - \_\_init\_\_.py Package exports
34
+ - client.py DynamoDB client and table name resolution
35
+ - entities
36
+ - base.py Base PynamoDB model with GSI declarations
37
+ - example.py Example entity definition
38
+ - \_\_init\_\_.py Entity exports
39
+ - config.json Table configuration including GSI definitions and local development settings
40
+ - project.json Project configuration and build targets
41
+ </FileTree>
42
+
43
+ The local development scripts are shared across all DynamoDB projects (both TypeScript and Python) and generated once into:
44
+
45
+ <FileTree>
46
+ - packages/common/scripts/src/dynamodb
47
+ - create-local-table.ts Creates the DynamoDB table in the local DynamoDB Local instance
48
+ - pull-image.ts Pulls the DynamoDB Local image
49
+ - start-container.ts Starts the DynamoDB Local container
50
+ </FileTree>
51
+
52
+ ### Infrastructure
53
+
54
+ <Snippet name="dynamodb/infrastructure" />
55
+
56
+ ## Local Development
57
+
58
+ ### Starting Local DynamoDB
59
+
60
+ <Snippet name="dynamodb/local-dev-start" />
61
+
62
+ ### Data Modelling
63
+
64
+ The generated project uses [PynamoDB](https://pynamodb.readthedocs.io/) for entity modelling. All entities **must** inherit from the generated `BaseModel` — it resolves the correct DynamoDB table name at runtime, reading from AWS AppConfig when deployed or from `config.json` when running locally via DynamoDB Local. Without this, PynamoDB will not know which table to use. `BaseModel` also uses [PynamoDB's polymorphism support](https://pynamodb.readthedocs.io/en/stable/polymorphism.html) to store multiple entity types in a single table, following [DynamoDB's single-table design](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/data-modeling-foundations.html).
65
+
66
+ Add or update entity files under `<name>/entities/`, using the generated example entity as a starting point:
67
+
68
+ ```python title="packages/my_table/my_table/entities/example.py"
69
+ from collections.abc import Iterator
70
+ from datetime import UTC, datetime
71
+ from pynamodb.attributes import UnicodeAttribute
72
+ from .base import BaseModel
73
+
74
+
75
+ class ExampleModel(BaseModel, discriminator='ExampleModel'):
76
+ """
77
+ Key design:
78
+ pk=EXAMPLE#<id>, sk=EXAMPLE#<id>
79
+ gsi1pk=CATEGORY#<cat>, gsi1sk=EXAMPLE#<id> <- list items by category
80
+ gsi2pk=EXAMPLE, gsi2sk=<created_at> <- list all items by date
81
+ """
82
+
83
+ name = UnicodeAttribute()
84
+ category = UnicodeAttribute()
85
+ created_at = UnicodeAttribute()
86
+ updated_at = UnicodeAttribute()
87
+
88
+ @classmethod
89
+ def make_pk(cls, id: str) -> str:
90
+ return f'EXAMPLE#{id}'
91
+
92
+ @classmethod
93
+ def create(cls, id: str, name: str, category: str) -> 'ExampleModel':
94
+ now = datetime.now(UTC).isoformat()
95
+ item = cls(
96
+ pk=cls.make_pk(id),
97
+ sk=cls.make_pk(id),
98
+ gsi1pk=f'CATEGORY#{category}',
99
+ gsi1sk=cls.make_pk(id),
100
+ gsi2pk='EXAMPLE',
101
+ gsi2sk=now,
102
+ name=name,
103
+ category=category,
104
+ created_at=now,
105
+ updated_at=now,
106
+ )
107
+ item.save()
108
+ return item
109
+
110
+ # ── Primary index ─────────────────────────────────────────────────────────
111
+ @classmethod
112
+ def get_by_id(cls, id: str) -> 'ExampleModel':
113
+ return cls.get(cls.make_pk(id), cls.make_pk(id))
114
+
115
+ # ── gsi1_index: partition=category, sort=id ───────────────────────────────
116
+ @classmethod
117
+ def list_by_category(cls, category: str) -> Iterator['ExampleModel']:
118
+ return cls.gsi1_index.query(f'CATEGORY#{category}')
119
+
120
+ # ── gsi2_index: partition=type, sort=created_at ───────────────────────────
121
+ @classmethod
122
+ def list_created_between(cls, start: datetime, end: datetime) -> Iterator['ExampleModel']:
123
+ return cls.gsi2_index.query(
124
+ 'EXAMPLE',
125
+ range_key_condition=ExampleModel.gsi2sk.between(
126
+ start.isoformat(), end.isoformat(),
127
+ ),
128
+ scan_index_forward=False,
129
+ )
130
+ ```
131
+
132
+ For more details, see the [PynamoDB tutorial](https://pynamodb.readthedocs.io/en/stable/tutorial.html).
133
+
134
+ #### Designing Around Access Patterns
135
+
136
+ In DynamoDB, schema design starts with your queries, not your data shape. Before writing any model, list every access pattern your application needs, then design `pk`, `sk`, and GSI key values so each pattern is answered by a single table request — no JOINs, no sequential reads.
137
+
138
+ The generated `ExampleModel` demonstrates this for three patterns:
139
+
140
+ - **Get by ID** — primary index, `pk=EXAMPLE#<id>`, `sk=EXAMPLE#<id>`
141
+ - **List by category** — `gsi1`, `pk=CATEGORY#<category>`
142
+ - **List by creation date** — `gsi2`, `pk=EXAMPLE`, sort key between ISO timestamps
143
+
144
+ The **type prefix** convention (e.g. `EXAMPLE#`, `CATEGORY#`) is deliberate: it makes items self-describing when browsing the table, prevents accidental key collisions between entity types that share an index, and allows sort key prefix filtering using `begins_with`.
145
+
146
+ Before writing a new entity, define its key patterns upfront in a docstring. The `OrderModel` in the next section follows this convention:
147
+
148
+ ```python
149
+ class OrderModel(BaseModel, discriminator='OrderModel'):
150
+ """
151
+ Key design:
152
+ pk=ORDER#<order_id>, sk=ORDER#<order_id>
153
+ gsi1pk=USER#<user_id>, gsi1sk=ORDER#<order_id> <- list orders for a user
154
+ gsi2pk=ORDER, gsi2sk=<created_at> <- list all orders by date
155
+ """
156
+ ```
157
+
158
+ #### Storing Multiple Entity Types
159
+
160
+ PynamoDB's `DiscriminatorAttribute` stores a type label (`entity_type`) in every item. When querying via `BaseModel`, this label is used to instantiate each result as its correct subclass automatically — so a single query can return a mix of `UserModel`, `OrderModel`, and any other entity type registered in the same table.
161
+
162
+ :::caution[PynamoDB is not purpose-built for single-table design]
163
+ PynamoDB's `DiscriminatorAttribute` was designed for class hierarchy polymorphism within a single homogeneous table — not for storing structurally different entity types in one table. The pattern used here works, but keep these caveats in mind:
164
+
165
+ - **Key collision is your responsibility.** PynamoDB does not prevent two entity types from writing the same `pk`/`sk` pair. Always use unique type prefixes (e.g. `ORDER#`, `USER#`).
166
+ - **Cross-entity queries require `BaseModel`.** PynamoDB automatically adds a discriminator filter when querying a specific subclass, so items of other entity types at the same key will never be returned. To retrieve a mix of types from a shared GSI partition key, always query via `BaseModel`.
167
+ - **Shared table settings.** All entities share the `Meta` configuration (table name, region, credentials) defined in `BaseModel`.
168
+ :::
169
+
170
+ Below is a complete two-entity example — a `UserModel` with associated `OrderModel` records stored in the same table:
171
+
172
+ ```python title="packages/my_table/my_table/entities/user.py"
173
+ from collections.abc import Iterator
174
+ from datetime import UTC, datetime
175
+ from pynamodb.attributes import UnicodeAttribute
176
+ from .base import BaseModel
177
+
178
+
179
+ class UserModel(BaseModel, discriminator='UserModel'):
180
+ """
181
+ Key design:
182
+ pk=USER#<user_id>, sk=USER#<user_id>
183
+ gsi2pk=USER, gsi2sk=<created_at> <- list all users by date
184
+ """
185
+
186
+ username = UnicodeAttribute()
187
+ email = UnicodeAttribute()
188
+ created_at = UnicodeAttribute()
189
+
190
+ @classmethod
191
+ def make_pk(cls, user_id: str) -> str:
192
+ return f'USER#{user_id}'
193
+
194
+ @classmethod
195
+ def create(cls, user_id: str, username: str, email: str) -> 'UserModel':
196
+ now = datetime.now(UTC).isoformat()
197
+ item = cls(
198
+ pk=cls.make_pk(user_id),
199
+ sk=cls.make_pk(user_id),
200
+ gsi2pk='USER',
201
+ gsi2sk=now,
202
+ username=username,
203
+ email=email,
204
+ created_at=now,
205
+ )
206
+ item.save()
207
+ return item
208
+
209
+ @classmethod
210
+ def get_by_id(cls, user_id: str) -> 'UserModel':
211
+ return cls.get(cls.make_pk(user_id), cls.make_pk(user_id))
212
+
213
+ @classmethod
214
+ def list_recent(cls, limit: int | None = None) -> Iterator['UserModel']:
215
+ return cls.gsi2_index.query('USER', limit=limit, scan_index_forward=False)
216
+ ```
217
+
218
+ ```python title="packages/my_table/my_table/entities/order.py"
219
+ from collections.abc import Iterator
220
+ from datetime import UTC, datetime
221
+ from pynamodb.attributes import UnicodeAttribute
222
+ from .base import BaseModel
223
+
224
+
225
+ class OrderModel(BaseModel, discriminator='OrderModel'):
226
+ """
227
+ Key design:
228
+ pk=ORDER#<order_id>, sk=ORDER#<order_id>
229
+ gsi1pk=USER#<user_id>, gsi1sk=ORDER#<order_id> <- list orders by user
230
+ gsi2pk=ORDER, gsi2sk=<created_at> <- list all orders by date
231
+ """
232
+
233
+ user_id = UnicodeAttribute()
234
+ total = UnicodeAttribute()
235
+ created_at = UnicodeAttribute()
236
+
237
+ @classmethod
238
+ def make_pk(cls, order_id: str) -> str:
239
+ return f'ORDER#{order_id}'
240
+
241
+ @classmethod
242
+ def create(cls, order_id: str, user_id: str, total: str) -> 'OrderModel':
243
+ now = datetime.now(UTC).isoformat()
244
+ item = cls(
245
+ pk=cls.make_pk(order_id),
246
+ sk=cls.make_pk(order_id),
247
+ gsi1pk=f'USER#{user_id}',
248
+ gsi1sk=cls.make_pk(order_id),
249
+ gsi2pk='ORDER',
250
+ gsi2sk=now,
251
+ user_id=user_id,
252
+ total=total,
253
+ created_at=now,
254
+ )
255
+ item.save()
256
+ return item
257
+
258
+ @classmethod
259
+ def get_by_id(cls, order_id: str) -> 'OrderModel':
260
+ return cls.get(cls.make_pk(order_id), cls.make_pk(order_id))
261
+
262
+ # ── gsi1_index: partition=user, sort=order_id ────────────────────────────
263
+ @classmethod
264
+ def list_by_user(cls, user_id: str) -> Iterator['OrderModel']:
265
+ return cls.gsi1_index.query(f'USER#{user_id}')
266
+
267
+ # ── gsi2_index: partition=type, sort=created_at ───────────────────────────
268
+ @classmethod
269
+ def list_recent(cls, limit: int | None = None) -> Iterator['OrderModel']:
270
+ return cls.gsi2_index.query('ORDER', limit=limit, scan_index_forward=False)
271
+ ```
272
+
273
+ Export the new entities from `__init__.py`:
274
+
275
+ ```python title="packages/my_table/my_table/entities/__init__.py"
276
+ from .user import UserModel
277
+ from .order import OrderModel
278
+ from .example import ExampleModel
279
+ ```
280
+
281
+ #### GSI Overloading
282
+
283
+ `BaseModel` provides two shared GSIs (`gsi1_index`, `gsi2_index`). Both `UserModel` and `OrderModel` above write to `gsi2` — but with different `gsi2pk` values (`USER` vs `ORDER`). This is **GSI overloading**: reusing a single physical index to serve multiple independent access patterns without consuming extra GSI capacity.
284
+
285
+ - **`UserModel`** — `gsi2pk=USER`, `gsi2sk=<created_at>` → list all users by date
286
+ - **`OrderModel`** — `gsi2pk=ORDER`, `gsi2sk=<created_at>` → list all orders by date
287
+
288
+ `gsi1` can also be overloaded when multiple entity types share the same parent. If you later add a `ReviewModel` that also belongs to a user, you can assign it `gsi1pk=USER#<user_id>` with a `REVIEW#<id>` sort key — no additional GSI needed. Querying `gsi1` via `BaseModel` then returns both orders and reviews for that user in one request, with PynamoDB instantiating each item as its correct subclass:
289
+
290
+ ```python
291
+ from .base import BaseModel
292
+ from .order import OrderModel
293
+ from .review import ReviewModel
294
+
295
+ user_id = 'user-123'
296
+ items = list(BaseModel.gsi1_index.query(f'USER#{user_id}'))
297
+
298
+ orders = [i for i in items if isinstance(i, OrderModel)]
299
+ reviews = [i for i in items if isinstance(i, ReviewModel)]
300
+ ```
301
+
302
+ To retrieve only one entity type from an overloaded GSI, use a sort key prefix condition:
303
+
304
+ ```python
305
+ orders_only = list(BaseModel.gsi1_index.query(
306
+ f'USER#{user_id}',
307
+ range_key_condition=BaseModel.gsi1sk.startswith('ORDER#'),
308
+ ))
309
+ ```
310
+
311
+ #### One-to-Many Relationships
312
+
313
+ In a **one-to-many** relationship the child entity stores a reference to its parent in a GSI partition key, making the relationship traversable in both directions without duplicating data. The `UserModel` / `OrderModel` example above is exactly this pattern:
314
+
315
+ - **Get a single order by ID** — primary table: `pk=ORDER#<id>`, `sk=ORDER#<id>`
316
+ - **List all orders for a user** — `gsi1`: `pk=USER#<user_id>`
317
+
318
+ An alternative to GSI-based lookups is the **item collection** pattern: give child items the same `pk` as their parent and use the sort key to differentiate them. This lets you retrieve the parent and all its children in a single primary-table query, without a GSI:
319
+
320
+ ```python title="packages/my_table/my_table/entities/order.py (item collection variant)"
321
+ class OrderModel(BaseModel, discriminator='OrderModel'):
322
+ """
323
+ Key design (item collection):
324
+ pk=USER#<user_id>, sk=ORDER#<order_id> <- co-located under the parent user
325
+ """
326
+ ...
327
+ ```
328
+
329
+ ```python
330
+ # Retrieve the user and all their orders in one primary-table query
331
+ # BaseModel dispatches each item to its correct subclass via DiscriminatorAttribute
332
+ items = list(BaseModel.query(f'USER#{user_id}'))
333
+ user = next(i for i in items if isinstance(i, UserModel))
334
+ orders = [i for i in items if isinstance(i, OrderModel)]
335
+ ```
336
+
337
+ The tradeoff: item collections place all children under a single partition key, which is optimal for most workloads but can create a hot partition at extreme write throughput. The GSI approach (used in the examples above) keeps each entity in its own partition and is generally safer to start with.
338
+
339
+ #### Many-to-Many Relationships
340
+
341
+ Many-to-many relationships require a **junction entity** using the [adjacency list pattern](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html): a dedicated item that records each link, with its GSI key inverting the direction so the relationship can be traversed both ways.
342
+
343
+ Consider `ArticleModel` and `TagModel`, where an article can have many tags and a tag can apply to many articles:
344
+
345
+ ```python title="packages/my_table/my_table/entities/article_tag.py"
346
+ from collections.abc import Iterator
347
+ from pynamodb.attributes import UnicodeAttribute
348
+ from .base import BaseModel
349
+
350
+
351
+ class ArticleTagModel(BaseModel, discriminator='ArticleTag'):
352
+ """
353
+ Junction entity for the Article ↔ Tag many-to-many relationship.
354
+
355
+ Key design:
356
+ pk=ARTICLE#<article_id>, sk=TAG#<tag_name> <- list tags for an article
357
+ gsi1pk=TAG#<tag_name>, gsi1sk=ARTICLE#<article_id> <- list articles for a tag
358
+ """
359
+
360
+ article_id = UnicodeAttribute()
361
+ tag_name = UnicodeAttribute()
362
+
363
+ @classmethod
364
+ def add(cls, article_id: str, tag_name: str) -> 'ArticleTagModel':
365
+ item = cls(
366
+ pk=f'ARTICLE#{article_id}',
367
+ sk=f'TAG#{tag_name}',
368
+ gsi1pk=f'TAG#{tag_name}',
369
+ gsi1sk=f'ARTICLE#{article_id}',
370
+ article_id=article_id,
371
+ tag_name=tag_name,
372
+ )
373
+ item.save()
374
+ return item
375
+
376
+ @classmethod
377
+ def remove(cls, article_id: str, tag_name: str) -> None:
378
+ cls.get(f'ARTICLE#{article_id}', f'TAG#{tag_name}').delete()
379
+
380
+ # ── Primary index: pk=article, sk=tag ─────────────────────────────────────
381
+ @classmethod
382
+ def list_tags_for_article(cls, article_id: str) -> Iterator['ArticleTagModel']:
383
+ return cls.query(f'ARTICLE#{article_id}')
384
+
385
+ # ── gsi1_index: pk=tag, sk=article ────────────────────────────────────────
386
+ @classmethod
387
+ def list_articles_for_tag(cls, tag_name: str) -> Iterator['ArticleTagModel']:
388
+ return cls.gsi1_index.query(f'TAG#{tag_name}')
389
+ ```
390
+
391
+ Because `ArticleTagModel` uses `pk=ARTICLE#<article_id>` — the same partition as the article itself — you can retrieve an article and all its tags in a single primary-table query:
392
+
393
+ ```python
394
+ from .base import BaseModel
395
+ from .article import ArticleModel
396
+ from .article_tag import ArticleTagModel
397
+
398
+ items = list(BaseModel.query('ARTICLE#article-123'))
399
+ article = next(i for i in items if isinstance(i, ArticleModel))
400
+ tags = [i.tag_name for i in items if isinstance(i, ArticleTagModel)]
401
+ ```
402
+
403
+ For further reading on DynamoDB data modelling, see the [DynamoDB data modelling guide](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/data-modeling.html) and [Creating a single-table design with Amazon DynamoDB](https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/).
404
+
405
+ ### Using the DynamoDB Client
406
+
407
+ The generated `client.py` exports two key utilities:
408
+
409
+ - `is_local()` — returns `True` when `LOCAL_DEV=true`, used to switch between local and AWS behaviour.
410
+ - `get_table_name()` — returns the DynamoDB table name. When `LOCAL_DEV=true`, reads the table name from `localDev.tableName` in `config.json`; otherwise fetches the name from AWS AppConfig using the `RUNTIME_CONFIG_APP_ID` environment variable and caches it for subsequent calls.
411
+
412
+ `BaseModel` in `entities/base.py` uses both to configure PynamoDB automatically:
413
+
414
+ - **Connection** — `BaseModel.Meta` sets `host` from `config.json` and hardcodes `region`, `aws_access_key_id`, and `aws_secret_access_key` when `is_local()` is `True`, pointing PynamoDB at the local DynamoDB instance. In AWS, these are left unset so PynamoDB uses the default credential chain.
415
+ - **Table name** — `BaseModel._get_connection()` calls `get_table_name()` before each operation, so the correct table is resolved at runtime without any manual configuration.
416
+
417
+ ### Stopping Local DynamoDB
418
+
419
+ <Snippet name="dynamodb/local-dev-windows" />
420
+
421
+ ## Adding/Removing Global Secondary Indexes
422
+
423
+ GSIs are defined in `config.json` at the project root under the `tableConfig.globalSecondaryIndexes` key. Add an entry for each GSI, then reflect the change in `BaseModel` by adding or removing the corresponding `GlobalSecondaryIndex` class and attributes in `<name>/entities/base.py`:
424
+
425
+ <Snippet name="dynamodb/gsi-config" parentHeading="Adding/Removing Global Secondary Indexes" />
426
+
427
+ ## Connecting to the Table
428
+
429
+ In any Python project, add the DynamoDB package as a workspace dependency and import entity classes directly:
430
+
431
+ ```python
432
+ from my_db_package.entities import ExampleModel
433
+
434
+ item = ExampleModel.get_by_id('123')
435
+ ```
436
+
437
+ Behind the scenes, `ExampleModel` calls `get_table_name()` to fetch the table name from AWS AppConfig at runtime.
438
+
439
+ <Snippet name="runtime-config-app-id-note" parentHeading="Connecting to the Table" />
440
+
441
+ ## Deploying your Table
442
+
443
+ <Snippet name="dynamodb/deploying-table" parentHeading="Deploying your Table" />
444
+
445
+ ## Connections
446
+
447
+ Use the <Link path="guides/connection">`connection`</Link> generator to integrate this project with others in your workspace. The following connections involve this project:
448
+
449
+ <CardGrid>
450
+ <ConnectionCard
451
+ title="FastAPI to Python DynamoDB"
452
+ description="Connect a FastAPI to a DynamoDB table"
453
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-fast-api-dynamodb`}
454
+ source="fastapi"
455
+ target="dynamodb"
456
+ targetBadge="python"
457
+ />
458
+ <ConnectionCard
459
+ title="Python Agent to Python DynamoDB"
460
+ description="Connect a Python Agent to a DynamoDB table"
461
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-agent-dynamodb`}
462
+ source="strands"
463
+ sourceBadge="python"
464
+ target="dynamodb"
465
+ targetBadge="python"
466
+ />
467
+ <ConnectionCard
468
+ title="Python MCP Server to Python DynamoDB"
469
+ description="Connect a Python MCP Server to a DynamoDB table"
470
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-mcp-server-dynamodb`}
471
+ source="mcp"
472
+ sourceBadge="python"
473
+ target="dynamodb"
474
+ targetBadge="python"
475
+ />
476
+ </CardGrid>
@@ -4,7 +4,9 @@ description: Generate a Python Model Context Protocol (MCP) server for providing
4
4
  generator: py#mcp-server
5
5
  ---
6
6
 
7
- import { FileTree } from '@astrojs/starlight/components';
7
+ import { FileTree, CardGrid } from '@astrojs/starlight/components';
8
+ import Astro from '@astrojs/react';
9
+ import ConnectionCard from '@components/connection-card.astro';
8
10
  import RunGenerator from '@components/run-generator.astro';
9
11
  import NxCommands from '@components/nx-commands.astro';
10
12
  import Link from '@components/link.astro';
@@ -115,14 +117,28 @@ def dynamic_resource(item_id: str) -> str:
115
117
 
116
118
  ## Running Your MCP Server
117
119
 
120
+ ### Local Development
121
+
122
+ To run your MCP server (and everything connected to it, such as a local database) locally, use the project's `dev` target:
123
+
124
+ <NxCommands commands={['dev your-project']} />
125
+
126
+ If you have added multiple components to your project (MCP servers, agents, etc.), this starts them all. To run just this MCP server, target its `<your-server-name>-dev` target:
127
+
128
+ <NxCommands commands={['your-server-name-dev your-project']} />
129
+
118
130
  ### Inspector
119
131
 
120
- The generator configures a target named `<your-server-name>-inspect`, which starts the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) with the configuration to connect to your MCP server using STDIO transport.
132
+ The generator configures a target named `<your-server-name>-inspect`, which starts your MCP server locally (via the `<your-server-name>-dev` target, including any connected dependencies such as a local database) and launches the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) pre-configured to connect to it over Streamable HTTP transport.
121
133
 
122
134
  <NxCommands commands={['your-server-name-inspect your-project']} />
123
135
 
124
136
  This will start the inspector at `http://localhost:6274`. Get started by clicking on the "Connect" button.
125
137
 
138
+ :::tip
139
+ To inspect the server using STDIO transport instead, use the `<your-server-name>-inspect-stdio` target, which launches the inspector against a STDIO instance of your server.
140
+ :::
141
+
126
142
  ### STDIO
127
143
 
128
144
  The easiest way to test and use an MCP server is by using the inspector or configuring it with an AI assistant (as above).
@@ -155,7 +171,50 @@ In order to build your MCP server for Bedrock AgentCore Runtime, a `bundle` targ
155
171
 
156
172
  A `docker` target specific to your MCP server is also added, which copies the `Dockerfile` and bundled artifacts into a docker context directory. This co-locates the `Dockerfile` with the built output, allowing CDK to build the Docker image directly using `AgentRuntimeArtifact.fromAsset`.
157
173
 
174
+ ### Image Scanning
175
+
176
+ <Snippet name="trivy-image-scan" parentHeading="Image Scanning" />
177
+
158
178
  ### Observability
159
179
 
160
180
  <Snippet name="mcp/observability" parentHeading="Observability" />
161
181
  </OptionFilter>
182
+
183
+ ## Connections
184
+
185
+ Use the <Link path="guides/connection">`connection`</Link> generator to integrate this project with others in your workspace. The following connections involve this project:
186
+
187
+ <CardGrid>
188
+ <ConnectionCard
189
+ title="TypeScript Agent to MCP"
190
+ description="Connect a TypeScript Agent to an MCP server"
191
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/ts-agent-mcp`}
192
+ source="strands"
193
+ sourceBadge="typescript"
194
+ target="mcp"
195
+ />
196
+ <ConnectionCard
197
+ title="Python Agent to MCP"
198
+ description="Connect a Python Agent to an MCP server"
199
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-agent-mcp`}
200
+ source="strands"
201
+ sourceBadge="python"
202
+ target="mcp"
203
+ />
204
+ <ConnectionCard
205
+ title="Python MCP Server to Python DynamoDB"
206
+ description="Connect a Python MCP Server to a DynamoDB table"
207
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-mcp-server-dynamodb`}
208
+ source="mcp"
209
+ sourceBadge="python"
210
+ target="dynamodb"
211
+ targetBadge="python"
212
+ />
213
+ <ConnectionCard
214
+ title="AgentCore Gateway to MCP Server"
215
+ description="Aggregate an MCP server behind an AgentCore Gateway"
216
+ href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/agentcore-gateway-mcp`}
217
+ source="agentcore"
218
+ target="mcp"
219
+ />
220
+ </CardGrid>