DEV Community

Vadym Kazulkin for AWS Heroes

Posted on • Edited on

Amazon Bedrock AgentCore Runtime - Part 2 Using Bedrock AgentCore Runtime Starter Toolkit with Strands Agents SDK

Introduction

In the part 1 of the series, we gave the introduction to the Amazon Bedrock AgentCore Runtime and the benefits it offers. We also described its key components.

In the this article, we'll deploy the same agent with the Amazon Bedrock AgentCore Runtime starter toolkit, that we implemented in the articles Exposing existing Amazon API Gateway REST API via MCP and Gateway endpoint and Exposing existing AWS Lambda function via MCP and Gateway endpoint. So, the precondition is that we did the whole setup described for example in the article Exposing existing Amazon API Gateway REST API via MCP and Gateway endpoint which includes creating Cogntio User Pool, Cognito Resource Server and Cognito User Pool Client and finally having AgentCore Gateway URL.

The only difference is that we'll host our agent now with AgentCore Runtime instead of running it locally. We won't use AgentCore Memory feature in this example and explore it in the later article.

Amazon Bedrock AgentCore Runtime Starter Toolkit

The Amazon Bedrock AgentCore Python SDK provides a lightweight wrapper that helps us deploy our agent functions as HTTP services compatible with Amazon Bedrock AgentCore. It handles all the HTTP server details so we can focus on our agent's core functionality.

In this article, we'll use the Bedrock AgentCore SDK and Starter Toolkit for quick prototyping and in the next part of the series we'll explore Custom Agent implementation which is a better choice when we need full control over our agent's HTTP interface.

I have provided the full source code in my amazon-agentcore-runtime-to-gateway-custom-agent-dem GitHub repository.

First, we need to make sure that all dependencies from the requirements.txt.

strands-agents
strands-agents-tools
uv
boto3
bedrock-agentcore
bedrock-agentcore-starter-toolkit
Enter fullscreen mode Exit fullscreen mode

are installed.
we can achieve it by executing

!pip install --force-reinstall -U -r requirements.txt --quiet
Enter fullscreen mode Exit fullscreen mode

Now let's look at the agent code

We import bunch of dependencies including importing AgentCore Runtime with:

from bedrock_agentcore.runtime import BedrockAgentCoreApp
Enter fullscreen mode Exit fullscreen mode

Then we initialize the App with:

app = BedrockAgentCoreApp()
Enter fullscreen mode Exit fullscreen mode

Then we decorate our function with @app.entrypoint :

@app.entrypoint
def invoke(payload)
....
Enter fullscreen mode Exit fullscreen mode

The full code of the invoke function looks like this:

@app.entrypoint
def invoke(payload):
  prompt = payload.get("prompt")
  model = BedrockModel(
  model_id="us.amazon.nova-pro-v1:0",
  temperature=0.7)

  user_pool_id, client_id, client_secret, scopeString = get_auth_info()
  access_token = get_auth_token(user_pool_id, client_id, client_secret, scopeString)

  mcp_client = MCPClient(lambda: create_streamable_http_transport(gateway_url, access_token))

  with mcp_client:
      tools = get_full_tools_list(mcp_client) 
      agent= Agent(model=model, tools=tools)
      return agent(prompt)
Enter fullscreen mode Exit fullscreen mode

In this function, we first get the prompt and initialize the model. We use Amazon Nova Pro model, but we can use any Bedrock model like Claude Sonnet but we need to ensure that in case we use Amazon Bedrock, the model is enabled in the Model Access or the local model like Ollama.

Then we get authentication info like user pool id, client id, client secret from the Coginito User Pool (function get_auth_info) to obtain authentication token (function get_auth_token ) to communicate with AgentCore Gateway. For more details, please read my article Exposing existing Amazon API Gateway REST API via MCP and Gateway endpoint.

When we initialze MCP client. To make this example work we need to replace the gateway_url varialbe in the agentcore_runtime_demo.py

gateway_url = "${YOUR_GATEWAY_URL}"
Enter fullscreen mode Exit fullscreen mode

with the value of the existing AgentCore Gateway, which can be one that we deployed in Exposing existing Amazon API Gateway REST API via MCP and Gateway endpoint or Exposing existing AWS Lambda function via MCP and Gateway endpoint articles. I personally tested with the Amazon API Gateway exposed via MCP as an AgentCore Gateway endpoint.

After this we get the list of the tools from the MCP client and initialize the Agent (which is Strands Agent) with this tool's list and model. Then we invoke the agent by passing a prompt as a parameter and deliver the result back to the client.

Configuring our Agent

Make sure that Amazon Bedrock AgentCore Runtime starter toolkit is installed (should be the case as we have already installed all dependencies in the requirement.txt):

pip install bedrock-agentcore-starter-toolkit
Enter fullscreen mode Exit fullscreen mode

To configure the agent, execute:

agentcore configure --entrypoint agentcore_runtime_demo.py -er IAM_ARN 
Enter fullscreen mode Exit fullscreen mode

We can omit the -er parameter and an IAM role with sufficient permissions will be created for us. But as we need access to Cognito User Pool to obtain authentication token we need to add the following to the attached execution policy:

{
        "Effect": "Allow",
        "Action": [
            "cognito-idp:ListUserPools"
        ],
        "Resource": [
            "*"
        ]
    },
    {
        "Effect": "Allow",
        "Action": [
            "cognito-idp:DescribeUserPool",
            "cognito-idp:DescribeResourceServer",
            "cognito-idp:ListUserPoolClients",
            "cognito-idp:DescribeUserPoolClient"
        ],
        "Resource": [
            "arn:aws:cognito-idp:${region}:${account_id}:userpool/*"
        ]
    },
Enter fullscreen mode Exit fullscreen mode

Please replace ${region} and ${account_id} variables with our values, and we can specify the concrete user pool instead of using "*" wildcard. We'll automate this IAM policy creation later, but below we can see the complete code of the execution policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ECRImageAccess",
            "Effect": "Allow",
            "Action": [
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer"
            ],
            "Resource": [
                "arn:aws:ecr: ${region}:${account_id}:repository/${agentcore-runtime-ecr-repo}"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cognito-idp:ListUserPools"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cognito-idp:DescribeUserPool",
                "cognito-idp:DescribeResourceServer",
                "cognito-idp:ListUserPoolClients",
                "cognito-idp:DescribeUserPoolClient"
            ],
            "Resource": [
                "arn:aws:cognito-idp: ${region}:${account_id}:userpool/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:DescribeLogStreams",
                "logs:CreateLogGroup"
            ],
            "Resource": [
                "arn:aws:logs: ${region}:${account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:DescribeLogGroups"
            ],
            "Resource": [
                "arn:aws:logs: ${region}:${account_id}:log-group:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs: ${region}:${account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
            ]
        },
        {
            "Sid": "ECRTokenAccess",
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords",
                "xray:GetSamplingRules",
                "xray:GetSamplingTargets"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Resource": "*",
            "Action": "cloudwatch:PutMetricData",
            "Condition": {
                "StringEquals": {
                    "cloudwatch:namespace": "bedrock-agentcore"
                }
            }
        },
        {
            "Sid": "GetAgentAccessToken",
            "Effect": "Allow",
            "Action": [
                "bedrock-agentcore:GetWorkloadAccessToken",
                "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
                "bedrock-agentcore:GetWorkloadAccessTokenForUserId"
            ],
            "Resource": [
                "arn:aws:bedrock-agentcore: ${region}:${account_id}:workload-identity-directory/default",
                "arn:aws:bedrock-agentcore: ${region}:${account_id}:workload-identity-directory/default/workload-identity/agentcore_runtime_agent-*"
            ]
        },
        {
            "Sid": "BedrockModelInvocation",
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel",
                "bedrock:InvokeModelWithResponseStream"
            ],
            "Resource": [
                "arn:aws:bedrock:*::foundation-model/*",
                "arn:aws:bedrock:${region}:${account_id}:*"
            ]
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Once again please replace ${region} and ${account_id} and additionally ${agentcore-runtime-ecr-repo} variables with our values.

What we see on the terminal is that we're asked to provide the Amazon Elastic Container Registry (ECR) Repository URL (if not, a new one will be created for us to storage a Docker image of this agent) and we confirm that we want to use IAM authentication. ECR repository is required for the AgentCore Runtime to pull the image of our Agent code.

After the command is executed successfully, we see the configuration summary:

We also see that some additional files are auto generated like Dockerfile (we don't need to change something there), which will be required to build the Docker image and push it into the ECR repository:

This is how the generated Dockerfile looks like:

FROM public.ecr.aws/docker/library/python:3.13-slim
WORKDIR /app

COPY requirements.txt requirements.txt
# Install from requirements file
RUN pip install -r requirements.txt

RUN pip install aws-opentelemetry-distro>=0.10.1


# Set AWS region environment variable

ENV AWS_REGION=us-east-1
ENV AWS_DEFAULT_REGION=us-east-1

# Signal that this is running in Docker for host binding logic
ENV DOCKER_CONTAINER=1

# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore

EXPOSE 8080
EXPOSE 8000

# Copy entire project (respecting .dockerignore)
COPY . .

# Use the full module path

CMD ["opentelemetry-instrument", "python", "-m", "agentcore_runtime_demo"]

Enter fullscreen mode Exit fullscreen mode

As we see extra package aws-opentelemetry-distro is installed and our agent is instrumented with the opentelemetry-instrument. We'll cover AgentCore Observability in the extra article.

We're now done with the agent configuration part.

Deploying our Agent to the AgentCore Runtime

Then we need to launch the AgentCore runtime, we can do it locally by executing

agentcore launch -l
Enter fullscreen mode Exit fullscreen mode

or directly in the AWS cloud (which we'll do) by executing

agentcore launch
Enter fullscreen mode Exit fullscreen mode

What we'll see is that AgentCore zipes the deployment package (all Python files, requirement.txt and Dockerfile), uploads them to S3 and launches Amazon CodeBuild service to build the Docker image and push it to ECR. Currently linux/arm64 architecture is required to build the image and it's good that AgentCore SDK and Starter Toolkit takes care of it:

We can see the same steps by looking into the details of the CodeBuild history:

After this step we'll be able to see the AgentCore Runtime deployed:

I have version 2, because I updated an already existing version. If we deploy it for the first time, it will be version 1.

In the agent details we see the agent's name, runtime id, created and updated dates and links to the ECR repository and CloudWatch GenAI Observability.

Below we see the information about Runtime endpoint and versions:

Invoking our Agent

Let's test our agent with SDK and Starter Toolkit first.

agentcore invoke '{"prompt": "Give me the information about order with id 12345"}' 
Enter fullscreen mode Exit fullscreen mode

And the agent response is:

As it's JSON response we can parse the value of the property "response" to get only model response from the body without additional metadata.

We can also test the Runtime endpoint via the UI of the AgentCore Runtime service itself by selecting our endpoint and pressing "Test Endpoint" button:

Finally, we can do the same with AWS Python SDK, like in the provided below example invoke_agent.py

import boto3
import json
import os

agent_core_client = boto3.client('bedrock-agentcore', region_name=os.environ['AWS_DEFAULT_REGION'])

payload = json.dumps({"prompt": "Give me the information about order with id 12345"})

response = agent_core_client.invoke_agent_runtime(
    agentRuntimeArn="{YOUR_RUNTIME_ARN}",
    qualifier="DEFAULT",
    payload=payload
)
response_body = response['response'].read()
response_data = json.loads(response_body)
print("Agent Response:", response_data)
Enter fullscreen mode Exit fullscreen mode

Please replace the value of the variable agentRuntimeArn with our deployed AgentCore Runtime ARN.

Of course, we can ask our developed agent to answer other questions. We can find some of such examples in my article Exposing existing Amazon API Gateway REST API via MCP and Gateway endpoint as in our example we used the same AgentCore Gateway which exposed the same Amazon Gateway Order API as MCP tools.

In these examples, our agent returned the results synchronously. Of course, Strands Agents can do it asynchronously. Here is the streaming agent example, and our invoke_agent.py can handle the agent’s response of the content type equal to text/event-stream. We can read more about invocation of the different agent types in the article Invoke an AgentCore Runtime agent.

Conclusion

In the this article, we deployed the same agent with the Amazon Bedrock AgentCore Runtime starter toolkit that we implemented in the articles Exposing existing Amazon API Gateway REST API via MCP and Gateway endpoint and Exposing existing AWS Lambda function via MCP and Gateway endpoint. The only difference is that we hosted our agent with the AgentCore Runtime instead of running it locally. For that we used very simple Bedrock AgentCore SDK and Starter Toolkit for quick prototyping. We also explored different ways of invoking our agent.

In the next parts of the series, we'll look deeper at the GenAI Observability and also use Custom Agent implementation instead of the Starter Toolkit which gives us full control over our agent's HTTP interface.

Please also check out my Amazon Bedrock AgentCore Gateway article series.

Top comments (0)