AWS Builder Center
Build an agentic AI application with Bedrock AgentCore and Spring AI

Build an agentic AI application with Bedrock AgentCore and Spring AI

AI engineering is an emerging trend after the recent breakthroughs in Foundation Models the last years. In this post we will see how can you start building your AI application by implementing production ready agents and cloud-based MCP servers. We will learn how we can combine Amazon Bedrock AgentCore, Strands Agents and Spring AI, 3 state of the art technologies in the AI world, to build our Agentic AI application

The last couple of years, there is an increasing demand for AI based applications that cover many different areas. From internal knowledge bases, to hyper personalisation and to customer support, more and more companies are trying to integrate Foundation Models (FMs) to improve the user experience. With services like Amazon Bedrock, developers have now access to many powerful FMs with a model-as-a-service approach. Therefore development teams can build AI applications without the need to have an in-depth knowledge of machine learning algorithms and other AI techniques
In this post, we will see how you can build an Agentic AI application by building a Spring-based MCP server and connecting it to a Strands agent deployed on Amazon Bedrock AgentCore. We will mostly focus on the implementation side of things and how to integrate the different components in order to have a good foundation to build your custom Agentic AI application with the same tools!

Architecture Overview

Let's have a look first at the architecture of our Agentic AI Application and the different components.
As you can see the application is consisted of 3 components.
  1. The Agent, build on Strands Agents Framework and deployed on Bedrock AgentCore Runtime , the serverless hosting environment for deploying Agents built by AWS
  2. The Foundation Model, hosted in Amazon Bedrock. We will use Claude Sonnet 3.7, but feel free to experiment with different ones
  3. The MCP server implemented in Java with Spring Boot and Spring AI deployed in Amazon ECS Fargate and fronted by an Application Load Balancer
Architecture Diagram

Building the MCP Server

Model Context Protocol is an open source standard developed by Anthropic for connecting Agentic AI applications with external tools. In the official documentation of MCP, it is very accurately described as a USB-C port as a provided standard protocol to connect devices. I really resonate this description since it summarizes the simplicity and the portability of the protocol! Therefore, MCP provides a standard to connect AI applications with external systems. Databases, search engines, local filestore, can be accessible by our Agentic AI application and provide useful information in combination with a FM.
In our application, the MCP server is implemented with Java, Spring Boot and Spring AI. We will also follow the hexagonal (or ports and adapters) architecture pattern for the software design of the MCP server.
Why hexagonal? Well why not??? Jokes aside it is a very popular pattern especially when implementing microservices architecture and since we want to have loosely-coupled components, following a microservices approach is the way to go. Furthermore, because of my software engineering background, I kind of perceive AI engineering as an evolution of software engineering by utilizing FMs and other AI tools and services. I might be over-simplifying here but coming from the software engineering world I am compiling (sic) the different components of an Agentic AI application (MCP tools, other agents etc) as micro-services or micro-ai-components. (will try to thing for a better name, promise :-))
So, here is a diagram describing the pattern from wikipedia.
By Cth027 - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=81276242
For the ones who are not familiar with the pattern, hexagonal architecture divides the application (or system or platform or service) to loosely couple components which are interchangeable. Examples of components are the following.
  • Application Domain (core)
  • Databases
  • User interface
  • Test system
  • etc...
The components interact with each other via ports and different communication protocols can be used depending on the use case (REST API, topics, grpc). As soon as you implement the port(s) then the adapters can connect to them and interact with the core of the application.
Following the hexagonal pattern, we broke down the MCP server to the following modules:
MCP Server project structure
mcp-server-application which is the core module of the server. If you are familiar with Spring Boot, here the @SpringBootApplication annotated class and the application properties are residing and that's it pretty much.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.aws.mcp.server;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class McpServerApplication {

public static void main(String[] args) {
log.info("=============================================");
log.info("Starting MCP Server with Streamable protocol!");
log.info("=============================================");
SpringApplication.run(McpServerApplication.class, args);
}

}
The MCP server is using Streamable-HTTP to communicate with the agent, so in the application.properties we are setting it up
1
2
3
spring.ai.mcp.server.name=mcp-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.protocol=STATELESS
Streamable-HTTP transport protocol has replaced the already obsolete HTTP+SSE. It allows the server to operate independently and can handle multiple connections using POST & GET. We are also using Stateless Streamable-HTTP in order to avoid implementing session affinity since we are deploying more than one tasks for our server. The caveat here is that session is not maintained between request. The stateful Streamable-HTTP will be cover in a future post, will keep you posted!
mcp-server-domain, this module is the core module of the hexagonal architecture. Here we are implementing the MCP tools and any additional logic that will be needed from our MCP Server. To keep things simple we are building calculator tools which of course can be found in almost any agent framework, but still it is a good starting point to build an MCP server and test the communication with an agent. We are implementing them in the following class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.aws.mcp.server.tools;

import lombok.extern.slf4j.Slf4j;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class CalculatorService {

@McpTool(name = "add", description = "Add two numbers")
public int add(@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b)
{
log.info("Adding: " + a + " and " + b);
return a + b;
}

@McpTool(name = "subtract", description = "Subtract two numbers")
public int subtract(@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b)
{
log.info("Substracting: " + b + " from " + a);
return a - b;
}

@McpTool(name = "multiply", description = "Multiply two numbers")
public int multiply(@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b)
{
log.info("Multiplying: " + a + " and " + b);
return a * b;
}

@McpTool(name = "divide", description = "Divide two numbers")
public int divide(@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b)
{
log.info("Dividing: " + a + " to " + b);
return a / b;
}

}
We are using the spring ai version 1.1.0 which introduced a lot of improvements in the MCP server capabilities and one of the most prominent ones was the stable @McpTool annotation. Beforehand you had to use the @Tool annotation and register a bean with a ToolCallbackProvider for the respective tool domains. With the @McpTool annotation Spring takes cares of the tool registration automatically and yes this is why Spring is so popular and awesome!
Other useful annotations for spring AI are the following:
1
2
3
@MCPResource - For Access to resources via URI
@McpPrompt - For prompt messages
@McpComplete - For auto-completion functionality
mcp-server-ports in this module we are building the ports of the hexagonal architecture. Spring AI exposes tool directly and Spring Actuator offers automated health-check endpoints, so this module contains only a couple of REST APIs for test purposes. This APIs will not be used by the AgentCore Gateway to establish communication between the agent and the MCP Server
mcp-server-persistence, currently an empty module but we can implement here all DB related things, like entities, repositories etc
infrastructure. The cdk stack which deploys a VPC, 4 subnets (2 public and 2 private), an Application Load Balancer, an ECS Fargate cluster, a service consisted of 2 tasks and a task definition. In order to deploy the task, we are pushing the container image of the mcp server to an ECR repository and it is used from there to deploy the service. We could have used the dns name of the ECS cluster, but we are putting it behind an Application Load Balancer for the following reasons:
  1. We want to have a stable dns to expose to the agents in case we need to redeploy the cluster seamlessly
  2. HTTPS. AgentCore Gateway targets support only https communication for MCP servers, so we need to provide an HTTPS endpoint. With our setup there are 2 ways to implement that. The first one and the one that we followed is by registering a certificate in AWS Certificate Manger and connecting it with a DNS name! The second option is to build a CloudFront distribution, which offers https by default. We opted for the firs one since we considered that we might want to build a stateful communication in the future and I did not test who CloudFront will be able to support open session for Streamable HTTP communication. But well, this is another story for another post! Yes, I know you can't wait but please be patient, I am a father of a newborn!
For new features, bug fixes and any other update to the MCP server we are using gitlab pipelines which are triggered whenever there is a merge of a feature branch and they update the container image host on ECR and trigger a redeployment with the updated task definition, wow boy I feel like a DevOps engineer now!!

Building the Agent

So our MCP server is ready, now it's Agent time!! From the different frameworks to implement agents like langchain, crewAI and Strands, we opted for the latter, because it offers the strongest AWS integration and we will deploy our agent to Amazon Bedrock AgentCore Runtime. Furthermore, it offers a wide range of FM via Amazon Bedrock.
Amazon Bedrock Agentcore is consisted of many different components and for this application we will focus on 2
  • AgentCore Runtime, the hosting environment of our agent
  • AgentCore Gateway, for easy and secure way of connecting our tools from the MCP server to the agent
But first things first, let's actual write some code for our agent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def create_agent():
"""Create and configure the Strands agent with MCP integration."""
session = boto3.Session(
region_name='us-east-1'
)

# Get model configuration from environment
model = BedrockModel(
model_id="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
boto_session=session
#temperature=0.3,
)
aws_region = os.getenv("AWS_REGION", "us-east-1")

logger.info(f"Configuring agent - Model: {model}, Region: {aws_region}")

# Create MCP client for calculator tools
mcp_client = create_mcp_client()

try:
# Try to connect to MCP server and get tools
with mcp_client:
logger.info("Connecting to MCP server for calculator tools")
mcp_tools = mcp_client.list_tools_sync()
logger.info(f"Retrieved {len(mcp_tools)} tools from MCP server")

# Use MCP calculator tools instead of local calculator
all_tools = [shell, http_request] + mcp_tools

# Create the agent with MCP tools
agent = Agent(
system_prompt=SYSTEM_PROMPT,
tools=all_tools,
model=model,
)

logger.info("Agent created successfully with MCP calculator tools")
print("✅ Connected to MCP server - Using remote calculator tools")
return agent, mcp_client

except Exception as e:
logger.warning(f"Failed to connect to MCP server: {e}")
print("⚠️ MCP server unavailable - Using local tools only")

# Fallback to local tools without calculator
agent = Agent(
system_prompt=SYSTEM_PROMPT,
tools=[python_repl, calculator, shell, http_request],
model=model,
)

logger.info("Agent created with local tools only")
return agent, None
As you can see from the code first of all we are selecting the model of our preference for our Agentic AI application, which in that case would be Claude 3.7 and it will be invoked via Amazon Bedrock. Here , you can check the available FMs which honestly are a LOT! Then we will try to create an MCP client but we will leave it for now to focus on the Agent code. In Lines 30-34 and 46-49 we are building the Agent object based on the tools that we have available, which depend on if we are connecting to an MCP server or not.
  • If yes, we are getting the tools of the server + shell and http_request built in tools from Strands
  • if not we are registering python_repl, calculator, shell and http_request.
Ideally we would like to use the calculator tools of our MPC server, not because they are better from the built ones (they actually might be :D), but because we want to establish communication with the MCP server for future tool additions (yes, this is for the next posts!)
Of course an agent should have a specific well defined prompt which is store in the SYSTEM_PROMPT variable. It can be something like that:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
SYSTEM_PROMPT = """
You are a helpful AI assistant with access to advanced calculator tools through an MCP (Model Context Protocol) server. Your primary purpose is to assist users with mathematical calculations, problem-solving, and numerical analysis.

## Your Core Capabilities

1. **Calculator Tools** - Access to mathematical operations through MCP server tools:
- Basic arithmetic (addition, subtraction, multiplication, division)

2. **Problem Solving** - Help users break down complex mathematical problems into manageable steps

3. **Explanation & Education** - Provide clear explanations of mathematical concepts and calculation methods

## Your Mission

- **Accurate Calculations**: Use the MCP calculator tools to perform precise mathematical operations
- **Step-by-Step Solutions**: Break down complex problems and show your work clearly
- **Educational Support**: Explain mathematical concepts and reasoning behind calculations
- **Verification**: Double-check important calculations using appropriate tools
- **Clear Communication**: Present results in an easy-to-understand format

## Guidelines

- Always use the available MCP calculator tools for mathematical operations rather than attempting calculations manually
- Show your work step-by-step for complex problems
- Explain the reasoning behind your approach to mathematical problems
- Verify important calculations when accuracy is critical
- Ask for clarification if a mathematical problem or request is ambiguous
- Provide context and explanations alongside numerical results

## Tool Usage

When performing calculations:
1. Use the appropriate MCP calculator tool for the operation
2. Explain what calculation you're performing and why
3. Present the result clearly
4. Provide additional context or explanation when helpful

Think step by step, use the calculator tools effectively, and always prioritize accuracy and clarity in your mathematical assistance.
"""
In our case we are asked our best coding friend Kiro  to come up with a prompt and after some tweaks we were ready to go! You can do the same, ask your coding assistant (yes, Kiro!!) to come up with some good ideas and modify it according to your use case.
Last but not least, we need to also expose our agent. Since we will deploy to AgentCore Runtime, we need to satisfy these  requirements. We are not using the starter toolkit for deployment, so we need to handle these requirements programatically.
We are using HTTP Protocol, so we are exposing these 2 endpoints:
  • /invocations (POST)
  • /ping (GET)
We built these endpoints using FastAPI as you can see below. For the /invocations enpoint:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@app.post("/invocations", response_model=InvocationResponse)
async def invoke_agent(request: InvocationRequest):
"""AgentCore-compatible invocation endpoint."""
try:
user_message = request.input.get("prompt", "")
if not user_message:
raise HTTPException(
status_code=400,
detail="No prompt found in input. Please provide a 'prompt' key in the input."
)

logger.info(f"Processing invocation request: {user_message[:100]}...")

# Process with agent (handle MCP context if available)
if mcp_client_instance:
with mcp_client_instance:
result = agent_instance(user_message)
else:
result = agent_instance(user_message)

# Determine available tools
tools_info = "Python REPL, Shell, HTTP Request"
if mcp_client_instance:
tools_info += ", Calculator Tools (add, subtract, multiply, divide)"

response = {
"message": result.message,
"timestamp": datetime.utcnow().isoformat(),
"agent": "My Agent",
"version": "1.0.0",
"tools_available": tools_info,
"mcp_enabled": mcp_client_instance is not None
}

return InvocationResponse(output=response)

except Exception as e:
logger.error(f"Invocation processing failed: {str(e)}")
raise HTTPException(status_code=500, detail=f"Agent processing failed: {str(e)}")
and for the /ping endpoint:
1
2
3
4
5
6
7
8
9
10
@app.get("/ping")
async def ping():
"""Health check endpoint."""
return {
"status": "healthy",
"agent": "My Agent",
"version": "1.0.0",
#"mcp_enabled": mcp_client_instance is not None,
"timestamp": datetime.utcnow().isoformat()
}

Deploying the application

So now it's time to deploy our Agentic AI application on AWS. We will deploy the following 3 components:
  • The MCP Server
  • The Agent
  • The AgentCore Gateway for the communication between the 2 components
As mentioned before, there is the infrastructure folder within the mcp-server project, which is cdk java code to deploy the required AWS components and then, for any updates or new features, we are using gitlab pipelines to deploy every new change to the server. I am not attaching the source code of the MCP server, but with Kiro's help you can have very fast the required setup (VPC, ALB, ECS, service, task definition, ECR repository to run your MCP server. Once again, bear in mind that AgentCore Gateway requires HTTPS for MCP target, so you need to expose either an HTTPS endpoint from your ALB (so do not forget to implement a certificate for your URL) or use Amazon CloudFront.
After that, to be able to connect with the MCP server we will deploy an AgentCore Gateway with the HTTPS URL of the MCP server as a target. In that case we are following a different approach based on Boto3 Python SDK
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def create_agentcore_gateway():
"""Create AgentCore Gateway for MCP server integration."""

# Initialize AgentCore client
client = boto3.client('bedrock-agentcore-control')

# Gateway configuration
gateway_name = "my-mcp-gateway"
mcp_server_url = YOUR_ALB_HTTPS_DNS_NAME/mcp

try:
# Step 1: Create the gateway
print(f"🌉 Creating AgentCore Gateway: {gateway_name}")

gateway_response = client.create_gateway(
name=gateway_name,
protocolType= 'MCP',
authorizerType='NONE',
roleArn='<THE_ROLE_ARN_FOR_THE_GATEWAY>'
)

gateway_id = gateway_response['gatewayId']
print(f"✅ Gateway created successfully: {gateway_id}")

# Wait for gateway to be ready
if not wait_for_gateway_ready(client, gateway_id):
print("❌ Gateway creation timeout")
return None

# Step 2: Create MCP server target
print(f"🎯 Adding MCP server target: {mcp_server_url}")

target_response = client.create_gateway_target(
gatewayIdentifier=gateway_id,
name="my-mcp-server-tools",
targetConfiguration={
'mcp': {
'mcpServer': {
'endpoint': mcp_server_url
}
}
}
)

target_id = target_response['targetId']
print(f"✅ MCP server target created: {target_id}")

# Wait for gateway to be ready
if not wait_for_gateway_target_ready(client, gateway_id, target_id):
print("❌ Gateway target creation timeout")
return None

# Step 3: Synchronize gateway to discover tools
print("🔄 Synchronizing gateway with MCP server...")

client.synchronize_gateway_targets(
gatewayIdentifier=gateway_id,
targetIdList=[target_id]
)

# Step 4: Wait for synchronization to complete
print("⏳ Waiting for synchronization to complete...")
wait_for_synchronization(client, gateway_id, target_id)

# Step 5: Save gateway configuration
gateway_config = {
"gateway_id": gateway_id,
"gateway_name": gateway_name,
"target_id": target_id,
"mcp_server_url": mcp_server_url,
"status": "ACTIVE",
"created_at": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime())
}

config_file = "gateway_config.json"
with open(config_file, "w") as f:
json.dump(gateway_config, f, indent=2)

print(f"✅ Gateway configuration saved to {config_file}")

print("\n🎉 AgentCore Gateway setup completed successfully!")
print("=" * 60)
print(f"Gateway ID: {gateway_id}")
print(f"Target ID: {target_id}")
print(f"MCP Server: {mcp_server_url}")
print(f"Configuration: {config_file}")

return gateway_config

except ClientError as e:
print(f"❌ Error creating gateway: {e}")
return None
You might have noticed that there a couple of functions with wait_ prefix. Since the gateway components take a couple of seconds to be registered, we have added these check status functions with retry mechanisms, which periodically check the status of the component and as soon as it is ready, it moves to the next function. I am not posting the code here, since it is quite straightforward, but if you need any help on that, just ping me!
We are also storing the configurations of the AgentCore Gateway to a json file which we will use to build the MCP client of our main Agent when we deploy it to AgentCore Runtime. We will specifically use the AgentCore Gateway endpoint that we built beforehand and we will implement an MCPClient with that endpoint. So when we will deploy it to AgentCore Runtime, the Agent will use this connectivity with the MCP Server
1
2
3
4
5
def create_mcp_client():
"""Create MCP client using AgentCore Gateway or fallback to direct connection."""
gateway_endpoint = YOUR_GATEWAY_ENDPOINT/mcp
logger.info(f"Gateway endpoint: {gateway_endpoint}")
return MCPClient(lambda: streamablehttp_client(gateway_endpoint))
If you look back at the Agent implementation code, you can see that as soon as the MCP client is present, then it will use the tools provided by the MCP server (which is the target of the Gateway, or for local testing the URL of the Application Load Balancer) in order to do the calculations.
So now let's deploy our agent to Amazon Bedrock AgentCore Runtime.
Before we implement the code to deploy the agent we need to build a Docker image of the agent which will be pushed to our ECR repo. Bear in mind to give the appropriate permissions of the agent role while deploying the agent to AgentCore Runtime.
To deploy the agent we keep using Boto3 SDK, which implements all required AgentCore functions to deploy on AgentCore Runtime and Docker to create the image.
Firstly we are building the Docker image and push it to an ECR repository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Use Python 3.12 slim image for smaller size
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PORT=8080

# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*


# Copy requirements first for better caching
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY my_agent.py .
COPY .env.example .env

# Create non-root user for security
RUN useradd --create-home --shell /bin/bash my_user && \
chown -R my_user:my_user /app

USER my_user

# Expose port
EXPOSE 9000
EXPOSE 8000
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/ping || exit 1


# Run the application
CMD ["python", "my_agent.py"]
After that we are implementing the deployment code of the agent. First of all we need a role for our agent and you can find the required permissions here . Hint: you might need a couple of more permissions to be able to invoke an Amazon Bedrock Model. ;)
To set up the policy and the role, then the Python code will look like that:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def create_role():
iam = boto3.client('iam')

...
role_name = 'AgentRuntimeRole'
try:
# Create role
print(f"Creating role: {role_name}")
role_response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description='Role for My Agent'
)

role_arn = role_response['Role']['Arn']
print(f"✅ Role created: {role_arn}")

# Create policy
policy_name = 'AgentRuntimePolicy'
policy_response = iam.create_policy(
PolicyName=policy_name,
PolicyDocument=json.dumps(permissions_policy),
Description='Permissions for My Agent'
)

policy_arn = policy_response['Policy']['Arn']
print(f"✅ Policy created: {policy_arn}")

# Attach policy to role
iam.attach_role_policy(
RoleName=role_name,
PolicyArn=policy_arn
)

print(f"✅ Policy attached to role")
print(f"Role ARN: {role_arn}")

return role_arn

except ClientError as e:
print(e)
if e.response['Error']['Code'] == 'EntityAlreadyExists':
# Role already exists, get its ARN
role = iam.get_role(RoleName=role_name)
role_arn = role['Role']['Arn']
print(f"✅ Role already exists: {role_arn}")
return role_arn
else:
print(f"❌ Error: {e}")
raise
Then we are checking if the Agent is already deployed in order to decide which function to invoke (create or update). For that we had to implement a function which checks if an agent with the same name already exists. We are doing that because Boto3 SDK requires different function invocation for update and create:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def check_agent_exists(client, agent_name):
"""Check if agent runtime already exists."""
try:
# List all agent runtimes and find the one with matching name
response = client.list_agent_runtimes()

for agent in response.get('agentRuntimes', []):
if agent['agentRuntimeName'] == agent_name:
print(f"✅ Agent runtime '{agent_name}' already exists")
print(f" Status: {agent['status']}")
print(f" ARN: {agent['agentRuntimeArn']}")

# Get full agent details using the ID
agent_id = agent['agentRuntimeId']
full_response = client.get_agent_runtime(agentRuntimeId=agent_id)
return True, full_response

# Agent not found
print(f"ℹ️ Agent runtime '{agent_name}' does not exist")
return False, None

except ClientError as e:
print(f"❌ Error checking agent existence: {e}")
raise
In case that this is the first deployment then we are creating the agent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def create_agent_runtime(client, agent_name, role_arn):
"""Create new agent runtime."""
print(f"🚀 Creating new agent runtime: {agent_name}")

try:
response = client.create_agent_runtime(
agentRuntimeName=agent_name,
agentRuntimeArtifact={
'containerConfiguration': {
'containerUri': '<THE_ECR_URL_OF_THE_DOCKER_IMAGE>'
}
},
networkConfiguration={"networkMode": "PUBLIC"},
roleArn=role_arn,
protocolConfiguration={
'serverProtocol': 'HTTP'
}
)

print(f"✅ Agent Runtime created successfully!")
print(f" Agent Runtime ARN: {response['agentRuntimeArn']}")
print(f" Status: {response['status']}")
return response

except ClientError as e:
print(f"❌ Error creating agent runtime: {e}")
raise
Otherwise we are updating the existing one:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def update_agent_runtime(client, existing_agent_info, role_arn):
"""Update existing agent runtime."""
# Extract from ARN as the most reliable method
arn = existing_agent_info['agentRuntimeArn']
agent_runtime_id = arn.split('/')[-1] # Get the last part of the ARN
agent_name = existing_agent_info['agentRuntimeName']

print(f"🔄 Updating agent runtime: {agent_name} (ID: {agent_runtime_id})")

try:
response = client.update_agent_runtime(
agentRuntimeId=agent_runtime_id,
agentRuntimeArtifact={
'containerConfiguration': {
'containerUri': '<THE_ECR_URL_OF_THE_DOCKER_IMAGE>'
}
},
networkConfiguration={"networkMode": "PUBLIC"},
roleArn=role_arn,
protocolConfiguration={
'serverProtocol': 'HTTP'
}
)

print(f"✅ Agent Runtime updated successfully!")
print(f" Agent Runtime ARN: {response['agentRuntimeArn']}")
print(f" Status: {response['status']}")
return response

Testing the Agentic AI Application

After running all scripts above and we confirm that everything is deployed (we can do that from the AWS console for instance), now it is time to check that we built actually works (not only on my machine ;)).
To test our application we built a python script which invokes the agent with a specific prompt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import boto3
import json

# Initialize the Bedrock AgentCore client
agent_core_client = boto3.client('bedrock-agentcore')

# Prepare the payload
payload = json.dumps({"prompt": "What is 11*444?"}).encode()

# Invoke the agent
response = agent_core_client.invoke_agent_runtime(
agentRuntimeArn=<AGENTS_ARN>,
runtimeSessionId=<THE_RUNTIME_SESSION_ID>,
payload=payload,
qualifier='DEFAULT'
)

response_body = response['response'].read()
response_data = json.loads(response_body)
print("Agent Response:", response_data)
We got this code snippet from the AgentCore Runtime console, so when we will execute it we will get the following result. Voila!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Agent Response:
{
'output': {
'message': {
'role': 'assistant',
'content': [{
'text': 'The result of 11×444 is 4,884.'
}]
},
'timestamp': '2026-01-08T09:17:18.403374',
'agent': 'My Agent',
'version': '1.0.0',
'tools_available': 'Python REPL, Shell, HTTP Request, Calculator Tools (add, subtract, multiply, divide)',
'mcp_enabled': True

}
}
So our Agentic AI application has been deployed on AWS and it is ready for use from other systems. Awesome!!

Conclusion

By leveraging Amazon Bedrock AgentCore platform, we could quickly build and deploy an Agentic AI application with minimal infrastructure complexity. The seamless integration between AgentCore Runtime (for Agent deployment), AgentCore Gateway (for secure MCP server connectivity) and containerized MCP server on ECS, creates a robust, scalable architecture that automatically handles scaling challenges.
This approach allows us to focus on building actual agent capabilities rather than managing underlying infrastructure, speeding up the development lifecycle from prototype to production.
And as Wernel Vogels says, Now Go Build!
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article