Skip to content

New serverless pattern - Strands Agent on Lambda with AgentCore Smithy Bedrock Target - #3221

Open
humem001 wants to merge 3 commits into
aws-samples:mainfrom
humem001:mjhume-feature-strands-agentcore-smithy
Open

New serverless pattern - Strands Agent on Lambda with AgentCore Smithy Bedrock Target#3221
humem001 wants to merge 3 commits into
aws-samples:mainfrom
humem001:mjhume-feature-strands-agentcore-smithy

Conversation

@humem001

@humem001 humem001 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Issue #, if available:

Description of changes:
Strands SDK agent on Lambda calls Bedrock Runtime via AgentCore Gateway using a Smithy
model target, with Cognito JWT auth and IAM role signing.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Statement:
- Effect: Allow
Action:
- bedrock-agentcore:*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of wild card (*) access, can you make it restricted to required actions only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The single bedrock-agentcore:* statement was replaced with two scoped statements:

TokenVaultApiKeyAccess — grants only GetResourceApiKey on the token vault resources. This is the correct action for retrieving API keys stored in the credential provider (e.g., for OpenAI or other MCP tool integrations).
WorkloadIdentityTokenAccess — grants the three workload access token actions on the workload identity directory resources. These are the only actions the gateway execution role needs for identity/auth flows.

Resource:
- !Sub 'arn:aws:bedrock:${AWS::Region}::foundation-model/*'
- !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/*'
- '*'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wildcard (*) opens up everything. Can you make it restricted to required resources only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the '*' resource entry

- bedrock:InvokeModelWithResponseStream
- bedrock:Converse
- bedrock:ConverseStream
Resource: '*'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wildcard (*) opens up everything. Can you make it restricted to required resources only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Replaced Resource: '*' with the same two scoped ARN patterns used in GatewayExecutionRole

- Effect: Allow
Principal:
Service: bedrock-agentcore.amazonaws.com
Action: sts:AssumeRole

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try adding the condition? Please test after adding. Adding these conditions to prevent the cross-service confused deputy problem

AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: bedrock-agentcore.amazonaws.com Action: sts:AssumeRole Condition: StringEquals: aws:SourceAccount: !Ref AWS::AccountId ArnLike: aws:SourceArn: !Sub 'arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:*'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aws:SourceAccount — ensures only resources from your own account can trigger the role assumption, blocking confused deputy attacks where another account's AgentCore service tricks a role.
aws:SourceArn with ArnLike — further scopes it to only AgentCore resources within your specific account and region, so even within the same account only the AgentCore service (not some other AWS service) can assume the role.

- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make it more restrictive? e.g.

Resource: - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-AgentFunction:*'

Test and modify if needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

  • Effect: Allow
    Action:
    • logs:CreateLogGroup
      Resource:
    • !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-AgentFunction'
  • Effect: Allow
    Action:
    • logs:CreateLogStream
    • logs:PutLogEvents
      Resource:
    • !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-AgentFunction:*'

if isinstance(body, dict):
prompt = body.get("prompt", "")
elif isinstance(body, str):
import json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the import statement at the top of the file to maintain standard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — import json is at line 9, in the top-level imports block. The inline import json inside the elif isinstance(body, str) branch was removed at the same time.

Transform: AWS::Serverless-2016-10-31
Description: >
AgentCore Smithy Bedrock Runtime - Serverless AI agent exposing Bedrock Runtime
operations through AWS Bedrock AgentCore Gateway using a Smithy model target.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWS Bedrock AgentCore --> Amazon Bedrock AgentCore

Bedrock --> Amazon Bedrock

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated



def create_mcp_client(gateway_url: str, token: str) -> MCPClient:
"""Create an MCP client configured to connect to AgentCore Gateway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AgentCore Gateway --> Amazon Bedrock AgentCore Gateway

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Configures Claude 3 Sonnet as the LLM provider via AWS Bedrock.

Args:
model_id: Bedrock model identifier. Defaults to Claude 3 Sonnet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the model id accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all three references now correctly say Claude Sonnet 4.6:

Comment on DEFAULT_MODEL_ID
Function docstring: Configures Claude Sonnet 4.6 as the LLM provider
Args description: Defaults to Claude Sonnet 4.6

One command deploys everything:

```bash
bash scripts/deploy.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy failed with the following issue:

`strands-agentcore-smithy % bash scripts/deploy.sh

Deploying stack: agentcore-smithy-bedrock in region: us-east-1

Step 1: Validating SAM template...

SAM CLI update available (1.163.0); (1.162.1 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
Template validation successful.

Step 2: Uploading Bedrock Runtime Smithy model to S3...
upload failed: ../../../../../../../tmp/bedrock-runtime-2023-09-30.json to s3://agentcore-smithy-bedrock-smithy-models/bedrock-runtime-2023-09-30.json An error occurred (AccessDenied) when calling the PutObject operation: Access Denied`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Three things fixed:

Removed duplicate prerequisites — they were listed twice (under ## Prerequisites and ### Step 1: Prerequisites). Now a single clean list.
Added IAM permissions note — explicitly tells users they need permissions for CloudFormation, IAM, Lambda, S3, Cognito, and AgentCore. This directly addresses the AccessDenied deployment failure.
Fixed step numbering — Deploy steps now run 1→2→3 cleanly.
The AccessDenied error itself is a credentials/IAM issue in the user's AWS account — the user needs to ensure their AWS CLI credentials have the necessary S3 permissions before running the deploy script.

@biswanathmukherjee biswanathmukherjee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please make the requested changes?

Comment on lines +111 to +172
## Changing the Outer Agent Model

The outer agent model is set in `src/agent/strands_client.py`:

```python
# Default Bedrock model ID for Claude Sonnet 4.6
DEFAULT_MODEL_ID = "us.anthropic.claude-sonnet-4-6"
```

To use a different model, update `DEFAULT_MODEL_ID` to any Bedrock cross-region inference profile ID you have access to. For example:

```python
DEFAULT_MODEL_ID = "us.anthropic.claude-3-5-sonnet-20241022-v2:0"
```

Make sure the model is enabled in your AWS account under **Bedrock → Model access** before deploying.

> **Note:** The inner model (the one the agent calls as a tool) is separate — it's configured in the `SYSTEM_PROMPT` string in `src/agent/agent_processor.py`. See [Changing the Inner Model](#changing-the-inner-model) and [Key Design Decisions](#key-design-decisions) for more detail.

## Changing the Inner Model

The inner model is the one the outer agent calls *as a tool* via the AgentCore Gateway. It's configured inside the `SYSTEM_PROMPT` string in `src/agent/agent_processor.py`:

```python
SYSTEM_PROMPT = """You have access to Bedrock Runtime tools via MCP. Use the Converse tool (not InvokeModel) to call another model.

When using bedrock-runtime-target___Converse:
- Set modelId to: anthropic.claude-haiku-4-5-20251001-v1:0
...
```

To use a different model, replace the `modelId` value in that instruction:

```python
- Set modelId to: anthropic.claude-haiku-4-5-20251001-v1:0
```

A few things to keep in mind:

- The model ID here is passed as a parameter in the MCP tool call, not used directly by the Lambda — so it must be a valid Bedrock `modelId` (not a cross-region inference profile).
- Make sure the model supports the **Converse API** (most Claude models do, but check the [Bedrock docs](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html)).
- Enable the model in your AWS account under **Bedrock → Model access** before deploying.

## Key Design Decisions

### Official Smithy Model (not custom)
Custom Smithy models don't work for AWS service targets — the gateway needs the full endpoint resolution metadata (`aws.api#service` trait, partition data, FIPS/DualStack logic) that only official models have. The deploy script downloads the official model from the [AWS API Models repo](https://github.com/aws/api-models-aws).

### Converse API (not InvokeModel)
The `InvokeModel` operation uses a `Blob` with `httpPayload` trait for the request body, which doesn't map to MCP tool input parameters. `Converse` has structured `messages` and `modelId` fields that map cleanly.

### GATEWAY_IAM_ROLE (not API_KEY)
Since the target is an AWS service (Bedrock Runtime), the gateway uses its IAM execution role to sign requests. No API keys, no Secrets Manager secrets, no credential provider CLI commands.

### Agent code is target-agnostic
All code in `src/agent/` and `src/shared/` connects to the gateway via MCP and doesn't reference Bedrock, DynamoDB, or any specific target. The same code works with any gateway target type.

## Run Tests

```bash
python3 -m pytest tests/ -v
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patterns are to showcase simple integration between different services. This section add complexity to the pattern. Also I got the following error:

ImportError while loading conftest '.../serverless-patterns/strands-agentcore-smithy/tests/conftest.py'. tests/conftest.py:9: in <module> import jwt E ModuleNotFoundError: No module named 'jwt'

## Prerequisites

- AWS CLI v2
- Python 3.12+

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the latest supported Python version.

- Python 3.12+
- pip3
- AWS account with Amazon Bedrock and Amazon Bedrock AgentCore enabled in `us-east-1`
- Amazon Bedrock model access enabled for Claude Sonnet 4.6 and Claude Haiku 4.5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the latest supported version to avoid the pattern getting dated sooner.

"description": "Strands SDK agent on AWS Lambda calls Amazon Bedrock Runtime via Amazon Bedrock AgentCore Gateway using a Smithy model target, with Cognito JWT auth and IAM role signing.",
"language": "Python",
"level": "300",
"framework": "CloudFormation",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWS CloudFormation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants