Introduction
In , we developed the (MCP-) client, capable of talking to our application running on AgentCore Runtime. In . It consists of the agent and Infrastructure as Code subfolders.
Let's first look at the changes that we need to make to the client. AgentCore Runtime also supports the HTTP protocol contract, which we'll use to deploy our MCP client and talk to it. This contract puts some requirements on the client:
Container requirements:
- Host : 0.0.0.0
- Port : 8080 - Standard port for HTTP-based agent communication
- Platform : ARM64 Docker container - Required for compatibility with the AgentCore Runtime environment. I usually borrow t4g small EC2 instance on AWS to build it.
Path requirements:
- /invocations endpoint: POST endpoint for agent interactions
- /ping endpoint: GET endpoint for health checks
You can read more about this topic in the are to implement these path requirements. If we use asynchronous communication, the entry point looks like:
@PostMapping(value = "/invocations", consumes = { "*/*" })
public Flux<String> invocations(@RequestBody String prompt) {
var token = getAuthTokenViaHttpClient();
var client = McpClient.async(getMcpClientTransport(token)).build();
...
var toolCallbacks = concatWithStream(asyncMcpToolCallbackProvider
.getToolCallbacks(), ToolCallbacks.from(new DateTimeTools()));
return this.chatClient.prompt().user(prompt)
.toolCallbacks(toolCallbacks)
.stream().content();
}
For synchronous communication, the entry point looks like:
@PostMapping(value = "/invocations", consumes = { "*/*" })
public String invocations(@RequestBody String prompt) {
var token = getAuthTokenViaHttpClient();
var client = McpClient.async(getMcpClientTransport(token)).build();
...
var toolCallbacks = concatWithStream(asyncMcpToolCallbackProvider
.getToolCallbacks(), ToolCallbacks.from(new DateTimeTools()));
return this.chatClient.prompt().user(prompt)
.toolCallbacks(toolCallbacks)
.call().content();
}
For adding the path to /ping, we have different options. We can either add such a simple method:
@GetMapping("/ping")
public String ping() {
return "{\"status\": \"healthy\"}";
}
Or use :
FROM amazoncorretto:25
COPY target/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Let's build the Docker file and upload it to the .
We don't need to make any other changes on the MCP client itself.
Let's now cover the IaC part with CDK for Java, which I implemented in . For a more detailed explanation, I refer to this article.
First, let's take a look at the creation of the AgentCore Runtime:
Runtime.Builder.create(this, "MCPRuntime-125")
.runtimeName(appName.replace("-", "_")+ "_runtime")
.protocolConfiguration(ProtocolType.HTTP)
.description("AgenCore Runtime with MCP protocol for running conference app")
...
.build();
Here we set some common properties, such as the runtime name, description, and protocol (in our case, HTTP).
Now let's look at the relevant code parts to assign this code artifact to the AgentCore Runtime:
var ecrImageURI= ConventionalDefaults.
getContextVariableValueWithReplacedAccountId(this, "ecrImageURIForConferenceSearchAndApplicationAgent");
var agentRuntimeArtifact =
AgentRuntimeArtifact.fromImageUri(ecrImageURI);
....
Runtime.Builder.create(this, "MCPRuntime-125")
.agentRuntimeArtifact(agentRuntimeArtifact)
...
.build();
First, we get the value of the variable ecrImageURI, which points to the imageURI in the ECR we pushed previously. This is typically done in the class to replace the placeholder with the real value:
static String getContextVariableValueWithReplacedAccountId(Stack stack, String contextVariableName) {
var awsAccountId=(String)stack.getNode().tryGetContext("awsAccountId");
if(awsAccountId == null || awsAccountId.trim().isEmpty()) {
System.out.println("please provide your aws account id as as content to the call, for example: cdk deploy -c awsAccountId=1234567890101");
}
var contextVariableValue= getContextVariableValue(stack, contextVariableName);
return replaceAWSAccountID(contextVariableValue, awsAccountId);
}
static String getContextVariableValue(Stack stack, String contextVariableName) {
return (String)stack.getNode().tryGetContext(contextVariableName);
}
private static String replaceAWSAccountID(String configParam, String awsAccountId) {
return configParam.replace("{AWS_ACCOUNT_ID}", awsAccountId);
}
Then we create AgentRuntimeArtifact from the image URI and set it as AgentCore Runtime agentRuntimeArtifact property.
Now let's cover the next part - defining the IAM execution role. It's very difficult to automate this part as it takes plenty of time. If I find it, I'll provide the IaC part in the future :). I refer you to the article , where I explained this part. In that article, we developed the agent in Python with the Strands Agents framework and deployed it on AgentCore Runtime.
Once we have defined the IAM role, we need to configure it in the :
var roleArnForTheAgentCoreRuntime=ConventionalDefaults
.getContextVariableValueWithReplacedAccountId(this, "roleArnForTheAgentCoreRuntime");
var role=Role.fromRoleArn(this,"roleArnForTheAgentCoreRuntimeRole", roleArnForTheAgentCoreRuntime);
Runtime.Builder.create(this, "MCPRuntime-123")
.runtimeName(appName.replace("-", "_")+ "_runtime")
...
.executionRole(role)
.authorizerConfiguration(RuntimeAuthorizerConfiguration.usingIAM())
.build();
Here, we also use an .
Now we are ready to deploy our MCP client on the AgentCore Runtime. The command to do it is:
cdk deploy -c awsAccountId={YOUR_AWS_ACCOUINT_ID}
Here is how the AgentCore Runtime looks in the console after its creation:
We'll need the Runtime ARN, which we see in the output of this command. Or we can grab it in the service console.
Now we still need to write a client that communicates with our MCP client on the Runtime. I provided such an as we're communicating with the same MCP client, but deployed elsewhere. When we invoke the invokeAgentRuntime method on the bedrockAgentCoreClient by providing the invokeAgentRuntimeRequest and convert the agent response to a string.
Conclusion
In this article, we looked at how to deploy and run our MCP client on AgentCore Runtime. With that, our MCP client now scales nicely within the Runtime.
Of course, you can create a nicer client by providing UI for entering the prompt and providing the agent response as a result. My goal was only to demonstrate how to implement such a client. Now we can change and redeploy our MCP client based on Spring AI on the AgentCore Runtime as often as we want. The client code remains unchanged as long as the Runtime ARN remains unchanged.
Starting from the next article, we'll look at the and give my repositories a star!
Please also check out my website for more technical content and upcoming public speaking activities.
SOCIAL SHARE CARD GENERATOR