@aws/nx-plugin-mcp 1.0.0-rc.2 → 1.0.0-rc.21
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.
- package/bin/aws-nx-mcp.js +5919 -4776
- package/docs/guides/agentcore-gateway.mdx +240 -0
- package/docs/guides/connection/agentcore-gateway-gateway.mdx +154 -0
- package/docs/guides/connection/agentcore-gateway-mcp.mdx +134 -0
- package/docs/guides/connection/py-agent-a2a.mdx +8 -5
- package/docs/guides/connection/py-agent-dynamodb.mdx +116 -0
- package/docs/guides/connection/py-agent-gateway.mdx +161 -0
- package/docs/guides/connection/py-agent-mcp.mdx +7 -4
- package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
- package/docs/guides/connection/py-mcp-server-dynamodb.mdx +116 -0
- package/docs/guides/connection/react-smithy.mdx +1 -1
- package/docs/guides/connection/react-trpc.mdx +1 -1
- package/docs/guides/connection/smithy-dynamodb.mdx +68 -0
- package/docs/guides/connection/smithy-rdb.mdx +1 -1
- package/docs/guides/connection/trpc-dynamodb.mdx +62 -0
- package/docs/guides/connection/trpc-rdb.mdx +1 -1
- package/docs/guides/connection/ts-agent-a2a.mdx +8 -5
- package/docs/guides/connection/ts-agent-dynamodb.mdx +128 -0
- package/docs/guides/connection/ts-agent-gateway.mdx +152 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +7 -4
- package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +125 -0
- package/docs/guides/connection.mdx +89 -0
- package/docs/guides/docker-bundling.mdx +13 -7
- package/docs/guides/fastapi.mdx +213 -3
- package/docs/guides/license.mdx +264 -109
- package/docs/guides/local-development.mdx +76 -0
- package/docs/guides/nx-generator.mdx +5 -0
- package/docs/guides/py-agent.mdx +42 -3
- package/docs/guides/py-dynamodb.mdx +449 -0
- package/docs/guides/py-mcp-server.mdx +5 -1
- package/docs/guides/react-website-auth.mdx +15 -0
- package/docs/guides/react-website.mdx +12 -4
- package/docs/guides/trpc.mdx +8 -8
- package/docs/guides/ts-agent.mdx +40 -2
- package/docs/guides/ts-dynamodb.mdx +158 -0
- package/docs/guides/ts-mcp-server.mdx +5 -1
- package/docs/guides/ts-rdb.mdx +58 -10
- package/docs/guides/ts-smithy-api.mdx +148 -3
- package/docs/guides/typescript-project.mdx +5 -10
- package/docs/guides/workspace.mdx +8 -2
- package/docs/snippets/api/type-safe-api-integrations.mdx +31 -0
- package/docs/snippets/api/waf-configuration.mdx +1 -1
- package/docs/snippets/connection/dynamodb-local-development.mdx +7 -0
- package/docs/snippets/connection/lambda-dynamodb-access.mdx +80 -0
- package/docs/snippets/connection/py-dynamodb-local-development.mdx +7 -0
- package/docs/snippets/dynamodb/deploying-table.mdx +171 -0
- package/docs/snippets/dynamodb/gsi-config.mdx +38 -0
- package/docs/snippets/dynamodb/infrastructure.mdx +33 -0
- package/docs/snippets/dynamodb/serve-local-start.mdx +13 -0
- package/docs/snippets/dynamodb/serve-local-windows.mdx +15 -0
- package/docs/snippets/mcp/config.mdx +1 -1
- package/docs/snippets/required-prerequisites.mdx +1 -1
- package/generators.json +100 -1
- package/package.json +1 -1
- package/src/agentcore-gateway/gateway-connection/schema.json +26 -0
- package/src/agentcore-gateway/mcp-connection/schema.json +26 -0
- package/src/agentcore-gateway/schema.json +65 -0
- package/src/license/schema.json +6 -0
- package/src/preset/schema.json +5 -0
- package/src/py/agent/gateway-connection/schema.json +26 -0
- package/src/py/dynamodb/agent-connection/schema.json +22 -0
- package/src/py/dynamodb/fast-api-connection/schema.json +18 -0
- package/src/py/dynamodb/mcp-server-connection/schema.json +22 -0
- package/src/py/dynamodb/schema.json +70 -0
- package/src/ts/agent/gateway-connection/schema.json +26 -0
- package/src/ts/dynamodb/agent-connection/schema.json +22 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.json +22 -0
- package/src/ts/dynamodb/schema.json +70 -0
- package/src/ts/dynamodb/smithy-connection/schema.json +18 -0
- package/src/ts/dynamodb/trpc-connection/schema.json +18 -0
package/docs/guides/fastapi.mdx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: FastAPI
|
|
3
3
|
description: Reference documentation for FastAPI
|
|
4
|
-
generator: py#
|
|
4
|
+
generator: py#api
|
|
5
5
|
when:
|
|
6
6
|
framework: [fastapi]
|
|
7
7
|
---
|
|
@@ -26,11 +26,11 @@ The FastAPI generator creates a new FastAPI with AWS CDK or Terraform infrastruc
|
|
|
26
26
|
|
|
27
27
|
You can generate a new FastAPI in two ways:
|
|
28
28
|
|
|
29
|
-
<RunGenerator generator="py#
|
|
29
|
+
<RunGenerator generator="py#api" requiredParameters={{ framework: 'fastapi' }} />
|
|
30
30
|
|
|
31
31
|
### Options
|
|
32
32
|
|
|
33
|
-
<GeneratorParameters generator="py#
|
|
33
|
+
<GeneratorParameters generator="py#api" />
|
|
34
34
|
|
|
35
35
|
<Snippet name="api/api-choice-note" />
|
|
36
36
|
|
|
@@ -185,6 +185,216 @@ Unhandled exceptions are caught by the middleware and:
|
|
|
185
185
|
It's recommended to specify response models for your API operations for better code generation if using the `connection` generator. <Link path="guides/connection/react-fastapi#errors">See here for more details</Link>.
|
|
186
186
|
:::
|
|
187
187
|
|
|
188
|
+
### Accessing the Calling User
|
|
189
|
+
|
|
190
|
+
When your API is protected by authentication, your route handlers often need to know who is calling. The generated FastAPI runs inside AWS Lambda via the [Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter), which forwards the API Gateway request context as JSON on the `x-amzn-request-context` header. You can read it from the FastAPI `Request` to extract the caller's identity.
|
|
191
|
+
|
|
192
|
+
As an example, let's add a `/me` endpoint that returns details about the calling user. We'll implement the extraction as a [FastAPI dependency](https://fastapi.tiangolo.com/tutorial/dependencies/) so it can be reused across routes. The shape of the request context — and therefore how you extract the identity — depends on both your selected `auth` method and whether you deployed a REST or HTTP API.
|
|
193
|
+
|
|
194
|
+
<OptionFilter when={{ auth: 'iam' }} description="Identity extraction for IAM-authenticated APIs">
|
|
195
|
+
For `IAM` authentication, we look up the caller in Cognito using the sub extracted from the API Gateway request context. Create `identity.py` alongside `main.py`:
|
|
196
|
+
|
|
197
|
+
<Tabs syncKey="http-rest">
|
|
198
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
199
|
+
```python
|
|
200
|
+
import json
|
|
201
|
+
import os
|
|
202
|
+
from typing import Annotated
|
|
203
|
+
|
|
204
|
+
from boto3 import client
|
|
205
|
+
from fastapi import Depends, HTTPException, Request
|
|
206
|
+
from pydantic import BaseModel
|
|
207
|
+
|
|
208
|
+
cognito = client("cognito-idp")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class Identity(BaseModel):
|
|
212
|
+
sub: str
|
|
213
|
+
username: str
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def get_identity(request: Request) -> Identity:
|
|
217
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
218
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
219
|
+
if not request_context_header:
|
|
220
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
221
|
+
|
|
222
|
+
request_context = json.loads(request_context_header)
|
|
223
|
+
provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
|
|
224
|
+
|
|
225
|
+
sub = provider.split(":")[-1] if provider else None
|
|
226
|
+
if not sub:
|
|
227
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
228
|
+
|
|
229
|
+
users = cognito.list_users(
|
|
230
|
+
# Assumes user pool id is configured in lambda environment
|
|
231
|
+
UserPoolId=os.environ["USER_POOL_ID"],
|
|
232
|
+
Limit=1,
|
|
233
|
+
Filter=f'sub="{sub}"',
|
|
234
|
+
).get("Users", [])
|
|
235
|
+
|
|
236
|
+
if len(users) != 1:
|
|
237
|
+
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
|
|
238
|
+
|
|
239
|
+
return Identity(sub=sub, username=users[0]["Username"])
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
243
|
+
```
|
|
244
|
+
</TabItem>
|
|
245
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
246
|
+
```python
|
|
247
|
+
import json
|
|
248
|
+
import os
|
|
249
|
+
from typing import Annotated
|
|
250
|
+
|
|
251
|
+
from boto3 import client
|
|
252
|
+
from fastapi import Depends, HTTPException, Request
|
|
253
|
+
from pydantic import BaseModel
|
|
254
|
+
|
|
255
|
+
cognito = client("cognito-idp")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class Identity(BaseModel):
|
|
259
|
+
sub: str
|
|
260
|
+
username: str
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_identity(request: Request) -> Identity:
|
|
264
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
265
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
266
|
+
if not request_context_header:
|
|
267
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
268
|
+
|
|
269
|
+
request_context = json.loads(request_context_header)
|
|
270
|
+
amr = (
|
|
271
|
+
request_context.get("authorizer", {})
|
|
272
|
+
.get("iam", {})
|
|
273
|
+
.get("cognitoIdentity", {})
|
|
274
|
+
.get("amr", [])
|
|
275
|
+
)
|
|
276
|
+
sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None)
|
|
277
|
+
sub = sign_in.split(":")[-1] if sign_in else None
|
|
278
|
+
|
|
279
|
+
if not sub:
|
|
280
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
281
|
+
|
|
282
|
+
users = cognito.list_users(
|
|
283
|
+
# Assumes user pool id is configured in lambda environment
|
|
284
|
+
UserPoolId=os.environ["USER_POOL_ID"],
|
|
285
|
+
Limit=1,
|
|
286
|
+
Filter=f'sub="{sub}"',
|
|
287
|
+
).get("Users", [])
|
|
288
|
+
|
|
289
|
+
if len(users) != 1:
|
|
290
|
+
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
|
|
291
|
+
|
|
292
|
+
return Identity(sub=sub, username=users[0]["Username"])
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
296
|
+
```
|
|
297
|
+
</TabItem>
|
|
298
|
+
</Tabs>
|
|
299
|
+
</OptionFilter>
|
|
300
|
+
|
|
301
|
+
<OptionFilter when={{ auth: 'cognito' }} description="Identity extraction for Cognito-authenticated APIs">
|
|
302
|
+
With `auth: 'cognito'`, the API Gateway Cognito User Pools authorizer verifies the JWT that the caller supplies in the `Authorization` header and places the verified claims on the request context.
|
|
303
|
+
|
|
304
|
+
Create `identity.py` alongside `main.py`:
|
|
305
|
+
|
|
306
|
+
<Tabs syncKey="http-rest">
|
|
307
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
308
|
+
```python
|
|
309
|
+
import json
|
|
310
|
+
from typing import Annotated
|
|
311
|
+
|
|
312
|
+
from fastapi import Depends, HTTPException, Request
|
|
313
|
+
from pydantic import BaseModel
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class Identity(BaseModel):
|
|
317
|
+
sub: str
|
|
318
|
+
username: str
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def get_identity(request: Request) -> Identity:
|
|
322
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
323
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
324
|
+
if not request_context_header:
|
|
325
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
326
|
+
|
|
327
|
+
request_context = json.loads(request_context_header)
|
|
328
|
+
claims = request_context.get("authorizer", {}).get("claims", {})
|
|
329
|
+
|
|
330
|
+
sub = claims.get("sub")
|
|
331
|
+
username = claims.get("username")
|
|
332
|
+
|
|
333
|
+
if not sub or not username:
|
|
334
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
335
|
+
|
|
336
|
+
return Identity(sub=sub, username=username)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
340
|
+
```
|
|
341
|
+
</TabItem>
|
|
342
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
343
|
+
HTTP APIs use a JWT authorizer which places the verified claims under `authorizer.jwt.claims`:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
import json
|
|
347
|
+
from typing import Annotated
|
|
348
|
+
|
|
349
|
+
from fastapi import Depends, HTTPException, Request
|
|
350
|
+
from pydantic import BaseModel
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class Identity(BaseModel):
|
|
354
|
+
sub: str
|
|
355
|
+
username: str
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def get_identity(request: Request) -> Identity:
|
|
359
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
360
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
361
|
+
if not request_context_header:
|
|
362
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
363
|
+
|
|
364
|
+
request_context = json.loads(request_context_header)
|
|
365
|
+
claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
|
|
366
|
+
|
|
367
|
+
sub = claims.get("sub")
|
|
368
|
+
username = claims.get("username")
|
|
369
|
+
|
|
370
|
+
if not sub or not username:
|
|
371
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
372
|
+
|
|
373
|
+
return Identity(sub=sub, username=username)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
377
|
+
```
|
|
378
|
+
</TabItem>
|
|
379
|
+
</Tabs>
|
|
380
|
+
|
|
381
|
+
:::tip[No token verification required]
|
|
382
|
+
You don't need any JWT-verification library here — the API Gateway Cognito User Pools authorizer has already verified the signature, issuer, scopes, and expiry by the time your Lambda runs. If any of those checks fail, API Gateway returns `401 Unauthorized` and your handler is never invoked.
|
|
383
|
+
:::
|
|
384
|
+
</OptionFilter>
|
|
385
|
+
|
|
386
|
+
You can then inject the `CurrentUser` dependency into any route that needs the caller's identity:
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
from .identity import CurrentUser, Identity
|
|
390
|
+
from .init import app, tracer
|
|
391
|
+
|
|
392
|
+
@app.get("/me")
|
|
393
|
+
@tracer.capture_method
|
|
394
|
+
def me(identity: CurrentUser) -> Identity:
|
|
395
|
+
return identity
|
|
396
|
+
```
|
|
397
|
+
|
|
188
398
|
<OptionFilter when={{ infra: 'rest-lambda' }} description="Streaming — REST API only">
|
|
189
399
|
### Streaming
|
|
190
400
|
|