<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vadym Kazulkin</title>
    <description>The latest articles on DEV Community by Vadym Kazulkin (@vkazulkin).</description>
    <link>https://dev.to/vkazulkin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F501061%2Ffa23b5ee-9c5f-48bd-b3bb-341a26a773c6.JPG</url>
      <title>DEV Community: Vadym Kazulkin</title>
      <link>https://dev.to/vkazulkin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vkazulkin"/>
    <language>en</language>
    <item>
      <title>Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 3 Develop Agents as web application with Embabel</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 03 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-agents-as-2d94</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-agents-as-2d94</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;part 2&lt;/a&gt;, we developed our first AI agents with the Embabel shell. We learned many concepts of this framework, such as agents, actions, and goals. In this article, we'll slightly adjust our application to convert it into a web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement AI agents as a web application with Embabel
&lt;/h2&gt;

&lt;p&gt;You can look at my GitHub &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/embabel-1.x-conference-app-agent-local" rel="noopener noreferrer"&gt;embabel-1.x-conference-app-agent-local&lt;/a&gt; repository for the example. We'll completely reuse the implementation of both agents, the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/CreateTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;CreateTalksAndApplyForConferencesAgent&lt;/a&gt; and the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/SearchForTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/a&gt; that we implemented in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Those agents are capable of responding to the following prompts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; "Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference." &lt;em&gt;SearchForTalksAndApplyForConferencesAgent&lt;/em&gt; is responsible for responding to this prompt.&lt;/li&gt;
&lt;li&gt;"Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service. Then provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today. Finally, for each conference, apply individually for it with the talk just created." &lt;em&gt;CreateTalksAndApplyForConferencesAgent&lt;/em&gt; is responsible for responding to this prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, let's check that we declared the following dependencies in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.embabel.agent&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;embabel-agent-starter-bedrock&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${embabel-agent.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, make sure not to declare the &lt;em&gt;embabel-agent-starter-shell&lt;/em&gt; dependency, as we don't use Embabel shell in this article.&lt;/p&gt;

&lt;p&gt;All we need to do now is to additionally implement the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/controller/EmbabelAgentController.java" rel="noopener noreferrer"&gt;EmbabelAgentController&lt;/a&gt; controller.&lt;/p&gt;

&lt;p&gt;Let's first implement a generic endpoint that will accept both prompts. Let's first autowire and create some objects coming from Embabel that we'll use for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmbabelAgentController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AgentPlatform&lt;/span&gt; &lt;span class="n"&gt;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AgentInvocation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ProcessOptions&lt;/span&gt; &lt;span class="n"&gt;processOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProcessOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withVerbosity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Verbosity&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withShowPrompts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withShowLlmResponses&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withShowPlanning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withDebug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;


&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;EmbabelAgentController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentPlatform&lt;/span&gt; &lt;span class="n"&gt;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentInvocation&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processOptions&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we autowire the Embabel's &lt;em&gt;AgentPlatform&lt;/em&gt; object and use it to create the &lt;em&gt;AgentInvocation&lt;/em&gt; object. We additionally pass the &lt;em&gt;ProcessOption&lt;/em&gt; object that is configured to show planning, LLM responses, and prompts. We also pass the type of the response - the domain object returned after the agents achieve their goals; in our case, the  &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/domain/Domain.java" rel="noopener noreferrer"&gt;Domain.ConferenceApplications&lt;/a&gt; class:&lt;/p&gt;

&lt;p&gt;Now, let's implement the endpoint itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/generic"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;genericPrompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;    
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we wrap the prompt into Embabel's &lt;em&gt;UserInput&lt;/em&gt; object that we put into the map with all inputs required to run the agent. Then we use the &lt;em&gt;AgentInvocation&lt;/em&gt; object to run the agent with the specified input synchronously. We can also invoke &lt;em&gt;runAsync&lt;/em&gt; to run the agent asynchronously. Finally, we retrieve the result of the agent invocation with the specified type.&lt;/p&gt;

&lt;p&gt;Let's build the application with &lt;code&gt;mvn clean package&lt;/code&gt; and run it locally with &lt;code&gt;mvn spring-boot:run&lt;/code&gt;. We'll send the prompt number 1 to this endpoint. What we'll see is that Embabel picked the wrong agent, &lt;em&gt;CreateTalksAndApplyForConferencesAgent&lt;/em&gt;, to run. Why? As we don't use the Embabel shell, Embabel doesn't analyze the prompt and compute the score to determine what exact agent to run. It currently only looks into the return type of all agents and picks the first one that matches the type defined in the &lt;em&gt;AgentInvocation&lt;/em&gt; object. But both our agents have the same return type &lt;em&gt;Domain.ConferenceApplications&lt;/em&gt;. Their goals, in the end, are the same: to apply for the conferences. With that, for our 2 agents, there is a 50% probability that Embabel will pick the wrong agent to run.&lt;/p&gt;

&lt;p&gt;How can we fix that? For example, we can define the endpoint per agent. Let's do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/applyToConferencesWithExistingTalks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;applyToConferencesWithExistingTalks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeAgent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
       &lt;span class="nc"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AGENT_NAME&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/applyToConferencesWithNewTalks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;applyToConferencesWithNewTalks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeAgent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;CreateTalksAndApplyForConferencesAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AGENT_NAME&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I additionally wrote 2 helper methods, &lt;em&gt;invokeAgent&lt;/em&gt; and &lt;em&gt;getAgentByName&lt;/em&gt;, to simplify the agent search by name and its subsequent invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;invokeAgent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;        
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAgentByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agentProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAgentProcess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;processOptions&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;agentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="nf"&gt;getAgentByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;optionalAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findFirst&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

     &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="n"&gt;optionalAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"agent with the name "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" not found"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;optionalAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain what happens in the &lt;em&gt;invokeAgent&lt;/em&gt; method. We first invoke the &lt;em&gt;getAgentByName&lt;/em&gt; method. It searches in the collection of agents that the agent platform knows for one that has a specified name. All classes annotated with Embabel's &lt;em&gt;Agent&lt;/em&gt; annotation will be registered in the agent platform. Then we use the &lt;em&gt;AgentPlatform&lt;/em&gt; object to create the &lt;em&gt;AgentProcess&lt;/em&gt; and pass the found agent, the already described process options, and inputs to it. Then we run the &lt;em&gt;AgentProcess&lt;/em&gt; and return its last output of type &lt;em&gt;Domain.ConferenceApplications.class&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now we send prompt number 1 (see above) to the &lt;em&gt;applyToConferencesWithExistingTalks&lt;/em&gt; endpoint and prompt number 2 to the &lt;em&gt;applyToConferencesWithNewTalks&lt;/em&gt; endpoint. We'll see something similar as a result, as we saw in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, here I use &lt;a href="https://httpie.io/" rel="noopener noreferrer"&gt;httpie&lt;/a&gt; to send prompt number 1: &lt;br&gt;
&lt;code&gt;http GET http://localhost:8080/applyToConferencesWithExistingTalks?prompt="Please provide me with the list of conferences including their IDs with Java topic happening in 2026 and 2027 with call for papers open today. Also provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference." Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the response of the agent:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxq4w3wz6pde5nonc5twv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxq4w3wz6pde5nonc5twv.png" alt=" " width="799" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having one endpoint per Embabel agent has its pros and cons. The biggest disadvantage is that a service like &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;AgentCore Runtime&lt;/a&gt; exposes only one HTTP POST &lt;em&gt;/invocations&lt;/em&gt; endpoint to send the prompt to the web application. This means that we'll need to think about how to design, implement, and deploy each Embabel agent on a separate AgentCore Runtime. Each agent belonging to the same web application will have a lot of shared logic with other agents. This is not very convenient. The biggest advantage is that you can scale each Embabel agent individually. AgentCore Runtime will do it for you.&lt;/p&gt;

&lt;p&gt;It's worth checking for the upcoming version of Embabel whether there are other options available &lt;a href="https://docs.embabel.com/embabel-agent/guide/1.0.0-SNAPSHOT/#reference.invoking" rel="noopener noreferrer"&gt;to invoke the agent&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we covered how to develop a web application with Embabel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springai</category>
      <category>embabel</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 2 Develop Agents with Embabel shell</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 27 Jul 2026 15:35:30 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-1-introduction-to-1ebl"&gt;part 1&lt;/a&gt;, we addressed some challenges when developing AI agents with Spring AI frameworks only. We start exploring how Embabel is capable of addressing and solving those challenges. Throughout this series, we'll start improving the agent developed in the &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;Provide MCP tools for Conference application via AgentCore Gateway&lt;/a&gt; article using the Embabel framework. First, we'll use the Embabel shell, then the web application. You can look at my GitHub &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/embabel-1.x-conference-app-agent-local" rel="noopener noreferrer"&gt;embabel-1.x-conference-app-agent-local&lt;/a&gt; repository for the example. I updated them to use the recently released Embabel version 1.0.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement AI agents with Embabel shell
&lt;/h2&gt;

&lt;p&gt;In this article, we'll implement 2 AI agents capable of responding to the following prompts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; "Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference."&lt;/li&gt;
&lt;li&gt;"Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service. Then provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today. Finally, for each conference, apply individually for it with the talk just created."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As our sample application also supports only searching for the conference as an attendee, not as a speaker, you can similarly implement those agents.&lt;/p&gt;

&lt;p&gt;Before we start, we need to understand the following &lt;a href="https://github.com/embabel/embabel-agent#key-concepts" rel="noopener noreferrer"&gt;key concepts&lt;/a&gt; of the Embabel framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actions: Steps an agent takes&lt;/li&gt;
&lt;li&gt;Goals: What an agent is trying to achieve&lt;/li&gt;
&lt;li&gt;Conditions: Conditions to assess before executing an action or determining that a goal has been achieved. Conditions are reassessed after each action is executed.&lt;/li&gt;
&lt;li&gt;Domain model: Objects underpinning the flow and informing Actions, Goals and Conditions.&lt;/li&gt;
&lt;li&gt;Plan: A sequence of actions to achieve a goal. Plans are dynamically formulated by the system, not the programmer. The system replans after the completion of each action, allowing it to adapt to new information as well as observe the effects of the previous action. This is effectively an OODA loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's first define the &lt;em&gt;actions&lt;/em&gt; for our agent - steps an agent takes.&lt;br&gt;
We can split our first agent into the following actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request: "Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today". LLM responds to it.&lt;/li&gt;
&lt;li&gt;Request: "Provide me with the list of my talks with this topic in the title".  LLM responds to it.&lt;/li&gt;
&lt;li&gt;Request: "For each conference and talk retrieved, apply individually for the conference". This request means that the LLM uses its responses from the first 2 requests as the request and responds to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And we can split our second agent into the following actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request: "Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service". LLM responds to it.&lt;/li&gt;
&lt;li&gt;Request: "Provide me with the list of my talks with this topic in the title".  LLM responds to it.&lt;/li&gt;
&lt;li&gt;Request: "For each conference and talk, apply individually for the conference". This request means that the LLM uses its responses from the first 2 requests as the request and responds to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this particular case, steps 2 and 3 are mainly the same for our 2 agents. The difference lies in step 1, where we either retrieve an existing talk(s) or create a new one. Your case might be different, so the design of the agents and their actions might also be different.&lt;/p&gt;

&lt;p&gt;First, let's declare some important dependencies in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt; to use the Embabel framework (I use version 1.0.0) and its shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.embabel.agent&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;embabel-agent-starter-bedrock&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${embabel-agent.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.embabel.agent&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;embabel-agent-starter-shell&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${embabel-agent.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, make sure not to declare the &lt;em&gt;spring-boot-starter-web&lt;/em&gt; dependency, as we'll implement a web application in the next article.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/EmbabelConferenceApplication.java" rel="noopener noreferrer"&gt;EmbabelConferenceApplication&lt;/a&gt; is the main entry point of our application. Here we also load Amazon Bedrock models, which we declared in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/resources/models/additional-bedrock.yaml" rel="noopener noreferrer"&gt;additional-bedrock.yaml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdditionalBedrockModels&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="nd"&gt;@Bean&lt;/span&gt;
   &lt;span class="nc"&gt;BedrockModelLoader&lt;/span&gt; &lt;span class="nf"&gt;bedrockModels&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BedrockModelLoader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultResourceLoader&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"classpath:models/additional-bedrock.yaml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/domain/Domain.java" rel="noopener noreferrer"&gt;Domain&lt;/a&gt; class, we declared a domain object (Java record) for each request and response, which corresponds to the agent actions described above. For example, for the action to search for the talks, those domain objects look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Request to LLM to create the talk&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;ConferenceSearchRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;startDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
  &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;endDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;callForPapersOpenOnThistDate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// Response from LLM for the talk creation request&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Conference&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;conferenceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;topics&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;homepage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;startDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;endDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;callForPapersStartDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;callForPapersEndDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;linkToCallforPapers&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's design those 2 agents. We defined the common actions for both of them in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/AbstractConferenceAgent.java" rel="noopener noreferrer"&gt;AbstractConferenceAgent&lt;/a&gt; class. We defined individual agent-specific actions in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/CreateTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;CreateTalksAndApplyForConferencesAgent&lt;/a&gt; and &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/SearchForTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/a&gt; classes, respectively.&lt;/p&gt;

&lt;p&gt;Let's explain the &lt;em&gt;talk search&lt;/em&gt; agent action from the &lt;em&gt;SearchForTalksAndApplyForConferencesAgent&lt;/em&gt; class in more detail. First of all, we annotate each agent with Embabel's &lt;em&gt;Agent&lt;/em&gt; annotation, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Agent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AGENT_NAME&lt;/span&gt; &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"search for the talk(s), search for the conference(s) by the given criteria (all, by the topic, by the date range, and by call for papers being open on some date), and apply for them with the found talks"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractConferenceAgent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
 &lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we give the agent a name and description.&lt;/p&gt;

&lt;p&gt;The first question is how we can break down a big unstructured prompt into the 3 actions that we described above? Let's start with the action to search for the talk first. The answer is: we can ask LLMs to do this job. Even cheap models are already capable of doing this. Here is how this looks for the action: "Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Action&lt;/span&gt;
&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TalkSearchRequest&lt;/span&gt; &lt;span class="nf"&gt;extractTalkSearchRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserInput&lt;/span&gt; &lt;span class="n"&gt;userInput&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OperationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withLlm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LlmOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us.amazon.nova-pro-v1:0"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
Create a talk search request from this user input, extracting the optional parameters like talk title substring, conference start and end date, and whether the call for papers is still open on a certain date. 
Don't include any other information in the request.
Here is the user input:
     %s"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formatted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userInput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContent&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TalkSearchRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain step by step what's happening here. The &lt;em&gt;extractTalkSearchRequest&lt;/em&gt; method is an action, which the Agent executes.  That's why we annotated it with the Embabel &lt;em&gt;Action&lt;/em&gt; annotation. This method also takes 2 parameters, both coming from the Embabel framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;UserInput&lt;/em&gt; object, which represents the unstructured user prompt&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;OperationContext&lt;/em&gt;, which gives us access to the AI functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We first invoke the &lt;em&gt;ai()&lt;/em&gt; method of the &lt;em&gt;context&lt;/em&gt; object and then provide the LLM to do the job. We can either pass the concrete model AI or use other options that are available, like using the default LLM, LLM by role, and others. As a result, we get back the Embabel implementation of the &lt;em&gt;PromptRunnerOperations&lt;/em&gt;. Or, we can declare such LLMs in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;, for example :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;embabel.models.default-llm&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-sonnet-4-6&lt;/span&gt;
&lt;span class="py"&gt;embabel.models.llms.best&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-sonnet-4-6&lt;/span&gt;
&lt;span class="py"&gt;embabel.models.llms.balanced&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us.amazon.nova-pro-v1:0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By invoking the &lt;em&gt;createObject()&lt;/em&gt; method on the &lt;em&gt;PromptRunnerOperations&lt;/em&gt; implementation, we pass the user prompt and instruction to the LLM on how to split this prompt into one subtask or action. It should only contain the information about the talk search criteria. Additionally, we pass the domain object, in this case, the object of type &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/domain/Domain.java" rel="noopener noreferrer"&gt;Domain.TalkSearchRequest&lt;/a&gt;. Embabel will make the best effort to map the LLM response to this domain object, which additionally provides us with type safety. &lt;/p&gt;

&lt;p&gt;The next step is to implement the talk search itself. For this, we define another action in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/SearchForTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Action&lt;/span&gt;
&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Talks&lt;/span&gt; &lt;span class="nf"&gt;talkSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TalkSearchRequest&lt;/span&gt; &lt;span class="n"&gt;talkSearchRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Ai&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;        
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;speaker&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;     
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;promptRunner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;            
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withPromptContributors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;talkSearchRequest&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withToolGroup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMcpToolGroupByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get-talks-by-"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Search for the talks with the given criteria"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Talks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that the object of the &lt;em&gt;Domain.TalkSearchRequest&lt;/em&gt; type, which is the output of the previous action, is now the input of this action. Also, we inject the &lt;em&gt;Ai&lt;/em&gt; object coming from the Embabel framework this time directly.  Let's first ignore the &lt;em&gt;config.speaker&lt;/em&gt; invocation, which we'll explain later. Here we pass the prompt, which contains the properties required to search for the talk. When with &lt;em&gt;createObject()&lt;/em&gt; method invocation, we once again pass the prompt and the domain object, which should match the outcome, in this case, the object of type &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/domain/Domain.java" rel="noopener noreferrer"&gt;Domain.Talks&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To respond to this prompt, the agent needs tools. Please review the articles &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;Develop local MCP client for Conference application &lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;Provide MCP tools for Conference application via AgentCore Gateway&lt;/a&gt; to understand the whole setup. I implemented very similar code to initialize the MCP client connected to the Bedrock AgentCore Gateway in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/service/McpToolService.java" rel="noopener noreferrer"&gt;McpToolService&lt;/a&gt; class. MCP client is then exposed as a Spring bean in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/EmbabelConferenceApplication.java" rel="noopener noreferrer"&gt;EmbabelConferenceApplication&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt; 
&lt;span class="nd"&gt;@Primary&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;McpSyncClient&lt;/span&gt; &lt;span class="nf"&gt;geMcpClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mcpToolService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMcpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By invoking the &lt;em&gt;withToolGroup(this.getMcpToolGroupByName("get-talks-by-"))&lt;/em&gt; method on the &lt;em&gt;PromptRunner&lt;/em&gt; (see above), we achieved another goal that we described in part 1. We not only split the prompt into individual actions, but are now also able to provide exact MCP tool(s) required to execute it.  Embabel provides the &lt;em&gt;ToolGroup&lt;/em&gt; abstraction for this. In this case, we give the LLM only the tools whose names consist of the &lt;em&gt;get-talks-by-&lt;/em&gt; substring. I implemented the MCP tool filtering in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/AbstractConferenceAgent.java" rel="noopener noreferrer"&gt;AbstractConferenceAgent&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;ToolGroup&lt;/span&gt; &lt;span class="nf"&gt;getMcpToolGroupByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt;  &lt;span class="n"&gt;toolGroupDescription&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nc"&gt;ToolGroupDescription&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Companion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
         &lt;span class="s"&gt;"A collection of tools to interact with 
         the MCP conference search service"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
   &lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;noOp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolCallContextMcpMetaConverter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Companion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;noOp&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;McpToolGroup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;toolGroupDescription&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"Vadym"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ToolGroupPermission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INTERNET_ACCESS&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpClient&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;filterMcpTool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;noOp&lt;/span&gt;
      &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;filterMcpTool&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ToolCallback&lt;/span&gt; &lt;span class="n"&gt;toolCallback&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// not passing name means return true -&amp;gt; pass all tools&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;toolCallback&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolDefinition&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downside is that if we were to rename the MCP tools, we'd need to adjust their names here also. The alternative is to use semantic search for the tools. The alternative is to use semantic search for the tools.&lt;/p&gt;

&lt;p&gt;Now let's come back to the &lt;em&gt;config.speaker()&lt;/em&gt; method invocation, which we saw in the &lt;em&gt;talkSearch&lt;/em&gt; action. Embabel gives the possibility to create different roles and attach different LLMs to them. In our case, we have 2 roles: conference attendee and conference speaker. We can define these roles either programmatically or in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;conference.attendee.llm.role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;best&lt;/span&gt;
&lt;span class="py"&gt;conference.attendee.persona.role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Conference attendee&lt;/span&gt;
&lt;span class="py"&gt;conference.attendee.persona.goal&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Conduct a thorough conference search with the information (topics, conference dates) provided so I decide which to attend.&lt;/span&gt;
&lt;span class="py"&gt;conference.attendee.persona.backstory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;As a Conference Attendee Specialist, your mission is to find a suitable conference that matches the given request&lt;/span&gt;


&lt;span class="py"&gt;conference.speaker.llm.role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;best&lt;/span&gt;
&lt;span class="py"&gt;conference.speaker.persona.role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Conference speaker&lt;/span&gt;
&lt;span class="py"&gt;conference.speaker.persona.goal&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Conduct the search for the talks or create the talk with the given criteria with the conference application for those talks&lt;/span&gt;
&lt;span class="py"&gt;conference.speaker.persona.backstory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;As a Conference Speaker Specialist, your mission is to search for the talk or create the talk with the given criteria with the conference application for those talks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also link each role to the LLM model by referencing the previously defined LLM strategy (in our case, by the name &lt;em&gt;best&lt;/em&gt;). See the LLM definition once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;embabel.models.default-llm&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-sonnet-4-6&lt;/span&gt;
&lt;span class="py"&gt;embabel.models.llms.best&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-sonnet-4-6&lt;/span&gt;
&lt;span class="py"&gt;embabel.models.llms.balanced&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us.amazon.nova-pro-v1:0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, the &lt;em&gt;best&lt;/em&gt; LLM strategy will use the &lt;em&gt;us.anthropic.claude-sonnet-4-6&lt;/em&gt; model.&lt;br&gt;
Next, we defined &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/config/ConferenceConfig.java" rel="noopener noreferrer"&gt;ConferenceConfig&lt;/a&gt; to have access to the roles and actors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Validated&lt;/span&gt;
&lt;span class="nd"&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"conference"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;ConferenceConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RoleGoalBackstory&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;attendee&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   
    &lt;span class="nc"&gt;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RoleGoalBackstory&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;conference&lt;/em&gt; prefix, together with &lt;em&gt;Actor&lt;/em&gt; definitions, determines the mapping to the roles. For example, &lt;em&gt;Actor speaker&lt;/em&gt; matched the &lt;em&gt;conference.speaker&lt;/em&gt; role definition in the application.properties.&lt;/p&gt;

&lt;p&gt;Finally, we injected the &lt;em&gt;ConferenceConfig&lt;/em&gt; object (as config) into the constructor of each agent. &lt;/p&gt;

&lt;p&gt;Similarly, we split the initial prompt into other individual steps or actions for searching for conferences. See the &lt;em&gt;extractConferenceSearchRequest&lt;/em&gt; and &lt;em&gt;conferenceSearch&lt;/em&gt; agents in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/AbstractConferenceAgent.java" rel="noopener noreferrer"&gt;AbstractConferenceAgent&lt;/a&gt; class.  Also, the last step (applying for conferences with talks) looks similar. See the &lt;em&gt;applyForConference&lt;/em&gt; action in the same class. There are 2 things to notice here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We don't ask the LLM to split the prompt. This is because the input to this action is the output of 2 actions before: &lt;em&gt;talkSearch&lt;/em&gt; and &lt;em&gt;conferenceSearch&lt;/em&gt;, and 2 domain objects, &lt;em&gt;Domain.Conferences&lt;/em&gt; and &lt;em&gt;Domain.Talks&lt;/em&gt; respectively. Both become the input to the &lt;em&gt;PromptContributors&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;This agent is additionally annotated with the &lt;em&gt;AchievesGoal&lt;/em&gt; annotation to outline that this action execution is the final step that the agent should take in the workflow. See the complete code:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Action&lt;/span&gt;
&lt;span class="nd"&gt;@AchievesGoal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"apply for the conferences with their IDs and talk IDs and provide status of the application"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;applyForConference&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Conferences&lt;/span&gt; &lt;span class="n"&gt;conferences&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Talks&lt;/span&gt; &lt;span class="n"&gt;talks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Ai&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;speaker&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;promptRunner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withPromptContributors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conferences&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;talks&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withToolGroup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMcpToolGroupByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apply-to-conference"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Apply for the conference with the given criteria"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're completely done with the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/SearchForTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/a&gt; agent. &lt;/p&gt;

&lt;p&gt;The implementation of the agent &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/CreateTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;CreateTalksAndApplyForConferencesAgent&lt;/a&gt; looks very similar. The biggest difference is the first step: instead of searching for the existing talks, the talk is created. See the &lt;em&gt;extractTalkCreationRequest&lt;/em&gt; and &lt;em&gt;createTalks&lt;/em&gt; actions.&lt;/p&gt;

&lt;p&gt;Now come the biggest questions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How is Embabel capable of identifying which agent to execute?&lt;/li&gt;
&lt;li&gt;How is Embabel capable of identifying the order of actions to execute for the individual agent? We only defined the last action to finally achieve the goal properly by annotation. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build the application with &lt;code&gt;mvn clean package&lt;/code&gt; and run it locally with &lt;code&gt;mvn spring-boot:run&lt;/code&gt;. We see that Embabel starts the shell, and we can execute the prompt by running: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;x 'Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference.'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's see what happens next: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ld5oyy2lwipllhrapd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ld5oyy2lwipllhrapd8.png" alt=" " width="799" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Embabel identifies 2 agents, takes their description to compute the score, and chooses a concrete agent to execute based on the user input (prompt). I assume Embabel also uses an LLM for it. As a result, it computed: As a result, it computed :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SearchForExistingTalksAndApplyForConferencesAgent: 0,97&lt;/li&gt;
&lt;li&gt;CreateNewTalksAndApplyForConferencesAgent: 0,05&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, the &lt;em&gt;SearchForExistingTalksAndApplyForConferencesAgent&lt;/em&gt; agent is clearly a winner.&lt;/p&gt;

&lt;p&gt;Next, we see that Embabel formulates a plan to achieve the goal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmmd3fkkej5t3t37hz99c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmmd3fkkej5t3t37hz99c.png" alt=" " width="796" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence of agent actions to execute is a correct one :&lt;/p&gt;

&lt;p&gt;Embabel could also come up with the plan to first search for the conferences, then for the talks, and then to apply. Both plans are valid. How does Embabel create such a plan? The system, not the programmer, dynamically formulates plans. The system replans after the completion of each action, allowing it to adapt to new information as well as observe the effects of the previous action. This is effectively an OODA loop. Emabebl analyzes the input and output types of each agent that we defined. This also helps to formulate the plan. &lt;/p&gt;

&lt;p&gt;We see how Embabel executes those individual actions in the identified order. It logs all structured outputs of each action. The final output looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3vwb5zwfdps6vbnkctj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3vwb5zwfdps6vbnkctj.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It found 2 talks and 1 conference to apply those talks that matched the criteria that we defined in the prompt. Finally, Embabel provides the statistics of the LLM used, the number of input (prompt) and output (completion) tokens spent. We also see the total cost of the agent execution. Embabel maintains the LLM pricing database. I don't expect this information to be very accurate, but it provides some indication. We also see the MCP tool names that the LLM used during the agent execution, their response times, and the number of failures.&lt;/p&gt;

&lt;p&gt;If we execute the prompt &lt;code&gt;x 'Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service. Then provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today. Finally, for each conference, apply individually for it with the talk just created.'&lt;/code&gt;, we'll see that Embabel chooses to execute the &lt;em&gt;CreateNewTalksAndApplyForConferencesAgent&lt;/em&gt; agent :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8q4edk7uyuzhu22cbssy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8q4edk7uyuzhu22cbssy.png" alt=" " width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It then formulates the plan and executes the actions to achieve the goal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwheywxj1ywnypnqznm66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwheywxj1ywnypnqznm66.png" alt=" " width="800" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, it provides the same statistics, which we previously explained:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flszjugfl3ytuiubz5otd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flszjugfl3ytuiubz5otd.png" alt=" " width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many more features that Embabel provides, like retries, which we'll explore in later articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we developed our first AI agents with the Embabel shell. We learned many concepts of this framework, such as agents, actions, and goals. In the next article, we'll slightly adjust our application to convert it into a web application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springai</category>
      <category>embabel</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 1 Introduction to the series and to the Embabel framework</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 20 Jul 2026 14:28:13 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-1-introduction-to-1ebl</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-1-introduction-to-1ebl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to the series and to the Embabel framework
&lt;/h2&gt;

&lt;p&gt;In my article series &lt;a href="https://dev.to/vkazulkin/series/37762"&gt;Building AI Agents with Spring AI and Amazon Bedrock AgentCore&lt;/a&gt;, we've already covered how to build AI Agents with Spring AI and Amazon Bedrock AgentCore. You'll need to go through at least its first 4 parts to be able to follow this article series. I'll reuse that sample application for this series.  Why might the abstractions provided by Spring AI frameworks alone not be enough? Let's look at the prompt that we sent to our application in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;Provide MCP tools for Conference application via AgentCore Gateway&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;"Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference."&lt;/p&gt;

&lt;p&gt;The prompt is highly unstructured. We set here at least 3 pairs of requests to the LLM and their responses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request: "Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today". LLM responds to it.&lt;/li&gt;
&lt;li&gt;Request: "Provide me with the list of my talks with this topic in the title".  LLM responds to it.&lt;/li&gt;
&lt;li&gt;Request: "For each conference and talk retrieved, apply individually for the conference". This request means that the LLM uses its responses from the first 2 requests as the request and responds to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The thing is that we, as humans, can think logically, but not every LLM can break this prompt down into such individual pieces. So, it might require a strong and more expensive model to be able to do it. If we can automatically break down the problem into subtasks so the prompt will become structured, the LLM will have better chances at doing its job right. Spring AI has a function &lt;em&gt;CallResponseSpec.entity&lt;/em&gt; method for the synchronous LLM call to at least convert the response &lt;a href="https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html" rel="noopener noreferrer"&gt;to structured output&lt;/a&gt;. But again, this is not enough, as we need to break down the initial prompt into a set of structured subtasks first.&lt;/p&gt;

&lt;p&gt;The next challenge is that in the same article, we exposed 7 MCP tools through the Bedrock AgentCore Gateway. And we gave all of them to the LLM all at once. This is because the LLM can't respond to any request without using the correct tool. And there is always only 1 correct tool out of 7 per request. It not only blows up the content window, but there is also a high chance that the LLM will hallucinate. It may not only break down the prompt correctly, but may also select the wrong tool to respond to the request.&lt;/p&gt;

&lt;p&gt;Wouldn't it be better if we could break down the prompt as described above and give the LLM exactly the correct tool per request? See the parts &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;2&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;4&lt;/a&gt; to see how we defined and exposed those MCP tools. For the first request, it will be the "conference search tool by topic, date, and call for papers open" tool. Then, for the second tool - "get talks by title substring" tool. For the third - "apply to conferences with conference ID and talk ID" tool.&lt;/p&gt;

&lt;p&gt;With Spring AI, we can filter the tools, as we explained in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;. But it's not enough as long as we can't break down the big prompt into individual subtasks, which may require suitable tool(s). If we implement prompt handling in a not structured way, it's not trivial to implement the tool filtering. This is because different prompts may require different tools depending on their content. &lt;/p&gt;

&lt;p&gt;The same challenges have another prompt from the same article: "Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service. Then provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today. Finally, for each conference, apply individually for it with the talk just created."&lt;/p&gt;

&lt;p&gt;The only difference from the previous prompt is that we ask the LLM to create a new talk instead of extracting the existing talk(s). For this, the LLM will need to use the "create new talk" tool.&lt;/p&gt;

&lt;p&gt;Also, our application has 2 different actors. First, these are conference attendees who are interested in searching for and attending the conference. Second, these are the conference speakers who apply to the conference with their talk(s). Maybe those actors should use a different LLM model that suits each role the best.&lt;/p&gt;

&lt;p&gt;Welcome to the &lt;a href="https://github.com/embabel/embabel-agent" rel="noopener noreferrer"&gt;Embabel&lt;/a&gt;. It's a framework for authoring agentic flows on the JVM that seamlessly mixes LLM-prompted interactions with code and domain models. It supports intelligent finding the path towards goals. Embabel builds on the Spring AI framework. &lt;/p&gt;

&lt;p&gt;I give some links to the resources, which will help you to grasp the concepts of this framework quickly. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.embabel.com/embabel-agent/guide/1.0.0/" rel="noopener noreferrer"&gt;Embabel user guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@igor.dayen/embabel-agentic-ai-framework-release-1-0-0-new-features-332f375d0fba" rel="noopener noreferrer"&gt;Embabel Agentic AI Framework Release 1.0.0: New Features&lt;/a&gt; by Igor Dayen&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=_Y-srK-Ad4c" rel="noopener noreferrer"&gt;The AI Generation Is Coming of Age: Enterprise JVM Agents with Embabel &lt;/a&gt; by Rod Johnson&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=G5VDQCZu6t0" rel="noopener noreferrer"&gt;Developing AI Agents in Java with Embabel (Getting Started)&lt;/a&gt; by Dan Vega&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=2mGr7kdstJs" rel="noopener noreferrer"&gt;Embabel Tools &amp;amp; MCP Servers: Optimize Your Java AI Agents&lt;/a&gt; by Dan Vega&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll explore in the next articles that Embabel is capable of addressing and solving those challenges that we described above. Throughout this series, We'll improve the agent developed in the &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;Provide MCP tools for Conference application via AgentCore Gateway&lt;/a&gt; article using the Embabel framework. First, we'll use the Embabel shell, then the web application. You can already look at my GitHub &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/embabel-1.x-conference-app-agent-local" rel="noopener noreferrer"&gt;embabel-1.x-conference-app-agent-local&lt;/a&gt; repository for the example. &lt;/p&gt;

&lt;p&gt;The reasons I decided to create a separate series for using the Embabel framework are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embabel recently became generally available (GA), and the current version is 1.0.0. I expect a lot of new functionality to be added soon. I don't want to mix the Embabel and Spring AI content, even though Embabel builds on Spring AI.&lt;/li&gt;
&lt;li&gt;Embabel currently doesn't support Spring Boot 4.x and Spring AI 2.x version. So, also here I'll need to update my examples when that version support is there.&lt;/li&gt;
&lt;li&gt;It's unclear whether it's possible to integrate &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore" rel="noopener noreferrer"&gt;spring-ai-agentcore&lt;/a&gt; into Embabel. For example, &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-runtime-starter" rel="noopener noreferrer"&gt;spring-ai-agentcore-runtime-starter&lt;/a&gt;, which we demonstrated for Spring AI in the article &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d"&gt;Deploy MCP client on AgentCore Runtime using Spring AI AgentCore Starter&lt;/a&gt;. And especially &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory" rel="noopener noreferrer"&gt;spring-ai-agentcore-memory&lt;/a&gt;, which we demonstrated in the articles &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9"&gt;Use Spring AI AgentCore short-term Memory for MCP client on AgentCore Runtime&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-8-use-spring-ai-agentcore-3457"&gt;Use Spring AI AgentCore long-term Memory for MCP client on AgentCore Runtime&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springai</category>
      <category>embabel</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 9 Configure AgentCore Observability</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 13 Jul 2026 12:03:51 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-9-configure-agentcore-kb5</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-9-configure-agentcore-kb5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d"&gt;part 6&lt;/a&gt;, we introduced our sample application &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt; using Spring AI AgentCore. Later, in parts &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9"&gt;7&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-8-use-spring-ai-agentcore-3457"&gt;8&lt;/a&gt;, we added AgentCore short- and long-term Memory, respectively, using Spring AI AgentCore Memory.&lt;/p&gt;

&lt;p&gt;In this part of the series, we'll explore how to configure &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability.html" rel="noopener noreferrer"&gt;AgentCore Observability&lt;/a&gt;. I've already covered this topic in my other article series when we used the Strands Agents SDK. I refer to my following articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-3-agentcore-observability-f08"&gt;AgentCore Runtime Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-gateway-part-4-agentcore-gateway-observability-2775"&gt;AgentCore Gateway Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-8-agentcore-memory-observability-32pc"&gt;AgentCore Memory Observability&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though the CloudWatch Generative AI Observability service UI now looks different and has more capabilities, CloudWatch Logging and Monitoring configuration looks the same for our application and is provided out of the box. For the CloudWatch metrics, please review the article &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-service-provided.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore generated observability data&lt;/a&gt; for the current state of the exposed metrics. Also, make sure to enable Log delivery for Application and Usage Logs and Tracing for all AgentCore Services involved. In our case, these are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime (2 of them, as we host the MCP server and the Agent there)&lt;/li&gt;
&lt;li&gt;Gateway&lt;/li&gt;
&lt;li&gt;Memory (both short- and long-term). &lt;/li&gt;
&lt;li&gt;Identity (please make sure to do so for the Runtime and Gateway Identity)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjb00pz7fm9220p7ner5s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjb00pz7fm9220p7ner5s.png" alt=" " width="799" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling AgentCore Tracing
&lt;/h2&gt;

&lt;p&gt;If we follow the steps described above and enable tracing for all AgentCore services in use, we'll only see the basic AgentCore metrics, but completely miss Sessions and Traces. The reason for this is that we provided the examples using the Strands Agents SDK. It works well with AgentCore Observability (baked by CloudWatch Generative AI Observability). We only had to add the dependency to &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-demos/blob/main/amazon-agentcore-runtime-to-gateway-demos/bedrock-agentcore-custom-agent/requirements.txt" rel="noopener noreferrer"&gt;aws-opentelemetry-distro&lt;/a&gt; and instrument our code, as shown below in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-demos/blob/main/amazon-agentcore-runtime-to-gateway-demos/bedrock-agentcore-custom-agent/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt;. Strands Agent has all the information on where to send the metrics and traces to the default OTEL provider, AWS CloudWatch. But how does it work for Java applications based on Spring AI and hosted on AgentCore Runtime?&lt;/p&gt;

&lt;p&gt;To view the metrics in CloudWatch Generative AI observability, we need to add the AWS Distro for OpenTelemetry (ADOT) SDK to our agent code. &lt;a href="https://aws-otel.github.io/" rel="noopener noreferrer"&gt;ADOT&lt;/a&gt; is a secure, production-ready, AWS-supported distribution of the OpenTelemetry project. Part of the Cloud Native Computing Foundation, OpenTelemetry provides open source APIs, libraries, and agents to collect distributed traces and metrics for application monitoring. With ADOT, we can instrument our applications just once to send correlated metrics and traces to multiple AWS and Partner monitoring solutions. In our case, we will send the metrics to the CloudWatch GenAI Observability service. &lt;/p&gt;

&lt;p&gt;AWS offers &lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-java-opentel-sdk.html" rel="noopener noreferrer"&gt;AWS Distro for OpenTelemetry Java&lt;/a&gt; with the AWS Distro for OpenTelemetry (ADOT). To get started, see the &lt;a href="https://aws-otel.github.io/docs/getting-started/java-sdk/auto-instr" rel="noopener noreferrer"&gt;AWS Distro for OpenTelemetry Java documentation&lt;/a&gt;. Let's add it to our sample application. To do so, we need to modify the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt; of our sample application. We first have to download the &lt;em&gt;aws-opentelemetry-agent&lt;/em&gt; and then run it as the  Java agent to instrument the code on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest/download/aws-opentelemetry-agent.jar /opt/aws-opentelemetry-agent.jar&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_TOOL_OPTIONS=-javaagent:/opt/aws-opentelemetry-agent.jar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The documentation also says that the second component is required to receive the metrics and traces: the AWS Distro for OpenTelemetry Collector. In all the &lt;a href="https://aws-otel.github.io/docs/getting-started/collector" rel="noopener noreferrer"&gt;examples&lt;/a&gt; AWS provides, the collector is a sidecar application deployed with Docker Compose. Unfortunately, it's not possible to use Docker Compose for the AgentCore Runtime. We only provide the reference to the image in the &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry&lt;/a&gt; (ECR) repository that the AgentCore Runtime pulls and runs for us.&lt;/p&gt;

&lt;p&gt;It took me a while to figure out how to achieve this, and I even created the &lt;a href="https://github.com/awslabs/agentcore-samples/issues/996" rel="noopener noreferrer"&gt;issue&lt;/a&gt; for it. There is a so-called collector-less &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-configure.html" rel="noopener noreferrer"&gt;Observability for the Amazon Bedrock AgentCore resources&lt;/a&gt;. As of now, unfortunately, not all parameters to be configured are described in this article. But I combined this information with the article &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLP-UsingADOT.html" rel="noopener noreferrer"&gt;Exporting collector-less telemetry using AWS Distro for OpenTelemetry (ADOT) SDK&lt;/a&gt; to achieve the goal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest/download/aws-opentelemetry-agent.jar /opt/aws-opentelemetry-agent.jar&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_TOOL_OPTIONS=-javaagent:/opt/aws-opentelemetry-agent.jar \&lt;/span&gt;
AGENT_OBSERVABILITY_ENABLED=true \
OTEL_RESOURCE_ATTRIBUTES=spring_ai_ac_conference_application_runtime,aws.log.group.names=
/aws/bedrock-agentcore/runtimes/spring_ai_ac_conference_application_runtime-a00QWV3i7t \
OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-aws-log-group=/aws/bedrock-agentcore/runtimes/spring_ai_ac_conference_application_runtime-a00QWV3i7t,x-aws-log-stream=runtime-logs,x-aws-metric-namespace=bedrock-agentcore \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf \
OTEL_TRACES_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://xray.us-east-1.amazonaws.com/v1/traces \
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf \
OTEL_LOGS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs.us-east-1.amazonaws.com/v1/logs 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides the already described steps to download the &lt;em&gt;aws-opentelemetry-agent&lt;/em&gt; and run it as the Java agent to instrument the code, we configured the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AGENT_OBSERVABILITY_ENABLED=true  to indicate that we use Agent Observability and would like to view the traces in the CloudWatch Generative AI Observability and not in X-Ray.&lt;/li&gt;
&lt;li&gt;OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, and OTEL_EXPORTER_OTLP_LOGS_PROTOCOL to all be &lt;em&gt;http/protobuf&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and OTEL_EXPORTER_OTLP_LOGS_ENDPOINT as regional endpoints for traces and logs. If you deploy your application in another region other than us-east-1, you need to adjust the URL.&lt;/li&gt;
&lt;li&gt;OTEL_RESOURCE_ATTRIBUTES to be service.name=spring_ai_ac_conference_application_runtime,aws.log.group.names=/aws/bedrock-agentcore/runtimes/spring_ai_ac_conference_application_runtime-a00QWV3i7t. Please adjust &lt;em&gt;service.name&lt;/em&gt; value to how you named the service in AgentCore Runtime. I called it &lt;em&gt;spring_ai_ac_conference_application_runtime&lt;/em&gt;. For the suffix of the &lt;em&gt;aws.log.group.names&lt;/em&gt; use your AgentCore Runtime ID (in my case &lt;em&gt;spring_ai_ac_conference_application_runtime-a00QWV3i7t&lt;/em&gt;). AWS Log Group Name for AgentCore Runtime always follows the pattern: /aws/bedrock-agentcore/runtimes/{RUNTIME_ID}.&lt;/li&gt;
&lt;li&gt;OTEL_EXPORTER_OTLP_LOGS_HEADERS to be x-aws-log-group=/aws/bedrock-agentcore/runtimes/spring_ai_ac_conference_application_runtime-a00QWV3i7t,x-aws-log-stream=runtime-logs,x-aws-metric-namespace=bedrock-agentcore. The same as above: for the suffix of the &lt;em&gt;x-aws-log-group&lt;/em&gt; use your AgentCore Runtime ID again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, after rebuilding and redeploying the application, we can see similar metrics and traces as provided in the articles using Strands Agents SDK above. There are, of course, some differences in the collected metadata. This is because we use the AWS OpenTelemetry Agent distribution for Java and not for Python, as in the articles above. Here are some selected screenshots taken from the &lt;a href="https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#/gen-ai-observability/agent-core/agents" rel="noopener noreferrer"&gt;CloudWatch GenAI Observability: Bedrock AgentCore Observability&lt;/a&gt; for the prompt "Please provide me with the list of conferences, including their IDs, with the Java topic happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference.", which I sent :&lt;/p&gt;

&lt;p&gt;All sessions view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgg5g2l900qst89pj39kf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgg5g2l900qst89pj39kf.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All traces view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmo9qbj2ksnu3djt54ht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmo9qbj2ksnu3djt54ht.png" alt=" " width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All spans view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wpfn4mvkblgq57dhck7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wpfn4mvkblgq57dhck7.png" alt=" " width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Session ID view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71rusfuxak2tq5emdije.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71rusfuxak2tq5emdije.png" alt=" " width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tree view of the Trace ID:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygkhq1bp6ct2sixqofea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygkhq1bp6ct2sixqofea.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oln85fpfw1h2gjlvejo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oln85fpfw1h2gjlvejo.png" alt=" " width="683" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Trajectory view of the Trace ID: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5o0w2lbddxzgn3ohxzwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5o0w2lbddxzgn3ohxzwt.png" alt=" " width="799" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dvbhhwvrktl4aiymwzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dvbhhwvrktl4aiymwzt.png" alt=" " width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Timeline view of the Trace ID: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq51ifxvc9layjwit1lz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq51ifxvc9layjwit1lz.png" alt=" " width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwlabev7kwf7ivcyf1z8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwlabev7kwf7ivcyf1z8.png" alt=" " width="799" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how to enable observability for Amazon Bedrock AgentCore Runtime hosted agents. If you don't host your application on AgentCore, you can still use CloudWatch to &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-get-started.html#enabling-observability-non-runtime-hosted" rel="noopener noreferrer"&gt;enable observability for non-Amazon Bedrock AgentCore-hosted agents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we described how to configure AgentCore Observability in a collector-less way. This involves running the Java &lt;em&gt;aws-opentelemetry-agent&lt;/em&gt; agent to instrument the code and set a bunch of environment variables in the Dockerfile. Please also make sure you have activated logging and tracing for both AgentCore Gateway and Runtime. &lt;/p&gt;

&lt;p&gt;In the next article, we'll explore how to configure the same AgentCore observability with the help of &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-otel-extension" rel="noopener noreferrer"&gt;spring-ai-agentcore-otel-extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my other series on &lt;a href="https://dev.to/vkazulkin/series/41348"&gt;Building AI Agents with Embabel, Spring AI, and Amazon Bedrock AgentCore&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
      <category>observability</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 8 Use Spring AI AgentCore long-term Memory for MCP client on AgentCore Runtime</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:07:04 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-8-use-spring-ai-agentcore-3457</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-8-use-spring-ai-agentcore-3457</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9"&gt;part 7&lt;/a&gt;, we explained how to add and use AgentCore short-term Memory to our application with the help of Spring AI AgentCore Memory. In this article, we'll use &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-long-term.html" rel="noopener noreferrer"&gt;AgentCore long-term Memory&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;We'll once again build on the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt; sample application, which we introduced in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d"&gt;part 6&lt;/a&gt;. Please review part 7 to better understand the base concept of Spring AI AgentCore Memory and how to add it to our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Bedrock AgentCore long-term Memory
&lt;/h2&gt;

&lt;p&gt;I've written the article &lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-7-using-agentcore-long-term-memory-with-strands-agents-sdk-lb2"&gt;Amazon Bedrock AgentCore Runtime - Part 7 Using AgentCore long-term Memory with Strands Agents SDK&lt;/a&gt; about what AgentCore long-term Memory is and how to create one with the Python SDK. Also provided an example of how to use it with &lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands Agent SDK&lt;/a&gt;. I refer to this article to understand the basics.&lt;/p&gt;

&lt;p&gt;To run examples, we've already deployed our &lt;a href="[spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime](https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime)"&gt;sample application&lt;/a&gt; on AgentCore Runtime by executing the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt; stack in part 6. Next, let's create AgentCore long-term Memory with CDK for Java by executing the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/memory/LongTermMemoryStack.java" rel="noopener noreferrer"&gt;LongTermMemoryStack&lt;/a&gt; stack. First, let's look at what is happening there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LongTermMemoryStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

 &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LongTermMemoryStack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Construct&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="nc"&gt;StackProps&lt;/span&gt; &lt;span class="n"&gt;stackProps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stackProps&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"long-term-memory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;memoryName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"long_term_memory_for_conference_application"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Long-Term Memory for Conference Application"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expirationDuration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;memoryStrategies&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MemoryStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingBuiltInSummarization&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                      &lt;span class="nc"&gt;MemoryStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingBuiltInSemantic&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                 

    &lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"LongTermMemoryIdOutput"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMemoryId&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;           
    &lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use Bedrock AgentCore &lt;em&gt;Memory.Builder&lt;/em&gt; to set the memory name, description, expiration duration, and then create the memory. By defining the memory strategies, we outline that we'll create the AgentCore long-term memory. We used the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-configuring-built-in-strategies.html#long-term-semantic-facts-strategy" rel="noopener noreferrer"&gt;semantic&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-configuring-built-in-strategies.html" rel="noopener noreferrer"&gt;summarization&lt;/a&gt; built-in memory strategies. You can also set the currently supported &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-configuring-built-in-strategies.html#long-term-user-preferences-strategy" rel="noopener noreferrer"&gt;user preference&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-configuring-built-in-strategies.html#long-term-session-episodic-strategy" rel="noopener noreferrer"&gt;episodic&lt;/a&gt;  memory strategies instead. The &lt;em&gt;MemoryStrategy&lt;/em&gt; class offers &lt;em&gt;usingBuiltInUserPreference&lt;/em&gt; and &lt;em&gt;usingBuiltInEpisodic&lt;/em&gt; methods for this purpose. You can also configure the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-configuring-custom-strategies.html" rel="noopener noreferrer"&gt;Custom Memory Strategy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can deploy the stack with the command: &lt;code&gt;cdk deploy spring-ai-ac-conference-application-lt-memory-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is how the created long-term memory looks in the AgentCore Memory UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmeuo39e4qxbu1qwybp02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmeuo39e4qxbu1qwybp02.png" alt=" " width="799" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is how the default namespaces look: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqsw4yqi05mtwu5xs74u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqsw4yqi05mtwu5xs74u.png" alt=" " width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're not satisfied with the built-in configuration, for example, default namespaces, you can set your own. Here is an example of how to create a semantic memory strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="nc"&gt;MemoryStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingSemantic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ManagedStrategyProps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;namespaces&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"namespace1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"namespace2"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other memory strategies work the same; just use the corresponding &lt;em&gt;using*&lt;/em&gt; method. But I always start with the built-in memory strategies.&lt;/p&gt;

&lt;p&gt;Also, the Memory ID will be printed out, which we will need to configure in our Spring AI application. We can find the same Memory ID in the service UI above.&lt;/p&gt;

&lt;p&gt;Finally, we need to configure the following IAM permissions to allow our application running on AgentCoreRuntime to access this AgentCore Memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BedrockAgentCoreLongTermMemory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:ListEvents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:CreateEvent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:RetrieveMemoryRecords"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:GetMemory"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:{YOUR_AWS_REGION}:{YOUR_AWS_ACCOUNT_ID}:memory/{YOUR_LONG_TERM_MEMORY_ID}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Bedrock AgentCore long-term Memory in our sample application
&lt;/h2&gt;

&lt;p&gt;To configure Bedrock AgentCore long-term Memory in our sample application, we need to make some changes to it. First, we need to configure some long-term memory-related properties in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;agentcore.memory.memory-id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{YOUR_LONG_TERM_MEMORY_ID}&lt;/span&gt;
&lt;span class="py"&gt;agentcore.memory.long-term.auto-discovery&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first required property is the AgentCore Memory ID we just created. By setting &lt;em&gt;agentcore.memory.long-term.auto-discovery&lt;/em&gt; to true, we use the recommended &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory#option-1-autodiscovery-recommended" rel="noopener noreferrer"&gt;AgentCore long-term Memory autodiscovery option&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Autodiscovery behavior is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries AWS to discover all strategies configured in your memory&lt;/li&gt;
&lt;li&gt;Creates advisors only for supported types: SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC&lt;/li&gt;
&lt;li&gt;Skips CUSTOM strategy types (not supported by autodiscovery)&lt;/li&gt;
&lt;li&gt;Uses the first namespace if a strategy has multiple namespaces&lt;/li&gt;
&lt;li&gt;Uses &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory#defaults-summary" rel="noopener noreferrer"&gt;default topK values&lt;/a&gt; for each strategy type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also override the specific settings for discovered strategies by providing explicit configuration. See the link above to find out how to do this. Another option is to use the &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory#option-2-explicit-configuration" rel="noopener noreferrer"&gt;AgentCore long-term Memory explicit configuration option&lt;/a&gt;, in which we need to specify each strategy manually.&lt;/p&gt;

&lt;p&gt;Next, we need to ensure that we set &lt;em&gt;ChatMemory&lt;/em&gt; to the &lt;em&gt;ChatClient&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt;. I'll provide the generic constructor, capable of dealing with no AgentCore Memory configured or short-term or long-term memory configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SpringAIAgentController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
&lt;span class="nc"&gt;ChatMemory&lt;/span&gt; &lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AgentCoreLongTermMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ltmAdvisors&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us.anthropic.claude-sonnet-4-6"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultAdvisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAllMemoryAdvisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ltmAdvisors&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;          
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Advisor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getAllMemoryAdvisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatMemory&lt;/span&gt; &lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AgentCoreLongTermMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ltmAdvisors&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
   &lt;span class="nc"&gt;Advisor&lt;/span&gt; &lt;span class="n"&gt;chatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageChatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cltmAdvisors&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Advisor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;ltmAdvisors&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allAdvisors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Advisor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
   &lt;span class="n"&gt;allAdvisors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cltmAdvisors&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;allAdvisors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="o"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;allAdvisors&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short-term Spring AI AgentCore Memory implements the Spring AI &lt;a href="https://docs.spring.io/spring-ai/reference/api/chat-memory.html" rel="noopener noreferrer"&gt;ChatMemoryRepository&lt;/a&gt; interface. This doesn't work for long-term Spring AI AgentCore Memory. For this, we inject the list of &lt;em&gt;AgentCoreLongTermMemoryAdvisor&lt;/em&gt;. Then we use the &lt;a href="https://docs.spring.io/spring-ai/reference/api/advisors.html" rel="noopener noreferrer"&gt;Advisors API&lt;/a&gt; to build the complete list of Advisors in the &lt;em&gt;getAllMemoryAdvisors&lt;/em&gt; method. We use &lt;em&gt;MessageChatMemoryAdvisor&lt;/em&gt; with the &lt;em&gt;ChatMemory&lt;/em&gt; to build the short-term memory advisor, and then we add the long-term advisors to the list. We then provide this complete list of memory advisors as an input to the &lt;em&gt;defaultAdvisors&lt;/em&gt; method of the &lt;em&gt;ChatClient.Builder&lt;/em&gt;. Even if we don't configure the AgentCore short-term or long-term Memory (or both), the same code still works without throwing any exceptions. &lt;em&gt;MessageChatMemoryAdvisor&lt;/em&gt;, and &lt;em&gt;List of AgentCoreLongTermMemoryAdvisors&lt;/em&gt; or both will be &lt;em&gt;null&lt;/em&gt; in such a case, which Spring AI treats the same way as not setting any advisors. That's all the changes we need to make to our application to use the AgentCore long-term memory. &lt;/p&gt;

&lt;p&gt;The last step is exactly the same as for the short-term memory: to include a custom &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory#conversation-id-format" rel="noopener noreferrer"&gt;ChatMemory Conversation ID&lt;/a&gt; in the &lt;em&gt;ChatClient&lt;/em&gt;.  According to the &lt;a href="https://docs.spring.io/spring-ai/reference/api/chat-memory.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, &lt;em&gt;ChatMemory.CONVERSATION_ID&lt;/em&gt; parameter is required for all memory advisors. Calls that omit this parameter will throw an IllegalArgumentException at runtime, as there is no default conversation ID. &lt;br&gt;
The Spring AI AgentCore long-term Memory supports flexible conversation ID formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple: user123 → actor: user123, session: default-session&lt;/li&gt;
&lt;li&gt;With Session: user123:session456 → actor: user123, session: session456&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how the code looks for it in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"default-actor-id-12345678:default-session-id-12345678"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@AgentCoreInvocation&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;invoceAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PromptRequest&lt;/span&gt; &lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AgentCoreContext&lt;/span&gt; &lt;span class="n"&gt;agentCoreContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mcpClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;


    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;advisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatMemory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, we defined a static CONVERSATION_ID. However, if you'd like to use individual IDs depending on the actor or user providing the prompt, you can add the login functionality and set the individual user ID. Finally, we set the value of the conversation ID as the parameter of the memory advisor.&lt;/p&gt;

&lt;p&gt;Then we need to rebuild the Docker image of our application and deploy it to the Amazon ECR. After it, we need to configure the correct &lt;em&gt;ecrImageURI&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;. We covered those concepts in parts &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;2&lt;/a&gt; and &lt;a href=""&gt;4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, we need to redeploy the AgentCore Runtime stack with the command: &lt;code&gt;cdk deploy spring-ai-ac-conference-application-agentcore-runtime-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, we can use the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/sdk/InvokeRuntimeAgent.java" rel="noopener noreferrer"&gt;InvokeRuntimeAgent&lt;/a&gt; class to send prompts to our agent running on AgentCore Runtime. We described this in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;part 5&lt;/a&gt; and can reuse these prompts to apply the talks to the conferences. &lt;/p&gt;

&lt;p&gt;But now, similarly to the example of the short-term memory in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9"&gt;part 7&lt;/a&gt;, we can ask such questions as: "You recently applied for some conferences for me. Can you provide me with the details?" The agent will give us a reply, which shows that it provided the answer using the AgentCore long-term Memory:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapb93yw9o8i2f5e7u7s8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapb93yw9o8i2f5e7u7s8.png" alt=" " width="773" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reply is very similar to one provided in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9"&gt;part 7&lt;/a&gt; for the long-term memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explained how to add and use AgentCore long-term Memory to our application with the help of Spring AI AgentCore Memory. In the next article, we'll add &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability.html" rel="noopener noreferrer"&gt;AgentCore Observability&lt;/a&gt; to our application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my other series on &lt;a href="https://dev.to/vkazulkin/series/41348"&gt;Building AI Agents with Embabel, Spring AI, and Amazon Bedrock AgentCore&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 7 Use Spring AI AgentCore short-term Memory for MCP client on AgentCore Runtime</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 29 Jun 2026 14:00:35 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-7-use-spring-ai-agentcore-fm9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d"&gt;part 6&lt;/a&gt;, we used Spring AI AgentCore Starter in our sample application to deploy and run our MCP client on AgentCore Runtime. We used several useful features of the Spring AI AgentCore Starter, like annotation-based auto-configuration, smart health checks, and rate limiting.&lt;/p&gt;

&lt;p&gt;Until now, our agent deployed on AgentCore Runtime was stateless. If we apply for conferences with some talks and then ask the agent in the next conversation about which talks we applied for which conferences, the agent won't be able to answer. In this article, we'll add the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html" rel="noopener noreferrer"&gt;AgentCore Memory&lt;/a&gt; to our application. We'll start with the short-term memory and later add the long-term memory. Then, we'll show how to use &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory" rel="noopener noreferrer"&gt;Spring AI AgentCore Memory&lt;/a&gt;, which provides the Spring AI ChatMemory integration with the Amazon Bedrock AgentCore Memory service.&lt;/p&gt;

&lt;p&gt;We'll build on the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt; sample application, which we introduced in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d"&gt;part 6&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring AI AgentCore Memory
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory" rel="noopener noreferrer"&gt;Spring AI AgentCore Memory&lt;/a&gt; provides &lt;a href="https://docs.spring.io/spring-ai/reference/api/chat-memory.html" rel="noopener noreferrer"&gt;Spring AI ChatMemory&lt;/a&gt; integration with the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Memory&lt;/a&gt; service.&lt;br&gt;
Its features currently include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring AI Integration: Implements ChatMemoryRepository interface
&lt;/li&gt;
&lt;li&gt;Auto-configuration: Zero-configuration setup with Spring Boot&lt;/li&gt;
&lt;li&gt;Short-Term Memory: Conversation history with MessageWindowChatMemory&lt;/li&gt;
&lt;li&gt;Long-Term Memory: 4 consolidation strategies (Semantic, User Preference, Summary, Episodic)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use Spring AI AgentCore Memory in our application, we need to add one more dependency to the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springaicommunity&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-agentcore-memory&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As stated above, in this article, we'll cover short-term memory, leaving the long-term memory to the next article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Bedrock AgentCore short-term Memory
&lt;/h2&gt;

&lt;p&gt;I've written the article &lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-6-using-agentcore-short-term-memory-with-strands-agents-sdk-55d4"&gt;Amazon Bedrock AgentCore Runtime - Part 6 Using AgentCore short-term Memory with Strands Agents SDK&lt;/a&gt; about what AgentCore short-term Memory is and how to create one with the Python SDK. Also provided an example of how to use it with the &lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands Agent SDK&lt;/a&gt;. I refer to this article to understand the basics.&lt;/p&gt;

&lt;p&gt;To run examples, we've already deployed our &lt;a href="[spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime](https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime)"&gt;sample application&lt;/a&gt; on AgentCore Runtime by executing the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt; stack in part 6. Next, let's create AgentCore short-term Memory with CDK for Java by executing the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/memory/ShortTermMemoryStack.java" rel="noopener noreferrer"&gt;ShortTermMemoryStack&lt;/a&gt; stack. First, let's look at what is happening there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortTermMemoryStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

 &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ShortTermMemoryStack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Construct&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="nc"&gt;StackProps&lt;/span&gt; &lt;span class="n"&gt;stackProps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stackProps&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"short-term-memory-1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;memoryName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"short_term_memory_for_conference_application"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Short-Term Memory for Conference Application"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expirationDuration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   

    &lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ShortTermMemoryIdOutput"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMemoryId&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;           
    &lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use Bedrock AgentCore &lt;em&gt;Memory.Builder&lt;/em&gt; to set the memory name, description and expiration duration, and then create the memory. &lt;/p&gt;

&lt;p&gt;You can deploy the stack with the command: &lt;code&gt;cdk deploy spring-ai-ac-conference-application-st-memory-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As we don't set any memory strategy, the short-term memory will be created like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuoxh5g9ibejsygmjv66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuoxh5g9ibejsygmjv66.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, the Memory ID will be printed out, which we will need to configure in our Spring AI application. We can find the same Memory ID in the service UI above.&lt;/p&gt;

&lt;p&gt;Finally, we need to configure the following IAM permissions to allow our application running on AgentCoreRuntime to access this AgentCore Memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BedrockAgentCoreShortTermMemory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:ListEvents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:CreateEvent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:RetrieveMemoryRecords"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:{YOUR_AWS_REGION}:{YOUR_AWS_ACCOUNT_ID}:memory/{YOUR_SHORT_TERM_MEMORY_ID}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Bedrock AgentCore short-term Memory in our sample application
&lt;/h2&gt;

&lt;p&gt;To configure Bedrock AgentCore short-term Memory in our sample application, we need to make some changes to it. First, we need to configure some short-term memory-related properties in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;agentcore.memory.memory-id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{YOUR_SHORT_TERM_MEMORY_ID}&lt;/span&gt;
&lt;span class="py"&gt;agentcore.memory.total-events-limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10 #Max events to retrieve (context window), default 100&lt;/span&gt;
&lt;span class="py"&gt;agentcore.memory.default-session&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default-session-id-12345678 #Default session name, default name is default-session&lt;/span&gt;
&lt;span class="py"&gt;agentcore.memory.page-size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;50  #API pagination size, default value is 50&lt;/span&gt;
&lt;span class="py"&gt;agentcore.memory.ignore-unknown-roles&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false #Handle unknown message roles, default value is false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only required property is the AgentCore Memory ID we just created. All others are optional, with default values described in the &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory#configuration-reference" rel="noopener noreferrer"&gt;Spring AI AgentCore Memory Configuration Reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, we need to ensure that we set &lt;em&gt;ChatMemory&lt;/em&gt; to the &lt;em&gt;ChatClient&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt;. We've already set it up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SpringAIAgentController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ChatMemory&lt;/span&gt; &lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us.anthropic.claude-sonnet-4-6"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="c1"&gt;//short term memory         &lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultAdvisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MessageChatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;          
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short-term Spring AI AgentCore Memory implements the Spring AI &lt;a href="https://docs.spring.io/spring-ai/reference/api/chat-memory.html" rel="noopener noreferrer"&gt;ChatMemoryRepository&lt;/a&gt; interface. That's why, when we provide the memory configuration as above, Spring AI injects the correct implementation of the &lt;em&gt;ChatMemory&lt;/em&gt; into the constructor. Then we use the &lt;a href="https://docs.spring.io/spring-ai/reference/api/advisors.html" rel="noopener noreferrer"&gt;Advisors API&lt;/a&gt; to build &lt;em&gt;MessageChatMemoryAdvisor&lt;/em&gt; with the &lt;em&gt;ChatMemory&lt;/em&gt; and to provide it as an input to the &lt;em&gt;defaultAdvisors&lt;/em&gt; method of the &lt;em&gt;ChatClient.Builder&lt;/em&gt;. Even if we don't configure the AgentCore short-term Memory, the same code still works without throwing any exceptions. &lt;em&gt;MessageChatMemoryAdvisor&lt;/em&gt; will be &lt;em&gt;null&lt;/em&gt;, which Spring AI treats the same way as not setting any advisors. That's all the changes we need to make to our application to use the AgentCore short-term memory. &lt;/p&gt;

&lt;p&gt;In addition, we need to include a custom &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory#conversation-id-format" rel="noopener noreferrer"&gt;ChatMemory Conversation ID&lt;/a&gt; in the &lt;em&gt;ChatClient&lt;/em&gt;.  According to the &lt;a href="https://docs.spring.io/spring-ai/reference/api/chat-memory.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, the &lt;em&gt;ChatMemory.CONVERSATION_ID&lt;/em&gt; parameter is required for all memory advisors. Calls that omit this parameter will throw an IllegalArgumentException at runtime, as there is no default conversation ID. &lt;br&gt;
The Spring AI AgentCore long-term Memory supports flexible conversation ID formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple: user123 → actor: user123, session: default-session&lt;/li&gt;
&lt;li&gt;With Session: user123:session456 → actor: user123, session: session456&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how the code looks for it in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"default-actor-id-12345678:default-session-id-12345678"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@AgentCoreInvocation&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;invoceAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PromptRequest&lt;/span&gt; &lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AgentCoreContext&lt;/span&gt; &lt;span class="n"&gt;agentCoreContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mcpClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;advisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatMemory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, we defined a static CONVERSATION_ID. However, if you'd like to use individual IDs depending on the actor or user providing the prompt, you can add the login functionality and set the individual user ID. Finally, we set the value of the conversation ID as the parameter of the memory advisor.&lt;/p&gt;

&lt;p&gt;Then we need to rebuild the Docker image of our application and deploy it to the Amazon ECR. After it, we need to configure the correct &lt;em&gt;ecrImageURI&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;. We covered those concepts in parts &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;2&lt;/a&gt; and &lt;a href=""&gt;4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, we need to redeploy the AgentCore Runtime stack with the command: &lt;code&gt;cdk deploy spring-ai-ac-conference-application-agentcore-runtime-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, we can use the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/sdk/InvokeRuntimeAgent.java" rel="noopener noreferrer"&gt;InvokeRuntimeAgent&lt;/a&gt; class to send prompts to our agent running on AgentCore Runtime. We described this in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;part 5&lt;/a&gt; and can reuse these prompts to apply the talks to the conferences. &lt;/p&gt;

&lt;p&gt;But now, additionally, we can ask such questions as: "You recently applied for some conferences for me. Can you provide me with the details?" The agent will give us a reply similar to the reply shown below, which shows that it provided the answer using the AgentCore short-term Memory:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhfo2i7f2m96h0ibhbza.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhfo2i7f2m96h0ibhbza.png" alt=" " width="789" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explained how to add and use AgentCore short-term Memory to our application with the help of Spring AI AgentCore Memory. In the next article, we'll use AgentCore long-term Memory instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my other series on &lt;a href="https://dev.to/vkazulkin/series/41348"&gt;Building AI Agents with Embabel, Spring AI, and Amazon Bedrock AgentCore&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 6 Deploy MCP client on AgentCore Runtime using Spring AI AgentCore Starter</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:25:38 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-6-deploy-mcp-client-on-d4d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;part 5&lt;/a&gt;, we looked at how to deploy and run our MCP client on AgentCore Runtime. Starting from this article, we'll look at the &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore" rel="noopener noreferrer"&gt;Spring AI AgentCore&lt;/a&gt; functionality. In this article, we'll use Spring AI AgentCore Starter in our sample application to deploy and run our MCP client on AgentCore Runtime. As Spring AI 2.0 and Spring Boot 4.1 have both already gone GA, I updated our application to use them; see &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;. For this,  I created a new &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt;. It consists of the agent and Infrastructure as Code (IaC) subfolders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differences in the implementation of the MCP client for the Conference application between Spring AI 1.1 and 2.0
&lt;/h2&gt;

&lt;p&gt;I have noticed several major differences in the implementation of the MCP client for the Conference application. We developed it mainly in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, and then adjusted it in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;part 4&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;part 5&lt;/a&gt;. See  &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt; for the full version of the controller.&lt;/p&gt;

&lt;p&gt;The first one is that &lt;em&gt;ChatClient&lt;/em&gt; now requires passing the &lt;em&gt;ChatOptions.Builder&lt;/em&gt; object to the &lt;em&gt;defaultOptions&lt;/em&gt; method instead of the  &lt;em&gt;ChatOptions&lt;/em&gt; object itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us.anthropic.claude-sonnet-4-6"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the biggest difference is that instead of using &lt;em&gt;WebClient&lt;/em&gt; from the &lt;em&gt;Spring Webflux&lt;/em&gt; dependency, Spring AI 2.0 now uses &lt;em&gt;HttpRequest.Builder&lt;/em&gt; from the &lt;em&gt;java.net.http&lt;/em&gt; module to create an MCP HTTP Streamable transport. Also, the implementation of this protocol is now in the  &lt;em&gt;HttpClientStreamableHttpTransport&lt;/em&gt; class, which was adjusted to use the &lt;em&gt;java.net.http&lt;/em&gt; module. In Spring AI 1.*, the class name was &lt;em&gt;WebClientStreamableHttpTransport&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;McpClientTransport&lt;/span&gt; &lt;span class="nf"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

 &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mcpServerConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMCPServerConfig&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
 &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;headerValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

 &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpRequestBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headerValue&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpClientStreamableHttpTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;        
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpRequestBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also requires splitting the MCP endpoint URL into 2 parts: base URL (domain) and endpoint itself (/mcp), which we did in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;amazon.bedrock.agentcore.gateway.base.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://spring-ai-conference-search-agentcore-gateway-xizwpytsgm.gateway.bedrock-agentcore.us-east-1.amazonaws.com&lt;/span&gt;
&lt;span class="py"&gt;amazon.bedrock.agentcore.gateway.endpoint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, Spring AI 2.0 introduced some changes to how to use the MCP tools. This is how the code looked in the previous Spring AI 1.x versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code in Spring AI 2.0 uses the &lt;em&gt;tools&lt;/em&gt; method, capable of accepting the local tools as well as tool callbacks as its parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we need to additionally define the ToolCallingManager Spring bean in the main &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/SpringAIConferenceAppOnAgentCoreRuntimeApplication" rel="noopener noreferrer"&gt;SpringAIConferenceAppOnAgentCoreRuntimeApplication&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingManager&lt;/span&gt; &lt;span class="nf"&gt;toolCallingManager&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else remains the same as described in parts 3-5.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring AI AgentCore SDK
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main" rel="noopener noreferrer"&gt;Spring AI AgentCore SDK&lt;/a&gt; is an open-source library that brings Amazon Bedrock AgentCore capabilities into Spring AI through familiar patterns: annotations, auto-configuration, and composable advisors. We'll start by using Spring AI AgentCore Starter in this article and then cover Spring AI AgentCore Memory in subsequent articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring AI AgentCore Starter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-runtime-starter" rel="noopener noreferrer"&gt;Spring AI AgentCore Starter&lt;/a&gt; is a Spring Boot starter that enables existing Spring Boot applications to conform to the Amazon Bedrock AgentCore Runtime contract with minimal configuration. Its features currently include: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-configuration: Automatically sets up AgentCore endpoints when added as a dependency&lt;/li&gt;
&lt;li&gt;Annotation-based: Simple @AgentCoreInvocation annotation to mark agent methods&lt;/li&gt;
&lt;li&gt;SSE Streaming: Server-Sent Events support with Flux return types&lt;/li&gt;
&lt;li&gt;Smart health checks: Built-in /ping endpoint with Spring Boot Actuator integration&lt;/li&gt;
&lt;li&gt;Async task tracking: Convenient methods for background task tracking&lt;/li&gt;
&lt;li&gt;Rate limiting: Built-in Bucket4j throttling for invocations and ping endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's add the relevant dependencies to &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springaicommunity&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-agentcore-bom&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${spring-ai-agentcore-version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt; 
            &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;pom&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;import&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencyManagement&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springaicommunity&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-agentcore-runtime-starter&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-actuator&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding the &lt;em&gt;spring-ai-agentcore-runtime-starter&lt;/em&gt; dependency, we can now use &lt;em&gt;org.springaicommunity.agentcore.annotation.AgentCoreInvocation&lt;/em&gt; annotation to annotate the method. Exactly this method we'll expose as a &lt;em&gt;/invocation&lt;/em&gt; POST endpoint on the Bedrock AgentCore Runtime. Please note that only one method per application can be annotated with this annotation. Next, we need to do a small adjustment to the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt; to use this annotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@AgentCoreInvocation&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;invoceAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PromptRequest&lt;/span&gt; &lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AgentCoreContext&lt;/span&gt; &lt;span class="n"&gt;agentCoreContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/PromptRequest.java" rel="noopener noreferrer"&gt;PromptRequest&lt;/a&gt; is a Java record representing a prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;PromptRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;){};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI Starter also supports &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-runtime-starter#rate-limiting" rel="noopener noreferrer"&gt;rate limiting&lt;/a&gt;. The starter includes built-in rate limiting using &lt;a href="https://github.com/bucket4j/bucket4j" rel="noopener noreferrer"&gt;Bucket4j&lt;/a&gt; to protect against excessive requests. However, rate limiting is deactivated by default and will be active only if limits are defined in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;agentcore.throttle.invocations-limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20&lt;/span&gt;
&lt;span class="py"&gt;agentcore.throttle.ping-limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;250&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provided rate limit response is an HTTP response code 429:&lt;br&gt;
({"error":"Rate limit exceeded"}). &lt;br&gt;
Rate limits are also applied per client IP address and reset every minute.&lt;/p&gt;

&lt;p&gt;Next, please comment out all application properties starting with &lt;em&gt;agentcore.memory&lt;/em&gt;, as we'll need them in the upcoming article where we'll cover Bedrock AgentCore Memory.&lt;/p&gt;

&lt;p&gt;By adding the dependency to the &lt;em&gt;spring-boot-starter-actuator&lt;/em&gt;, we enabled &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-runtime-starter#health-monitoring" rel="noopener noreferrer"&gt;health monitoring&lt;/a&gt;. We no longer need to implement the intelligent &lt;em&gt;/ping&lt;/em&gt; endpoint ourselves.&lt;/p&gt;

&lt;p&gt;Without Spring Boot Actuator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Returns static "Healthy" status&lt;/li&gt;
&lt;li&gt;Always responds with HTTP 200&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Spring Boot Actuator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrates with Actuator health checks&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maps Actuator status to AgentCore format:  UP → "Healthy" (HTTP 200), DOWN → "Unhealthy" (HTTP 503), Other → "Unknown" (HTTP 503)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tracks status change timestamps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thread-safe concurrent access&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, let's briefly cover the IaC part with CDK for Java, which I implemented in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt; stack. It's completely identical to the IaC that we described in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;part 5&lt;/a&gt;. I wanted to have a separate IaC stack (also including an IAM role and ECR repository) for this Spring AI 2.0 and Spring AI AgentCore application. Also, please comment out the ShortTermMemoryStack and LongTermMemoryStack execution in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-ac-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/CDKApp.java" rel="noopener noreferrer"&gt;CDKApp&lt;/a&gt; class. We'll cover those later.&lt;/p&gt;

&lt;p&gt;You can deploy the stack with the command: &lt;code&gt;cdk deploy spring-ai-ac-conference-application-agentcore-runtime-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, please visit the above-mentioned &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;part 5&lt;/a&gt; for more details on how to deploy this application on AgentCore Runtime and send prompts to it. All the prompts remain valid, as this is the same application, but using Spring AI AgentCore now. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we used Spring AI AgentCore Starter in our sample application to deploy and run our MCP client on AgentCore Runtime. We used several useful features of the Spring AI AgentCore Starter, like annotation-based auto-configuration, smart health checks, and rate limiting.&lt;/p&gt;

&lt;p&gt;Until now, our agent deployed on AgentCore Runtime was stateless. If we apply for the conferences with some talks and then ask the agent in the next conversation about which talks we applied for which conferences, the agent won't be able to answer. In the next article, we'll add the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html" rel="noopener noreferrer"&gt;AgentCore Memory&lt;/a&gt; to our application. We'll start with the short-term memory and later add the long-term memory. Then, we'll show how to use &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory" rel="noopener noreferrer"&gt;Spring AI AgentCore Memory&lt;/a&gt;, which provides the Spring AI ChatMemory integration with the Amazon Bedrock AgentCore Memory service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my other series on &lt;a href="https://dev.to/vkazulkin/series/41348"&gt;Building AI Agents with Embabel, Spring AI, and Amazon Bedrock AgentCore&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and Aurora DSQL - Part 7 Lambda performance optimization approaches</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 15 Jun 2026 14:51:59 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-lambda-4hbj</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-lambda-4hbj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the previous articles of the series about how to develop, run, and optimize Serverless applications on AWS with Lambda using Java 25, API Gateway, and Aurora DSQL database, we used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managed Java 25 runtime&lt;/li&gt;
&lt;li&gt;GraalVM Native Image deployed as Lambda Custom Runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also did Lambda performance (cold and warm starts) measurements with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda functions used 1024 MB of memory&lt;/li&gt;
&lt;li&gt;Java compilation option "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" &lt;/li&gt;
&lt;li&gt;Lambda x86_64 architecture used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we'll introduce some additional Lambda performance (cold and warm starts) optimization approaches to apply to our sample application. You'll need to measure the performance by yourself to figure out whether they will provide the desired Lambda performance improvements.&lt;/p&gt;

&lt;p&gt;Please keep in mind that you can also deploy our sample application on AWS Lambda as a (Docker) Container Image. I didn't cover this approach, but you can look into my article series &lt;a href="https://dev.to/vkazulkin/series/34789"&gt;Lambda function using Docker Container Image&lt;/a&gt; for a step-by-step introduction on how to do it. I used DynamoDB as a database in this example. The cold start will be quite big. Lambda SnapStart isn't available for the Lambda deployment as a Container Image. Instead, you can use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/java-customization.html#aot-cds-caches" rel="noopener noreferrer"&gt;Ahead-of-Time (AOT) and CDS caches&lt;/a&gt; for the Container Image and then measure the Lambda performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda performance optimization approaches
&lt;/h2&gt;

&lt;p&gt;To find a good balance between the cold and warm start times of the Lambda function, you can try out the optimization techniques introduced below. I have not taken any additional measurements with our sample application with Java and GraalVM 25, but have done so using older Java, GraalVM, and dependency versions. &lt;/p&gt;

&lt;p&gt;We can apply the following approaches to the managed Java runtime and GraalVM Native Image. For the managed Java runtime, it includes enabling SnapStart and applying the priming techniques on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try out different Lambda memory settings. We performed all measurements with 1024 MB of memory for the Lambda function. With different memory settings, you might become better at the price-performance trade-off. &lt;/li&gt;
&lt;li&gt;Try out setting Lambda arm64 architecture using AWS Graviton2 processor, which supports SnapStart since July 2024. This can provide a better cost-performance trade-off compared to x86 architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can apply the following approaches primarily only to the managed Java runtime on Lambda. This includes SnapStart being enabled and applying the priming techniques on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try out different Java compilation options for the Lambda function. We performed all measurements until now with the compilation option "-XX:+TieredCompilation -XX:TieredStopAtLevel=1". We can provide other compilation options to the Lambda function using an environment variable called &lt;em&gt;JAVA_TOOL_OPTIONS&lt;/em&gt;. This can have different cold and warm starts trade-offs. For GraalVM Native Image, the choice of Java compilation method doesn't have much impact on the Lambda performance. This is because our application is already compiled natively.&lt;/li&gt;
&lt;li&gt;Further exclude unused dependencies. With that, we can especially reduce the cold start times (also for SnapStart enabled). In the case of GraalVM Native Image, only reachable Java classes, functions, and methods will become a part of the Native Image, so including unused dependencies may not help that much.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can apply the following approach primarily to the managed Java runtime on Lambda with&amp;nbsp;SnapStart enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search for further Lambda SnapStart priming potential in addition to those we introduced in this series. For this, you can use AWS Lambda Profiler Extension for Java. I described it in my article &lt;a href="https://dev.to/aws-heroes/aws-lambda-profiler-extension-for-java-part-2-improving-lambda-performance-with-lambda-snapstart-4p06"&gt;Improving Lambda performance with Lambda SnapStart and priming&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can apply the following approach primarily to the GraalVM Native Image :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try out &lt;a href="https://www.graalvm.org/21.3/reference-manual/native-image/PGO/" rel="noopener noreferrer"&gt;Profile-Guided Optimizations&lt;/a&gt; to see whether you can further improve Lambda performance. The difficulty of trying out this technique is that you'll need to do some additional semi-automated steps to run your application either with the Lambda emulator locally or in an extra environment to obtain the profile of your application, which you'll then need to use to generate the optimized Native Image. You can use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-extensions.html" rel="noopener noreferrer"&gt;Lambda extension&lt;/a&gt; for it, but it still requires a lot of additional work. This is the work AWS did for us in case Lambda SnapStart is enabled. I really appreciate that I don't need to care about generating, encrypting, storing, and restoring the snapshots/profiles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we introduced additional Lambda performance optimization approaches that we can use in our sample application. Try them out on your own to figure out whether they will provide the desired Lambda performance improvements. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36298"&gt;series&lt;/a&gt; where I use a NoSQL serverless &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; database instead of Aurora DSQL to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and DynamoDB - Part 7 Lambda performance optimization approaches</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Tue, 09 Jun 2026 15:04:06 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-7-lambda-4po1</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-7-lambda-4po1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the previous articles of the series about how to develop, run, and optimize Serverless applications on AWS with Lambda using Java 25, API Gateway, and DynamoDB, we used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managed Java 25 runtime&lt;/li&gt;
&lt;li&gt;GraalVM Native Image deployed as Lambda Custom Runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also did Lambda performance (cold and warm starts) measurements with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda functions used 1024 MB of memory&lt;/li&gt;
&lt;li&gt;Java compilation option "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" &lt;/li&gt;
&lt;li&gt;Lambda x86_64 architecture used&lt;/li&gt;
&lt;li&gt;Default Apache HTTP Client (version 4.5) used to connect to the DynamoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we'll introduce some additional Lambda performance (cold and warm starts) optimization approaches to apply to our sample application. You'll need to measure the performance by yourself to figure out whether they will provide the desired Lambda performance improvements.&lt;/p&gt;

&lt;p&gt;Please keep in mind that you can also deploy our sample application on AWS Lambda as a (Docker) Container Image. I didn't cover this approach, but you can look into my article series &lt;a href="https://dev.to/vkazulkin/series/34789"&gt;Lambda function using Docker Container Image&lt;/a&gt; for a step-by-step introduction on how to do it. The cold start will be quite big. Lambda SnapStart isn't available for the Lambda deployment as a Container Image. Instead, you can use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/java-customization.html#aot-cds-caches" rel="noopener noreferrer"&gt;Ahead-of-Time (AOT) and CDS caches&lt;/a&gt; for the Container Image and then measure the Lambda performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda performance optimization approaches
&lt;/h2&gt;

&lt;p&gt;To find a good balance between the cold and warm start times of the Lambda function, you can try out the optimization techniques introduced below. I have not taken any additional measurements with our sample application with Java and GraalVM 25, but have done so using older Java, GraalVM, and dependency versions. I'll provide references to my relevant articles. Measurements that I did back then might already be outdated, so I strongly recommend you to re-measure.&lt;/p&gt;

&lt;p&gt;We can apply the following approaches to the managed Java runtime and GraalVM Native Image. For the managed Java runtime, it includes enabling SnapStart and applying the priming techniques on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try out different Lambda memory settings. We performed all measurements with 1024 MB of memory for the Lambda function. With different memory settings, you might become better at the price-performance trade-off. See my article &lt;a href="https://dev.to/aws-builders/aws-snapstart-part-14-measuring-cold-and-warm-starts-with-java-21-using-different-compilation-options-el4"&gt;Measuring cold and warm starts and deployment time with Java 21 using different Lambda memory settings&lt;/a&gt; for further examples, performance measurements, and conclusions.&lt;/li&gt;
&lt;li&gt; Try out setting Lambda arm64 architecture using the AWS Graviton2 processor, which supports SnapStart since July 2024. This can provide a better cost-performance trade-off compared to x86 architecture. See my article &lt;a href="https://dev.to/aws-builders/aws-lambda-performance-with-java-21-x86-vs-arm64-part-1-initial-measurements-506"&gt;AWS Lambda performance with Java 21: x86 vs arm64 - Initial measurements&lt;/a&gt; for some insights.&lt;/li&gt;
&lt;li&gt;Try out different synchronous HTTP clients to establish an HTTP connection to DynamoDB. We performed all measurements until now with the default synchronous Apache HTTP Client version 4.5. There are other options like UrlConnection and AWS CRT HTTP clients, which provide different performance trade-offs for the cold and warm start. See my article &lt;a href="https://dev.to/aws-builders/aws-snapstart-part-15-measuring-cold-and-warm-starts-with-java-21-using-different-synchronous-http-clients-579o"&gt;Measuring cold and warm starts with Java 21 using different synchronous HTTP clients&lt;/a&gt; for further examples, performance measurements, and conclusions. GraalVM Native Image also supports the AWS CRT HTTP Client, and I did some measurements using a pure Java Lambda function in my article &lt;a href="https://dev.to/aws-heroes/lambda-function-with-graalvm-native-image-part-6-measuring-cold-and-warm-starts-with-graalvm-23-4d3a"&gt;Measuring cold and warm starts with GraalVM 23 and AWS CRT HTTP Client&lt;/a&gt;. Recently, also &lt;a href="https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-apache5.html" rel="noopener noreferrer"&gt;Apache 5.x based HTTP client&lt;/a&gt; has been released, so you can try it out.&lt;/li&gt;
&lt;li&gt;Explore whether an asynchronous HTTP client for DynamoDB is an option for your use case. The default asynchronous HTTP Client is NettyNio. There is another option, the AWS CRT async HTTP client, which provides different performance trade-offs for the cold and warm starts. See my article &lt;a href="https://dev.to/aws-builders/aws-snapstart-part-16-measuring-cold-and-warm-starts-with-java-21-using-different-asynchronous-http-clients-4n2"&gt;Measuring cold and warm starts with Java 21 using different asynchronous HTTP clients&lt;/a&gt; for further examples, performance measurements, and conclusions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can apply the following approaches primarily only to the managed Java runtime on Lambda. This includes SnapStart being enabled and applying the priming techniques on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try out different Java compilation options for the Lambda function. We performed all measurements until now with the compilation option "-XX:+TieredCompilation -XX:TieredStopAtLevel=1". We can provide other compilation options to the Lambda function using an environment variable called &lt;em&gt;JAVA_TOOL_OPTIONS&lt;/em&gt;. This can have different cold and warm starts trade-offs. See my article &lt;a href="https://dev.to/aws-builders/aws-snapstart-part-14-measuring-cold-and-warm-starts-with-java-21-using-different-compilation-options-el4"&gt;Measuring cold and warm starts with Java 21 using different compilation options&lt;/a&gt; for further examples, performance measurements, and conclusions. For GraalVM Native Image, the choice of Java compilation method doesn't have much impact on the Lambda performance. This is because our application is already compiled natively.&lt;/li&gt;
&lt;li&gt;Further exclude unused dependencies. With that, we can especially reduce the cold start times (also for SnapStart enabled); see my article &lt;a href="https://dev.to/aws-builders/aws-snapstart-part-11-measuring-cold-starts-with-java-21-using-different-deployment-artifact-sizes-4g29"&gt;Measuring cold starts with Java 21 using different deployment artifact sizes&lt;/a&gt;. In the case of GraalVM Native Image, only reachable Java classes, functions, and methods will become a part of the Native Image, so including unused dependencies may not help that much.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can apply the following approach primarily to the managed Java runtime on Lambda with&amp;nbsp;SnapStart enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search for further Lambda SnapStart priming potential in addition to those we introduced in this series. For this, you can use AWS Lambda Profiler Extension for Java. I described it in my article &lt;a href="https://dev.to/aws-heroes/aws-lambda-profiler-extension-for-java-part-2-improving-lambda-performance-with-lambda-snapstart-4p06"&gt;Improving Lambda performance with Lambda SnapStart and priming&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can apply the following approach primarily to the GraalVM Native Image :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try out &lt;a href="https://www.graalvm.org/21.3/reference-manual/native-image/PGO/" rel="noopener noreferrer"&gt;Profile-Guided Optimizations&lt;/a&gt; to see whether you can further improve Lambda performance. The difficulty of trying out this technique is that you'll need to do some additional semi-automated steps to run your application either with the Lambda emulator locally or in an extra environment to obtain the profile of your application, which you'll then need to use to generate the optimized Native Image. You can use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-extensions.html" rel="noopener noreferrer"&gt;Lambda extension&lt;/a&gt; for it, but it still requires a lot of additional work. This is the work AWS did for us in case Lambda SnapStart is enabled. I really appreciate that I don't need to care about generating, encrypting, storing, and restoring the snapshots/profiles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we introduced additional Lambda performance optimization approaches that we can use in our sample application. Try them out on your own to figure out whether they will provide the desired Lambda performance improvements. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36919"&gt;series&lt;/a&gt; where I use a relational serverless &lt;a href="https://aws.amazon.com/rds/aurora/dsql/" rel="noopener noreferrer"&gt;Amazon Aurora DSQL&lt;/a&gt; database and additionally the &lt;a href="https://hibernate.org/" rel="noopener noreferrer"&gt;Hibernate ORM framework&lt;/a&gt; instead of DynamoDB to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>AWS Lambda Managed Instances with Java 25 and AWS SAM – Part 7 Implement scheduled scaling</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 01 Jun 2026 15:36:09 +0000</pubDate>
      <link>https://dev.to/aws-heroes/aws-lambda-managed-instances-with-java-25-and-aws-sam-part-7-implement-scheduled-scaling-4df9</link>
      <guid>https://dev.to/aws-heroes/aws-lambda-managed-instances-with-java-25-and-aws-sam-part-7-implement-scheduled-scaling-4df9</guid>
      <description>&lt;h2&gt;
  
  
  Implement scheduled scaling
&lt;/h2&gt;

&lt;p&gt;Recently, &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/05/amazon-eventbridge-sdk-integrations/" rel="noopener noreferrer"&gt;Amazon EventBridge Scheduler added 619 new SDK API actions&lt;/a&gt;. &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-scaling.html#lambda-managed-instances-scheduled-scaling" rel="noopener noreferrer"&gt;One of these actions&lt;/a&gt; adjusts the Lambda function's minimum and maximum execution environments for Lambda Managed Instances (LMI) on a recurring or one-time schedule. This is useful for predictable traffic patterns, such as scaling up before peak hours and scaling down during off-peak hours. We can use CloudFormation or the latest versions of AWS CDK or AWS CLI to perform this action. In our example, we'll use AWS CLI to adjust the Lambda Function Scaling Configuration, for which we'll use the &lt;em&gt;PutFunctionScalingConfig API&lt;/em&gt; as a universal target. We'll use the Lambda function with the name &lt;em&gt;GetProductByIdJava25WithLMI&lt;/em&gt;, which we introduced in &lt;a href="https://dev.to/aws-heroes/aws-lambda-managed-instances-with-java-25-and-aws-sam-part-1-introduction-and-sample-application-1eb7"&gt;part 1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First of all, let's create an SQS dead-letter queue, which we'll use for our action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sqs create-queue &lt;span class="nt"&gt;--queue-name&lt;/span&gt; scheduler-dlq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's create an EventBridge Scheduler IAM execution role with the name &lt;em&gt;scale-lambda-managed-instances-eventbridge-scheduler-role&lt;/em&gt;. This role grants permission to call the &lt;em&gt;lambda:PutFunctionScalingConfig&lt;/em&gt; and send the message to the dead-letter queue on our target function.&lt;/p&gt;

&lt;p&gt;This is how the trusted policy looks for the IAM role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scheduler.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is how the IAM policy looks (use the ARNs of the Lambda function and SQS dead-letter queue here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"lambda:PutFunctionScalingConfig"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:{aws_region}:{aws_account_id}:function:GetProductByIdJava25WithLMI:$LATEST.PUBLISHED"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"sqs:SendMessage"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sqs:{aws_region}:{aws_account_id}:scheduler-dlq"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace the values of &lt;em&gt;{aws_region}&lt;/em&gt; and &lt;em&gt;{aws_account_id}&lt;/em&gt; with your own values, and adjust the Lambda function and SQS Queue names if needed. &lt;/p&gt;

&lt;p&gt;Now, let's create the scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws scheduler create-schedule &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"ScaleLambdaManagedInstances"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--schedule-expression&lt;/span&gt; &lt;span class="s2"&gt;"at(2026-05-18T08:10:00)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--flexible-time-window&lt;/span&gt; &lt;span class="s1"&gt;'{"Mode": "OFF"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="s1"&gt;'{
     {"DeadLetterConfig": {"Arn": "arn:aws:sqs:{aws_region}:{aws_account_id}:scheduler-dlq"},
    "Arn": "arn:aws:scheduler:::aws-sdk:lambda:PutFunctionScalingConfig",
    "RoleArn": "arn:aws:iam::{aws_account_id}:role/scale-lambda-managed-instances-eventbridge-scheduler-role",
    "Input": "{\"FunctionName\": \"GetProductByIdJava25WithLMI\", \"Qualifier\": \"$LATEST.PUBLISHED\", \"FunctionScalingConfig\": {\"MinExecutionEnvironments\": 5, \"MaxExecutionEnvironments\": 10}}"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain what happens here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we create the schedule with the name &lt;em&gt;ScaleLambdaManagedInstances&lt;/em&gt; using a one-time schedule (executes at 08:10 on May 18, 2026). You can use other &lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html" rel="noopener noreferrer"&gt;Schedule types in EventBridge Scheduler&lt;/a&gt;, like cron-based expressions.&lt;/li&gt;
&lt;li&gt;Then, we target the PutFunctionScalingConfig Scheduler API as a universal target. &lt;/li&gt;
&lt;li&gt;Next, we specify the SQS dead-letter queue ARN created above&lt;/li&gt;
&lt;li&gt;Then, we specify the IAM execution Role ARN created above&lt;/li&gt;
&lt;li&gt;Next, we specify the new MinExecutionEnvironments and MaxExecutionEnvironments values in the Input payload&lt;/li&gt;
&lt;li&gt;Finally, as the scheduler input, we specify the name of the Lambda function and its qualifier (usually &lt;em&gt;$LATEST.PUBLISHED&lt;/em&gt;), for which we'd like to change the Function Scaling Configuration. In our case, we set &lt;em&gt;MinExecutionEnvironments&lt;/em&gt; to 5 and &lt;em&gt;MaxExecutionEnvironments&lt;/em&gt; to 10.&lt;/li&gt;
&lt;li&gt;We can also optionally set the retry policy and encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creating the schedule, we'll see something similar in the &lt;a href="https://us-east-1.console.aws.amazon.com/scheduler/home?region=us-east-1#schedules" rel="noopener noreferrer"&gt;Amazon EventBridge Scheduler Service&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6f4txd2cnukcowm5uy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6f4txd2cnukcowm5uy1.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43ujj0wnaw07l7b9m0p8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43ujj0wnaw07l7b9m0p8.png" alt=" " width="772" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m3ca0w0gx6g5838yx2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m3ca0w0gx6g5838yx2m.png" alt=" " width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the scheduler has run, we can verify that the Lambda function Scaling Configuration has changed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1akscybp4lcegyf73a46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1akscybp4lcegyf73a46.png" alt=" " width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also very important to configure the dead-letter queue. First, I didn't do it and configured the Lambda function Resource ARN in the IAM policy like &lt;em&gt;arn:aws:lambda:{aws_region}:{aws_account_id}:function:GetProductByIdJava25WithLMI&lt;/em&gt;. I observed that the scheduler hasn't been invoked and saw the errors in Amazon CloudWatch.&lt;br&gt;
By configuring the dead-letter queue, I saw the exact error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR_CODE: AccessDeniedException
ERROR_MESSAGE :

User: arn:aws:sts::{aws_account_id}:assumed-role/scale-lambda-managed-instances-eventbridge-scheduler-role/f375e2c757da339a8d593587ce800265 
is not authorized to perform: lambda:PutFunctionScalingConfig on resource: 
arn:aws:lambda:{aws_region}:{aws_account_id}:function:GetProductByIdJava25WithLMI:$LATEST.PUBLISHED 
because no identity-based policy allows the lambda:PutFunctionScalingConfig action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, it was clear to me that I needed to append &lt;em&gt;$LATEST.PUBLISHED&lt;/em&gt; to the ARN of the Lambda function. Please also use &lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/troubleshooting.html" rel="noopener noreferrer"&gt;Troubleshooting Amazon EventBridge Scheduler&lt;/a&gt;, in case you experience some issues.&lt;/p&gt;

&lt;p&gt;Here, we scaled up the capacity at the given time, and in the same way we can scale it down. Things to pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For workloads with predictable peaks, create multiple schedules to match your traffic pattern: one to scale up your function before peak hours, and another to scale down after peak hours. Each schedule follows the same pattern with updated MinExecutionEnvironments and MaxExecutionEnvironments values.&lt;/li&gt;
&lt;li&gt;Scheduled scaling adjusts the provisioned floor and ceiling of execution environments, but actual scaling between min and max still responds to CPU utilization and concurrency saturation.&lt;/li&gt;
&lt;li&gt;If your traffic more than doubles within 5 minutes of a scheduled scale-up, you might still experience throttling as capacity is provisioned.&lt;/li&gt;
&lt;li&gt;When scaling to zero to deactivate a function, remember that reactivation requires an explicit PutFunctionScalingConfig call with non-zero values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>scheduledscaling</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 5 Deploy MCP client for Conference application on AgentCore Runtime</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Tue, 26 May 2026 14:40:49 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we explained how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. In this article, we'll develop the (MCP-) client, capable of talking to our application running on AgentCore Runtime. Later, in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, we developed the (MCP-) client, capable of talking to our application running on AgentCore Runtime. In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;part 4&lt;/a&gt;, we looked at how to provide the MCP Tools for the Conference application via AgentCore Gateway in a centralized way.&lt;/p&gt;

&lt;p&gt;As we saw in previous articles, the local MCP client for the Conference application, to talk to AgentCore Runtime or Gateway, became quite big. If we have many customers using such a client, changing and operating it can become quite challenging. That's why, in this article, we look at how to deploy and run our MCP client on AgentCore Runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update from July, 2026&lt;/strong&gt;. After I published this article, Spring AI 2.0 became generally available. The changes that I need to make for that are the same that I described in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;part 4&lt;/a&gt;. Please also check out my &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-2.0-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt; repository with the updated sample application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement the MCP client for the Conference application to be deployable on AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;We'll reuse the MCP client based on Spring AI that we implemented in parts 3 and 4. But as we need to make some small changes to deploy it on AgentCore Runtime, I created a new &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt;. It consists of the agent and Infrastructure as Code subfolders.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;Container requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host : 0.0.0.0&lt;/li&gt;
&lt;li&gt;Port : 8080 - Standard port for HTTP-based agent communication &lt;/li&gt;
&lt;li&gt;Platform : ARM64 Docker container - Required for compatibility with the AgentCore Runtime environment. I usually borrow t4g small EC2 instance on AWS to build it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Path requirements: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/invocations endpoint: POST endpoint for agent interactions&lt;/li&gt;
&lt;li&gt;/ping endpoint: GET endpoint for health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read more about this topic in the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-http-protocol-contract.html" rel="noopener noreferrer"&gt;HTTP protocol contract&lt;/a&gt; article.&lt;/p&gt;

&lt;p&gt;The only changes we need to make to our &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;REST Controller &lt;/a&gt; are to implement these path requirements. If we use asynchronous communication, the entry point looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/invocations"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"*/*"&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;invocations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolCallbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;concatWithStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For synchronous communication, the entry point looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/invocations"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"*/*"&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;invocations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolCallbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;concatWithStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For adding the path to &lt;em&gt;/ping&lt;/em&gt;, we have different options. We can either add such a simple method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ping"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"{\"status\": \"healthy\"}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use &lt;a href="https://spring.io/guides/gs/actuator-service" rel="noopener noreferrer"&gt;Spring Boot Actuator service&lt;/a&gt; and add some properties to the application.properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;management.endpoints.web.exposure.include&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;health&lt;/span&gt;
&lt;span class="py"&gt;management.endpoints.web.base-path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;
&lt;span class="py"&gt;management.endpoints.web.path-mapping.health&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we need to deploy our MCP client as an ARM64 Docker container, I also added a simple &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/agent/Dockerfile" rel="noopener noreferrer"&gt;Docker file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; amazoncorretto:25&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime-0.0.1-SNAPSHOT.jar app.jar&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java","-jar","/app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's build the Docker file and upload it to the &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build the application&lt;/span&gt;
mvn clean package 

&lt;span class="c"&gt;# build the Docker image&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1 

&lt;span class="c"&gt;# Login to ECR&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com  

&lt;span class="c"&gt;# Create ECR repository (if it doesn't exist)&lt;/span&gt;
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; spring-ai-conference-app-agent-bedrock-agentcore-runtime &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;  

&lt;span class="c"&gt;# Tag the Docker image&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1 &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1

&lt;span class="c"&gt;# Push the Docker Image to the ECR repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace AWS {account_id} and {region} with our own values. Also, your version may not be &lt;em&gt;v1&lt;/em&gt; but a different one.&lt;/p&gt;

&lt;p&gt;We can also build the Docker image by using Buildpack support built into Spring instead of a Dockerfile. Just use the Maven task &lt;a href="https://docs.spring.io/spring-boot/maven-plugin/build-image.html" rel="noopener noreferrer"&gt;spring-boot:build-image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We don't need to make any other changes on the MCP client itself. &lt;/p&gt;

&lt;p&gt;Let's now cover the IaC part with CDK for Java, which I implemented in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt; stack. We've already covered many steps in creating the CDK App and Stack, and even the AgentCore Runtime with the MCP protocol in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;. For a more detailed explanation, I refer to this article.&lt;/p&gt;

&lt;p&gt;First, let's take a look at the creation of the AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-125"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;protocolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProtocolType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgenCore Runtime with MCP protocol for running conference app"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we set some common properties, such as the runtime name, description, and protocol (in our case, HTTP).&lt;/p&gt;

&lt;p&gt;Now let's look at the relevant code parts to assign this code artifact to the AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ecrImageURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="nf"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ecrImageURIForConferenceSearchAndApplicationAgent"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;            

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;    
    &lt;span class="nc"&gt;AgentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromImageUri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ecrImageURI&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="o"&gt;....&lt;/span&gt;

&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-125"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we get the value of the variable &lt;em&gt;ecrImageURI&lt;/em&gt;, which points to the imageURI in the ECR we pushed previously.  This is typically done in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="err"&gt;ecrImageUR&lt;/span&gt;&lt;span class="s2"&gt;": "&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;.dkr.ecr.us-east&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="err"&gt;.amazonaws.com/spring-ai-conference-app-agent-bedrock-agentcore-runtime:v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s2"&gt;"
 }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please adjust the value so that it matches your imageURI. We use the placeholder {AWS_ACCOUNT_ID} there. The reason for it is that I don't want to expose the AWS account ID publicly. That's why I wrote the following utility method &lt;em&gt;getContextVariableValueWithReplacedAccountId&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/ConventionalDefaults.java" rel="noopener noreferrer"&gt;ConventionalDefaults&lt;/a&gt; class to replace the placeholder with the real value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"awsAccountId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;awsAccountId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"please provide your aws account id as as content to the call, for example: cdk deploy -c awsAccountId=1234567890101"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contextVariableValue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextVariableValue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;configParam&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;configParam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{AWS_ACCOUNT_ID}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we create &lt;em&gt;AgentRuntimeArtifact&lt;/em&gt; from the image URI and set it as AgentCore Runtime &lt;em&gt;agentRuntimeArtifact&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html" rel="noopener noreferrer"&gt;IAM Permissions for AgentCore Runtime&lt;/a&gt; for more information. You can also read my article &lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-2-deploy-the-agent-with-the-agentcore-runtime-starter-3706"&gt;Amazon Bedrock AgentCore Runtime - Part 2 Using Bedrock AgentCore Runtime Starter Toolkit with Strands Agents SDK&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;Once we have defined the IAM role, we need to configure it in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"roleArnForTheAgentCoreRuntime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{AWS_ACCOUNT_ID}:role/service-role/spring-ai-conference-search-application-agentcore-runtime-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the placeholder for the AWS account ID as explained above.  Here is the relevant code to grab the value of the &lt;em&gt;roleArnForTheAgentCoreRuntime&lt;/em&gt; variable and set it to the execution role of the Runtime from the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;roleArnForTheAgentCoreRuntime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"roleArnForTheAgentCoreRuntime"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRoleArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"roleArnForTheAgentCoreRuntimeRole"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roleArnForTheAgentCoreRuntime&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executionRole&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeAuthorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingIAM&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we also use an &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-oauth.html" rel="noopener noreferrer"&gt;IAM authorizer&lt;/a&gt; for the inbound AgentCore Runtime authentication. This is the default authentication and authorization mechanism that works automatically without additional configuration. You can also use JSON Web Tokens (JWT) as we showed in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we are ready to deploy our MCP client on the AgentCore Runtime.  The command to do it is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk deploy -c awsAccountId={YOUR_AWS_ACCOUINT_ID}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is how the AgentCore Runtime looks in the console after its creation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxoi9zh0ojkkdkhacs2v3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxoi9zh0ojkkdkhacs2v3.png" alt=" " width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jf34i7p9o95skgqo67r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jf34i7p9o95skgqo67r.png" alt=" " width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll need the Runtime ARN, which we see in the output of this command.  Or we can grab it in the service console.  &lt;/p&gt;

&lt;p&gt;Now we still need to write a client that communicates with our MCP client on the Runtime. I provided such an &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/sdk/InvokeRuntimeAgent.java" rel="noopener noreferrer"&gt;InvokeRuntimeAgent&lt;/a&gt; client written in Java, but you can use any programming language for which AWS provides a (bedrockagentcore) SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;AGENT_RUNTIME_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"arn:aws:bedrock-agentcore:us-east-1:{AWS_ACCOUNT_ID}:runtime/spring_ai_conference_search_application_runtime-143wvBghklZ"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

 &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
   &lt;span class="s"&gt;"{\"prompt\":\"Please provide me with the list of conferences, including their IDs, 
with the Java topic happening in 2027, with the call for papers open today. 
Also, provide me with the list of my talks with this topic in the title. 
Finally, for each conference and talk retrieved, apply individually for the conference.\"}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ApacheHttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectionTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socketTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bedrockAgentCoreClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;US_EAST_1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invokeAgentRuntimeRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InvokeAgentRuntimeRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;                 
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AGENT_RUNTIME_ARN&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;                               
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEFAULT"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SdkBytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromUtf8String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responseStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrockAgentCoreClient&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeAgentRuntime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invokeAgentRuntimeRequest&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAllBytes&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go step-by-step through it. First of all, we define RUNTIME_ARN, which we deployed in the step before. Please still use the &lt;em&gt;{AWS_ACCOUNT_ID}&lt;/em&gt; placeholder, which will be dynamically replaced with your AWS Account ID. When we create &lt;em&gt;BedrockAgentCoreClient&lt;/em&gt;. We also explicitly set the Apache HTTP client with the extended connection and socket timeouts. Default 30-second timeouts maybe to short for communication with the Runtime. Then we create &lt;em&gt;InvokeAgentRuntimeRequest&lt;/em&gt; and set the agent Runtime ARN, qualifier (always DEFAULT), content type, and payload. The payload is our prompt. You can see the examples of the prompts in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;part 4&lt;/a&gt; as we're communicating with the same MCP client, but deployed elsewhere. When we invoke the &lt;em&gt;invokeAgentRuntime&lt;/em&gt; method on the &lt;em&gt;bedrockAgentCoreClient&lt;/em&gt; by providing the &lt;em&gt;invokeAgentRuntimeRequest&lt;/em&gt; and convert the agent response to a string. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Starting from the next article, we'll look at the &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore" rel="noopener noreferrer"&gt;Spring AI AgentCore&lt;/a&gt; functionality. Spring AI AgentCore SDK is an open-source library that brings Amazon Bedrock AgentCore capabilities into Spring AI through familiar patterns: annotations, auto-configuration, and composable advisors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my other series on &lt;a href="https://dev.to/vkazulkin/series/41348"&gt;Building AI Agents with Embabel, Spring AI, and Amazon Bedrock AgentCore&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 4 Provide MCP tools for Conference application via AgentCore Gateway</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 18 May 2026 14:09:12 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we explained how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. In this article, we'll develop the (MCP-) client, capable of talking to our application running on AgentCore Runtime. Later, in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, we developed the (MCP-) client, capable of talking to our application running on AgentCore Runtime. In this article, we'll look at another alternative to AgentCore Runtime to host MCP servers on AgentCore Gateway. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update from June, 2026&lt;/strong&gt;. After I published this article, Spring AI 2.0 became generally available. At the bottom of this article, I'll provide the required changes to make this example work with this version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provide the MCP Tools for the Conference application via AgentCore Gateway
&lt;/h2&gt;

&lt;p&gt;Let's imagine a hypothetical situation: we not only want to search for the conferences, but also create, search, and apply for the talks for them. With this, our conference application now supports not only the attendee role but also the speaker role. This is the reason why I added functionality to support conference search by the open call for papers criteria, see part 2. This is required for conference speakers to determine whether it's still possible to apply for the conference with their talks. &lt;/p&gt;

&lt;p&gt;When searching for conferences, we didn't have a public API, which is why we created MCP. On the other hand, for creating, searching, and applying the talks for the conferences, we indeed have a public API. Let's assume this API is hosted on the Amazon API Gateway. But it could also be any external application that exposes an OpenAPI specification. How to implement such a use case? Of course, we can use &lt;a href="https://docs.aws.amazon.com/bedrockagentcore/latest/devguide/gateway.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway&lt;/a&gt; to securely connect our API to the AgentCore Gateway. The AgentCore Gateway can expose API functionality as MCP tools. But with this, we'll need to authenticate and hold the connection to multiple sources: AgentCore Runtime and Gateway. Without a centralized approach, customers face significant challenges: discovering and sharing tools across organizations becomes fragmented, managing authentication across multiple MCP servers grows increasingly complex, and maintaining separate gateway instances for each server quickly becomes unmanageable. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/de/blogs/machine-learning/transform-your-mcp-architecture-unite-mcp-servers-through-agentcore-gateway/" rel="noopener noreferrer"&gt;The centralized approach&lt;/a&gt;, which exposes all the tools from the central (MCP server) endpoint, would be a much better solution for our use case. Luckily, AgentCore Gateway helps to solve these challenges by treating existing MCP servers as native targets. This gives us a single point of control for routing, authentication, and tool management. It makes it as simple to integrate MCP servers as to add other targets to the gateway. AgentCore made it possible by supporting multiple targets. Those are, as of now: OpenAPI, Smithy, Amazon API Gateway, AWS Lambda, MCP Servers, and Integrations: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtx2vqmvatg9evkaldku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtx2vqmvatg9evkaldku.png" alt=" " width="798" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conference Talks and Applications Demo
&lt;/h2&gt;

&lt;p&gt;For creating, searching, and applying the talks for the conferences, I implemented a small &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/conference-talks-and-applications-app" rel="noopener noreferrer"&gt;conference-talks-and-applications-demo&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo27stzmtpcrkfji5ktf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo27stzmtpcrkfji5ktf7.png" alt=" " width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I currently don't use any database to store the talks and conference applications for simplicity reasons.  My goal is only to demonstrate the approach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; I maintain a static list of the talks in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/src/main/java/software/amazonaws/example/conference/handler/GetConferenceTalksByTitleSubstring.java" rel="noopener noreferrer"&gt;GetConferenceTalksByTitleSubstring&lt;/a&gt; class. The search consists of looking for the provided substring of the title.&lt;/li&gt;
&lt;li&gt; When creating a new talk, I generate its random ID between 1 and 100 in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/src/main/java/software/amazonaws/example/conference/handler/CreateConferenceTalk.java" rel="noopener noreferrer"&gt;CreateConferenceTalk&lt;/a&gt; class and return the talk with ID, title, and description.&lt;/li&gt;
&lt;li&gt;When applying for a talk for a specific conference, I simply acknowledge that the application is created in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/src/main/java/software/amazonaws/example/conference/handler/CreateConferenceApplication.java" rel="noopener noreferrer"&gt;CreateConferenceTalk&lt;/a&gt; class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I prefer to use AWS SAM as IaC for pure Serverless applications. Unfortunately, AWS SAM doesn't provide any IaC for Amazon Bedrock AgentCore yet.  Also, SAM has some limitations, as it's, for example, not possible to create the response codes for each API. And those response codes are required by the OpenAPI specification to be present. That's why I created &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/ConferenceTalksAndApplicationsAppAPI-OpenAPISpec.yaml" rel="noopener noreferrer"&gt;OpenAPI spec&lt;/a&gt; on my own for it. We can refer to this specification when defining the API like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Api&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;Stage&lt;/span&gt;
    &lt;span class="na"&gt;DefinitionBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;Fn::Transform&lt;/span&gt;
           &lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Include&lt;/span&gt;
           &lt;span class="s"&gt;Parameters&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
             &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConferenceTalksAndApplicationsAppAPI-OpenAPISpec.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also secured our API with an API key, whose value is by definition passed as the HTTP header parameter "x-api-key". This will play a role when we configure the outbound authentication of the AgentCore Gateway API Gateway target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyApiKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::ApiKey&lt;/span&gt;
  &lt;span class="s"&gt;....&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConferenceTalksAndApplicationsAppAPIKey"&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConferenceTalksAndApplicationsApp&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Key"&lt;/span&gt;
    &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;GenerateDistinctId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a6ZbcDgjkQW10BN56ASR25&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also defined an API stage with the name &lt;em&gt;prod&lt;/em&gt;. Now, we can deploy this application by executing &lt;code&gt;sam deploy -g&lt;/code&gt;, and we will see the individual URL in the response. For example, &lt;em&gt;&lt;a href="https://k370s19lk3.execute-api.us-east-1.amazonaws.com/prod" rel="noopener noreferrer"&gt;https://k370s19lk3.execute-api.us-east-1.amazonaws.com/prod&lt;/a&gt;&lt;/em&gt;.  We'll need the REST API ID, which in our case is k370s19lk3, later when creating the IaC for the AgentCore Gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create AgentCore Gateway with different targets
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we started to create the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk" rel="noopener noreferrer"&gt;IaC for the Conference (Search) application&lt;/a&gt;. It consisted mainly of the AgentCore Runtime with the MCP protocol and everything needed for that, like the Cognito User (Client) Pool. We used CDK for Java for it. We'll now call this application the Conference application, as we are extending its functionality beyond the search. Our goal is now to create AgentCore Gateway with 2 targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;existing AgentCore Runtime with MCP protocol for the conference search (MCP) tools&lt;/li&gt;
&lt;li&gt;conference talks and applications demo deployed on Amazon Gateway API to expose all its APIs as (MCP) tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the full source code in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/agentcore/gateway/GatewayTargetStack.java" rel="noopener noreferrer"&gt;GatewayTargetStack&lt;/a&gt; class. &lt;/p&gt;

&lt;p&gt;Let's go step-by-step through it. We first create the AgentCore Gateway itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Gateway-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gatewayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"-gateway"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomJwtAuthorizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;allowedClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
               &lt;span class="n"&gt;userPoolClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserPoolClientId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;discoveryUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;COGNITO_DISCOVERY_URL&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeWithMCPStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgenCore Runtime with MCP protocol for running conference search app"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most interesting part is configuring the custom JWT authorizer as an inbound authentication. Here we reuse the Cognito User (Client) Pool created in part 2. We set the same user client pool ID and discovery URL. We also reuse the same AWS IAM role that we used to create AgentCore  Runtime in part 2. Please also read the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/policy-getting-started.html" rel="noopener noreferrer"&gt;Getting started with Policy in AgentCore&lt;/a&gt; in addition to the resources from part 2 on how to create one.&lt;/p&gt;

&lt;p&gt;Now, let's create the AgentCore Gateway target of our MCP Server running on AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCP-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;McpServerTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;         
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialProviderConfigurations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauthCredentialProviderConfigs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gatewayTargetName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mcp-target"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgentCore Runtime MCP Server Target "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set &lt;em&gt;McpServerTargetConfiguration&lt;/em&gt;, which defines that the Gateway target is the MCP Server running on AgentCore Runtime. Also, we set the target name and description, and provide the AgentCore Gateway to which this target belongs.  We need to set the endpoint URL, which always follows the same schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://bedrock-agentcore."&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="s"&gt;".amazonaws.com/runtimes/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="nc"&gt;RuntimeWithMCPStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAgentRuntimeId&lt;/span&gt;&lt;span class="o"&gt;()+&lt;/span&gt;
       &lt;span class="s"&gt;"/invocations? 
       qualifier=DEFAULT&amp;amp;accountId="&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getAccount&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We obtain the runtime ID property from the created AgentCore Runtime in the &lt;em&gt;RuntimeWithMCPStack&lt;/em&gt; stack. The next part is to configure the outbound authentication. This means configuring how the AgentCore Gateway MCP target authenticates with the AgentCore Runtime with the MCP protocol. For this, we need to use AgentCore Identity.&lt;br&gt;
As described in the following &lt;a href="https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/23" rel="noopener noreferrer"&gt;issue&lt;/a&gt;, it's currently not possible to create the AgentCore Identity with CloudFormation. That's why CDK also can't provide this functionality. That's why we need to create it manually and then provide the configuration for this stack. Let's secure it with the existing OAuth Client. Let's go to AgentCore Identity and click on "Add Outbound Auth" -&amp;gt; "Add OAuth Client". Then select "Custom Provider" -&amp;gt; "Discovery URL" :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnrsvb1oprnutpatb4el2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnrsvb1oprnutpatb4el2.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can reuse the Cognito User Pool Client ID, Client Secret, and Discovery URL from &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After we created the AgentCore Identity, let's grab its ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncju8yxda8r98p3n05ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncju8yxda8r98p3n05ft.png" alt=" " width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Client Secret will be automatically stored as a Secret in the AWS Secrets Manager. Let's also grab the Secret ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf555ge962hqi15p4bhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf555ge962hqi15p4bhu.png" alt=" " width="799" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's configure both in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agentcoreIdentityOutboundOAuthArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:us-east-1:{AWS_ACCOUNT_ID}:token-vault/default/oauth2credentialprovider/resource-provider-oauth-gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"oAuthSecretArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:{AWS_ACCOUNT_ID}:secret:bedrock-agentcore-identity!default/oauth2/resource-provider-oauth-gateway-ba3b089d-toYfaV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace both values with your individual ARNs. I explained in part 2 how we handle the AWS Account ID. Now, let's create and configure the credential provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CloudFormation, see the issue https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/2391&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oAuthProviderArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"agentcoreIdentityOutboundOAuthArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oAuthSecretArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"oAuthSecretArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oauthCredentialProviderConfigs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GatewayCredentialProvider&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromOauthIdentityArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OAuthConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;providerArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oAuthProviderArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;secretArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oAuthSecretArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

  &lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCP-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialProviderConfigurations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauthCredentialProviderConfigs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We grab the AgentCore Identity and Secret ARNs and use them to create an OAuth Credential Provider. We then set it when creating the AgentCore Target credential provider configuration.&lt;/p&gt;

&lt;p&gt;Now we are done with creating the AgentCore MCP Target.  The next step is to create an Amazon API Gateway target. Please also read the article &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-api-gateway.html" rel="noopener noreferrer"&gt;AgentCore Gateway Amazon API Gateway stages&lt;/a&gt; to gain an understanding of how AgentCore Gateway obtains the OpenAPI spec from the Amazon API Gateway stage.&lt;/p&gt;

&lt;p&gt;First of all, let's define the API stage name in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"restApiStageName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll pass the restApiId via the console parameter. We created it above when we deployed the conference talks and applications demo. Similar to AWS Account ID, which is public, we don't want to configure it in cdk.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;restApiId&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"restApiId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;restApiStageName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"restApiStageName"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;         
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;restApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRestApiId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-ID"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;restApiId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restApiStageName&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;...&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gatewayTargetName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apigateway-target"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Amazon ApiGateway Target "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we create the AgentCore Gateway Target as an Amazon API Gateway Target, set the target name and description. We also provide the REST API ID, stage, and AgentCore Gateway to which this target belongs.&lt;/p&gt;

&lt;p&gt;We can define the tool filters. With that, we can shrink what Amazon API Gateway APIs will be exposed as MCP tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolFilters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ApiGatewayToolFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks/{titleSubstring}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                                
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;                 
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;ApiGatewayToolFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/apply"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;ApiGatewayToolFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                    
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our example, we expose all 3 APIs (/apply, /talks, //talks/{titleSubstring}) as MCP tools.&lt;/p&gt;

&lt;p&gt;Next, let's use the tool override to give the MCP tools the proper names and descriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolOverrides&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;ApiGatewayToolOverride&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apply-to-conferences-w-conference-id-talk-id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/apply"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apply to the conference with conference Id and talk Id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
         &lt;span class="nc"&gt;ApiGatewayToolOverride&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create-new-talk"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create a new talk with talk Id, title and description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;                    
         &lt;span class="nc"&gt;ApiGatewayToolOverride&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get-talks-by-title-substring"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks/{titleSubstring}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get talks by their title substring"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, the LLM can easily find the right tool for the job.&lt;/p&gt;

&lt;p&gt;The last part is to define how AgentCore Gateway handles the outbound authentication to the Amazon API Gateway. As described above and in the following &lt;a href="https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/23" rel="noopener noreferrer"&gt;issue&lt;/a&gt;, it's currently not possible to create the AgentCore Identity with CloudFormation. That's why CDK also can't provide this functionality. That's why we need to create it manually and then provide the configuration for this stack. Let's secure this Target with the API Key, as it is how we secured our Amazon Gateway API. Let's go to AgentCore Identity and click on "Add Outbound Auth" -&amp;gt; "Add API Key" :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis2os77ekoke7z1d0cdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis2os77ekoke7z1d0cdh.png" alt=" " width="744" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please use the same API Key that we used to secure our API. We defined it in the  &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/template.yaml" rel="noopener noreferrer"&gt;SAM template&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
After we created the AgentCore Identity, let's grab its ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i3n44bxl7e81xevky0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i3n44bxl7e81xevky0y.png" alt=" " width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Client Secret will be automatically stored as a Secret in the AWS Secrets Manager. Let's also grab the Secret ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npa4hw9j3qf2ni3o3zb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npa4hw9j3qf2ni3o3zb.png" alt=" " width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's configure both in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agentcoreIdentityOutboundApiKeyArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:us-east-1:{AWS_ACCOUNT_ID}:token-vault/default/apikeycredentialprovider/resource-provider-api-key-gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKeySecretArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:{AWS_ACCOUNT_ID}:secret:bedrock-agentcore-identity!default/apikey/resource-provider-api-key-gateway-02d581b0-L9scmD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace both values with your individual ARNs. I explained in part 2 how we handle the AWS Account ID. Now, let's create and configure the credential provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKeyProviderArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"agentcoreIdentityOutboundApiKeyArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKeySecretArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"apiKeySecretArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKeyProviderConfigs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GatewayCredentialProvider&lt;/span&gt;          
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromApiKeyIdentityArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiKeyCredentialProviderProps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;providerArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKeyProviderArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;secretArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKeySecretArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialLocation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiKeyCredentialLocation&lt;/span&gt;                    
                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiKeyAdditionalConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialParameterName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-api-key"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialPrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialProviderConfigurations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKeyProviderConfigs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We grab the AgentCore Identity and Secret ARNs and use them to create an API Key Credential Provider. Then we define to set the credentials within the HTTP header with the name &lt;em&gt;x-api-key&lt;/em&gt;. This is how we secured the Amazon API Gateway. Another option that AgentCore Gateway supports is to set them as query parameters. We then set them when creating the AgentCore Target credential provider configuration.&lt;/p&gt;

&lt;p&gt;To deploy the AgentCore Gateway, please invoke &lt;code&gt;cdk deploy spring-ai-conference-search-agentcore-gateway-with-mcp-server-target-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID} -c restApiId={YOUR_API_ID}&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;After having successfully executed the AgentCore Gateway deployment, we'll see our Gateway in the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0fnro4uqhadcnrtsu84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0fnro4uqhadcnrtsu84.png" alt=" " width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to grab the Gateway URL, which ends with &lt;em&gt;/mcp&lt;/em&gt;.  We also see both Gateway targets we created:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bgts2vqw5d9fzhmi4nz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bgts2vqw5d9fzhmi4nz.png" alt=" " width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This AgentCore Gateway exposes 7 MCP tools in total:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; 4 tools for the conference search provided by the MCP server from part 2 and deployed on AgentCore Runtime.&lt;/li&gt;
&lt;li&gt; 3 tools to create a talk, search for existing talks, and apply for the conference with the talk. These 3 tools are provided through the Amazon API Gateway we deployed in this article.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's extend our Conference Application MCP client that we developed in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, so it can use this AgentCore Gateway MCP endpoint.&lt;/p&gt;

&lt;p&gt;The important remaining topic is designing the IAM role and permissions so that AgentCore Gateway can handle inbound and outbound authentication and communicate with the Amazon API Gateway. I'll refer you to the articles, which cover those topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-inbound-auth.html#gateway-inbound-auth-iam" rel="noopener noreferrer"&gt;inbound authentication&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-outbound-auth.html#gateway-outbound-auth-oauth" rel="noopener noreferrer"&gt;outbound authorization with an OAuth client&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-outbound-auth.html#gateway-outbound-auth-api-key" rel="noopener noreferrer"&gt;outbound authorization with an API Key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-api-gateway.html#gateway-target-api-gateway-outbound" rel="noopener noreferrer"&gt;outbound authorization methods for an API Gateway API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Extend our local Conference Application MCP client
&lt;/h2&gt;

&lt;p&gt;In part 3, we developed a generic &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-agent-local" rel="noopener noreferrer"&gt;local MCP client&lt;/a&gt; capable of talking to each MCP server. I decided to extend it to be able to configure the AgentCore Gateway endpoint. This gives us the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;by configuring the &lt;em&gt;amazon.bedrock.agentcore.runtime.id&lt;/em&gt; property in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; to be not a blank string, we'll still connect to the MCP server running on AgentCore Runtime. It exposes only 4 MCP tools for the conference search.&lt;/li&gt;
&lt;li&gt;by configuring the &lt;em&gt;amazon.bedrock.agentcore.gateway.url&lt;/em&gt; property in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; to be not a blank string, we'll connect to the AgentCore Gateway created previously, which exposes all 7 MCP tools. This is how we'll use it to show what is possible with that. Please make sure that &lt;em&gt;amazon.bedrock.agentcore.runtime.id=&lt;/em&gt; is set to an empty string.&lt;/li&gt;
&lt;li&gt;by configuring both properties, &lt;em&gt;amazon.bedrock.agentcore.runtime.id&lt;/em&gt; takes precedence. This is how I implemented the logic in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt; class:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getMCPServerEndpoint&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="no"&gt;AGENTCORE_RUNTIME_ID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isBlank&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"https://bedrock-agentcore."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;awsRegion&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".amazonaws.com/runtimes/"&lt;/span&gt;
       &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;AGENTCORE_RUNTIME_ID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/invocations?qualifier=DEFAULT&amp;amp;accountId="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAccountId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="no"&gt;AGENTCORE_GATEWAY_URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isBlank&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;AGENTCORE_GATEWAY_URL&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" no AgentCore Runtime Id or AgentCore Gateway URL defined"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change this logic if you wish.&lt;/p&gt;

&lt;p&gt;Now we can use CURL or &lt;a href="https://httpie.io/docs/cli/installation" rel="noopener noreferrer"&gt;HTTPie&lt;/a&gt; to send some prompts. For example:&lt;/p&gt;

&lt;p&gt;"Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference".&lt;/p&gt;

&lt;p&gt;Here is an example of the request with HTTPie:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http GET http://localhost:8080/conference?prompt="Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference." Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the correct LLM response: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahrxf31uh3ctynvjgvs4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahrxf31uh3ctynvjgvs4.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try another prompt:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http GET http://localhost:8080/conference?prompt="Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service. Then provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today. Finally, for each conference, apply individually for it with the talk just created." Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the correct LLM response again: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kuaovv9kzlbkc5os1it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kuaovv9kzlbkc5os1it.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool, we created AgentCore Gateway, which gives us centralized access to the MCP tools that we need or the agent needs to accomplish the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at how to provide the MCP Tools for the Conference application via AgentCore Gateway in a centralized way.&lt;/p&gt;

&lt;p&gt;As we saw in this and previous articles, the local MCP client for the Conference application, to talk to AgentCore Runtime or Gateway, became quite big. If we have many customers using such a client, changing and operating it can become quite challenging. That's why, in the next article, we look at how to deploy and run our MCP client on AgentCore Runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update to Spring AI 2.0
&lt;/h2&gt;

&lt;p&gt;After I published this article, Spring AI 2.0 became generally available. I also used the opportunity to update the example to use Spring Boot version 4.1. You can find the working example in my &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-2.0-conference-app-agent-local" rel="noopener noreferrer"&gt;spring-ai-2.0-conference-app-agent-local&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have noticed several major differences in the implementation of the MCP local client in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-conference-app-agent-local/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt; controller.&lt;/p&gt;

&lt;p&gt;The first one is that &lt;em&gt;ChatClient&lt;/em&gt; now requires passing the &lt;em&gt;ChatOptions.Builder&lt;/em&gt; object to the &lt;em&gt;defaultOptions&lt;/em&gt; method instead of the  &lt;em&gt;ChatOptions&lt;/em&gt; object itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us.anthropic.claude-sonnet-4-6"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the biggest difference is that instead of using &lt;em&gt;WebClient&lt;/em&gt; from the &lt;em&gt;Spring Webflux&lt;/em&gt; dependency, Spring AI 2.0 now uses &lt;em&gt;HttpRequest.Builder&lt;/em&gt; from the &lt;em&gt;java.net.http&lt;/em&gt; module to create an MCP HTTP Streamable transport. Also, the implementation of this protocol is now in the  &lt;em&gt;HttpClientStreamableHttpTransport&lt;/em&gt; class, which was adjusted to use the &lt;em&gt;java.net.http&lt;/em&gt; module. In Spring AI 1.*, the class name was &lt;em&gt;WebClientStreamableHttpTransport&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;McpClientTransport&lt;/span&gt; &lt;span class="nf"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

 &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mcpServerConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMCPServerConfig&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
 &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;headerValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

 &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpRequestBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headerValue&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpClientStreamableHttpTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;        
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpRequestBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also requires splitting the MCP endpoint URL into 2 parts: base URL (domain) and endpoint itself (/mcp), which we did in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;amazon.bedrock.agentcore.gateway.base.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://spring-ai-conference-search-agentcore-gateway-xizwpytsgm.gateway.bedrock-agentcore.us-east-1.amazonaws.com&lt;/span&gt;
&lt;span class="py"&gt;amazon.bedrock.agentcore.gateway.endpoint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, Spring AI 2.0 introduced some changes to how to use the MCP tools. This is how the code looked in the previous Spring AI 1.x versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code in Spring AI 2.0 uses the &lt;em&gt;tools&lt;/em&gt; method, capable of accepting the local tools as well as tool callbacks as its parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;&lt;span class="n"&gt;syncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we need to additionally define the ToolCallingManager Spring bean in the main &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-2.0-conference-app-agent-local/src/main/java/dev/vkazulkin/SpringAIConferenceLocalMCPClient.java" rel="noopener noreferrer"&gt;SpringAIConferenceLocalMCPClient&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingManager&lt;/span&gt; &lt;span class="nf"&gt;toolCallingManager&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else remains the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my other series on &lt;a href="https://dev.to/vkazulkin/series/41348"&gt;Building AI Agents with Embabel, Spring AI, and Amazon Bedrock AgentCore&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
  </channel>
</rss>
