<?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: Dennis Traub</title>
    <description>The latest articles on DEV Community by Dennis Traub (@dennistraub).</description>
    <link>https://dev.to/dennistraub</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F752988%2Fea2b8598-0758-4ffa-b8d7-d904360f46e4.png</url>
      <title>DEV Community: Dennis Traub</title>
      <link>https://dev.to/dennistraub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dennistraub"/>
    <language>en</language>
    <item>
      <title>8 Agents Wrote Perfect Components - And Nothing Worked</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Fri, 27 Mar 2026 22:50:44 +0000</pubDate>
      <link>https://dev.to/aws/8-agents-wrote-perfect-components-and-nothing-worked-2176</link>
      <guid>https://dev.to/aws/8-agents-wrote-perfect-components-and-nothing-worked-2176</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Parallel AI agents don't coordinate on shared contracts, such as column names, URL paths, parameter formats, or identifiers. Extract those contracts into a single reference file before generation, and run a review agent that traces end-to-end data flows once the parallel agents are done. This single step fixed all 17 bugs in one pass.&lt;/p&gt;




&lt;p&gt;I launched 8 AI agents in parallel to build a full-stack app on AWS: infrastructure stacks, a React frontend, and a Java backend. Each agent owned one piece, and they all delivered clean, compiling code. The CDK type-checked, the Java backend followed Spring Boot conventions, the React UI looked nice.&lt;/p&gt;

&lt;p&gt;But when I tried to wire them together I hit bugs at every single boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;A full-stack app on AWS with a lot of moving parts. Multiple CDK stacks for the infrastructure (IAM, VPC, DB with seed functions, Cognito, CodePipeline, CloudFront/WAF), a Spring Boot backend on ECS Fargate, and a React frontend hosted on S3.&lt;/p&gt;

&lt;p&gt;The implementation plan was thorough and covered every component. But it wasn't detailed enough for agents that need to agree on shared contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs
&lt;/h2&gt;

&lt;p&gt;The first two block everything. Bugs 3 through 5 only show up after you fix the previous ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: The Spring Boot app won't even start
&lt;/h3&gt;

&lt;p&gt;The seed data function creates a schema with &lt;code&gt;passenger_id&lt;/code&gt; and &lt;code&gt;full_name&lt;/code&gt;, but the Spring Boot entity maps to &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Agent 1: seed data function creates the schema&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;passengers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;passenger_id&lt;/span&gt;   &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;full_name&lt;/span&gt;      &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Agent 2: The Spring Boot entity maps the table&lt;/span&gt;
&lt;span class="nd"&gt;@Column&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="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// Schema says "passenger_id"&lt;/span&gt;
&lt;span class="nd"&gt;@Column&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="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// Schema says "full_name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;ddl-auto: validate&lt;/code&gt;, Hibernate checks the mapping on startup. But the columns don't exist, so the ECS task crashes before serving a single request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 2: Every call returns 404
&lt;/h3&gt;

&lt;p&gt;The CDK stack registers ALB routes for /approve and /generate while the Java client sends requests to /voucher/approve and /voucher/generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CDK ALB routes:  /approve, /generate
Java client:     /voucher/approve, /voucher/generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both agents wrote correct, working code in isolation, but the CDK stack used clean paths while the Java client added a service prefix. Neither checked the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 3: Missing request fields
&lt;/h3&gt;

&lt;p&gt;A downstream service validates four required fields. The Java client sends three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lambda expects:  escalationId, passengerId, amount, situation
Java sends:      escalationId, passengerId, amount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with the URLs from bug 2 fixed, every approval returns 400.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 4: User lookup doesn't work
&lt;/h3&gt;

&lt;p&gt;This one was the most interesting: three systems work with the user, and each of them created their own identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cognito custom attribute:  custom:passenger_id = "pax-a1b2c3d4-e5f6-..."
RDS seed data:             passenger_id = "PAX-a1b2c3d4-e5f6-..."
JWT subject claim:         sub = "a1b2c3d4-e5f6-..."  (Cognito UUID)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend uses &lt;code&gt;jwt.getSubject()&lt;/code&gt; to look up the user. That's a Cognito UUID - neither prefixed with &lt;code&gt;pax-&lt;/code&gt; nor with &lt;code&gt;PAX-&lt;/code&gt;. No user lookup ever returns a result.&lt;/p&gt;

&lt;p&gt;Three agents. Three naming conventions. Zero coordination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 5: Every status lookup returns "not found"
&lt;/h3&gt;

&lt;p&gt;A downstream service returns JSON. The Java client parses XML:&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="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FOUND_LOCAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warehouse-B-Shelf-47"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractXmlElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Looks for &amp;lt;status&amp;gt;...&amp;lt;/status&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No XML tags in a JSON string. &lt;code&gt;extractXmlElement&lt;/code&gt; returns empty for every single request.&lt;/p&gt;

&lt;p&gt;The agent that wrote the downstream service followed one spec (JSON). The agent that wrote the Java client followed a different spec (XML).&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs 6 to 17: SSM parameter path mismatches
&lt;/h3&gt;

&lt;p&gt;One CDK stack writes an SSM parameter. Another CDK stack reads it. But they never coordinated on paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Producer stack writes:  /${AppName}/test/data/rds-secret-arn
Consumer stack reads:   /${AppName}/${Env}/data/rds-password-secret-arn

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twelve SSM parameters mismatched between producer and consumer stacks. The app fails on every one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why parallel agents can't catch this
&lt;/h2&gt;

&lt;p&gt;Each agent had context about the overall plan and its own component. But none of them could see the implementation details that the others came up with.&lt;/p&gt;

&lt;p&gt;When I write an app, I hold the contracts in working memory. "The column is &lt;code&gt;passenger_id&lt;/code&gt;, so I'll use that in both the migration and the entity." But an AI agent writing the migration doesn't know what the entity agent chose for its column name - and vice versa.&lt;/p&gt;

&lt;p&gt;The plan contained all the high-level information, but the agents were reading different sections and making their own calls on the shared details.&lt;/p&gt;

&lt;p&gt;Each agent wrote correct code that followed good conventions. But they never coordinated. Like digging a tunnel from two sides of a mountain - without ever checking in with each other. &lt;/p&gt;

&lt;h2&gt;
  
  
  How I found all of them at once
&lt;/h2&gt;

&lt;p&gt;After generation, before actually deploying the app. I ran an architecture review agent with a simple instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trace the actual data flow from user login through form submission to the downstream service calls, following every cross-component boundary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It found every one of the bugs in a single pass.&lt;/p&gt;

&lt;p&gt;The review agent started at the user-facing entry point, traced the request through every boundary, and at each one checked whether what one component sent actually matched what the next one expected. Same thing integration tests do after deployment, but you catch it before deploying anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prevent seam bugs
&lt;/h2&gt;

&lt;p&gt;Before launching parallel agents, pull every shared contract out of the plan into a single reference file and pass it to every agent as mandatory context.&lt;/p&gt;

&lt;p&gt;Then, after your parallel agents did their thing, run a review agent that traces a few real user flows across all the boundaries.&lt;/p&gt;

&lt;p&gt;Fix the seam bugs in one pass, then deploy.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are seam bugs in AI-generated code?
&lt;/h3&gt;

&lt;p&gt;Seam bugs are integration defects at the boundaries between components built by different AI agents. Each agent writes correct, working code in isolation, but the components don't fit together because the agents each made their own decisions about shared details - things like what a column is called, what path an API lives at, or what format an identifier uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does parallel AI code generation produce integration bugs?
&lt;/h3&gt;

&lt;p&gt;Each agent only sees its own component and the plan it was given. When two agents need to agree on something - say, what a database column is called - they each pick a reasonable name independently. Those names often don't match. The plan says what the column should represent, but not necessarily the exact string both sides should use.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you catch integration bugs from parallel AI agents?
&lt;/h3&gt;

&lt;p&gt;Run a single review agent after generation that traces real user flows across all the boundaries. Give it a prompt like "trace the data flow from user login through the frontend, backend, to databases and downstream service calls, checking every boundary." It will catch the mismatches in one pass.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
      <category>productivity</category>
    </item>
    <item>
      <title>My 8 Agents Wrote Perfect Components - And Nothing Worked</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Fri, 27 Mar 2026 22:50:44 +0000</pubDate>
      <link>https://dev.to/aws/8-agents-wrote-perfect-components-and-nothing-worked-5h2b</link>
      <guid>https://dev.to/aws/8-agents-wrote-perfect-components-and-nothing-worked-5h2b</guid>
      <description>&lt;p&gt;I launched 8 AI agents in parallel to build a full-stack app on AWS: infrastructure stacks, a React frontend, and a Java backend. Each agent owned one piece, and they all delivered clean, compiling code. The CDK type-checked, the Java backend followed Spring Boot conventions, the React UI looked nice.&lt;/p&gt;

&lt;p&gt;But when I tried to wire them together I hit bugs at every single boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;A full-stack app on AWS with a lot of moving parts. Multiple CDK stacks for the infrastructure (IAM, VPC, DB with seed functions, Cognito, CodePipeline, CloudFront/WAF), a Spring Boot backend on ECS Fargate, and a React frontend hosted on S3.&lt;/p&gt;

&lt;p&gt;The implementation plan was thorough and covered every component. But it wasn't detailed enough for agents that need to agree on shared contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs
&lt;/h2&gt;

&lt;p&gt;The first two block everything. Bugs 3 through 5 only show up after you fix the previous ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: The Spring Boot app won't even start
&lt;/h3&gt;

&lt;p&gt;The seed data function creates a schema with &lt;code&gt;passenger_id&lt;/code&gt; and &lt;code&gt;full_name&lt;/code&gt;, but the Spring Boot entity maps to &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Agent 1: seed data function creates the schema&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;passengers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;passenger_id&lt;/span&gt;   &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;full_name&lt;/span&gt;      &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Agent 2: The Spring Boot entity maps the table&lt;/span&gt;
&lt;span class="nd"&gt;@Column&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="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// Schema says "passenger_id"&lt;/span&gt;
&lt;span class="nd"&gt;@Column&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="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// Schema says "full_name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;ddl-auto: validate&lt;/code&gt;, Hibernate checks the mapping on startup. But the columns don't exist, so the ECS task crashes before serving a single request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 2: Every call returns 404
&lt;/h3&gt;

&lt;p&gt;The CDK stack registers ALB routes for /approve and /generate while the Java client sends requests to /voucher/approve and /voucher/generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CDK ALB routes:  /approve, /generate
Java client:     /voucher/approve, /voucher/generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both agents wrote correct, working code in isolation, but the CDK stack used clean paths while the Java client added a service prefix. Neither checked the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 3: Missing request fields
&lt;/h3&gt;

&lt;p&gt;A downstream service validates four required fields. The Java client sends three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lambda expects:  escalationId, passengerId, amount, situation
Java sends:      escalationId, passengerId, amount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with the URLs from bug 2 fixed, every approval returns 400.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 4: User lookup doesn't work
&lt;/h3&gt;

&lt;p&gt;This one was the most interesting: three systems work with the user, and each of them created their own identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cognito custom attribute:  custom:passenger_id = "pax-a1b2c3d4-e5f6-..."
RDS seed data:             passenger_id = "PAX-a1b2c3d4-e5f6-..."
JWT subject claim:         sub = "a1b2c3d4-e5f6-..."  (Cognito UUID)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend uses &lt;code&gt;jwt.getSubject()&lt;/code&gt; to look up the user. That's a Cognito UUID - neither prefixed with &lt;code&gt;pax-&lt;/code&gt; nor with &lt;code&gt;PAX-&lt;/code&gt;. No user lookup ever returns a result.&lt;/p&gt;

&lt;p&gt;Three agents. Three naming conventions. Zero coordination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 5: Every status lookup returns "not found"
&lt;/h3&gt;

&lt;p&gt;A downstream service returns JSON. The Java client parses XML:&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="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FOUND_LOCAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warehouse-B-Shelf-47"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractXmlElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Looks for &amp;lt;status&amp;gt;...&amp;lt;/status&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No XML tags in a JSON string. &lt;code&gt;extractXmlElement&lt;/code&gt; returns empty for every single request.&lt;/p&gt;

&lt;p&gt;The agent that wrote the downstream service followed one spec (JSON). The agent that wrote the Java client followed a different spec (XML).&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs 6 to 17: SSM parameter path mismatches
&lt;/h3&gt;

&lt;p&gt;One CDK stack writes an SSM parameter. Another CDK stack reads it. But they never coordinated on paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Producer stack writes:  /${AppName}/test/data/rds-secret-arn
Consumer stack reads:   /${AppName}/${Env}/data/rds-password-secret-arn

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twelve SSM parameters mismatched between producer and consumer stacks. The app fails on every one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why parallel agents can't catch this
&lt;/h2&gt;

&lt;p&gt;Each agent had context about the overall plan and its own component. But none of them could see the implementation details that the others came up with.&lt;/p&gt;

&lt;p&gt;When I write an app, I hold the contracts in working memory. "The column is &lt;code&gt;passenger_id&lt;/code&gt;, so I'll use that in both the migration and the entity." But an AI agent writing the migration doesn't know what the entity agent chose for its column name - and vice versa.&lt;/p&gt;

&lt;p&gt;The plan contained all the high-level information, but the agents were reading different sections and making their own calls on the shared details.&lt;/p&gt;

&lt;p&gt;Each agent wrote correct code that followed good conventions. But they never coordinated. Like digging a tunnel from two sides of a mountain - without ever checking in with each other. &lt;/p&gt;

&lt;h2&gt;
  
  
  How I found all of them at once
&lt;/h2&gt;

&lt;p&gt;After generation, before actually deploying the app. I ran an architecture review agent with a simple instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trace the actual data flow from user login through form 
submission to the downstream service calls, following every 
cross-component boundary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It found every one of the bugs in a single pass.&lt;/p&gt;

&lt;p&gt;The review agent started at the user-facing entry point, traced the request through every boundary, and at each one checked whether what one component sent actually matched what the next one expected. Same thing integration tests do after deployment, but you catch it before deploying anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prevent seam bugs
&lt;/h2&gt;

&lt;p&gt;Before launching parallel agents, pull every shared contract out of the plan into a single reference file and pass it to every agent as mandatory context.&lt;/p&gt;

&lt;p&gt;Then, after your parallel agents did their thing, run a review agent that traces a few real user flows across all the boundaries.&lt;/p&gt;

&lt;p&gt;Fix the seam bugs in one pass, then deploy.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are seam bugs in AI-generated code?
&lt;/h3&gt;

&lt;p&gt;Seam bugs are integration defects at the boundaries between components built by different AI agents. Each agent writes correct, working code in isolation, but the components don't fit together because the agents each made their own decisions about shared details - things like what a column is called, what path an API lives at, or what format an identifier uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does parallel AI code generation produce integration bugs?
&lt;/h3&gt;

&lt;p&gt;Each agent only sees its own component and the plan it was given. When two agents need to agree on something - say, what a database column is called - they each pick a reasonable name independently. Those names often don't match. The plan says what the column should represent, but not necessarily the exact string both sides should use.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you catch integration bugs from parallel AI agents?
&lt;/h3&gt;

&lt;p&gt;Run a single review agent after generation that traces real user flows across all the boundaries. Give it a prompt like "trace the data flow from user login through the frontend, backend, to databases and downstream service calls, checking every boundary." It will catch the mismatches in one pass.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Missing from the MCP debate: Who holds the keys when 50 agents access 50 APIs?</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Wed, 18 Mar 2026 12:35:57 +0000</pubDate>
      <link>https://dev.to/aws/missing-from-the-mcp-debate-who-holds-the-keys-when-50-agents-access-50-apis-mb3</link>
      <guid>https://dev.to/aws/missing-from-the-mcp-debate-who-holds-the-keys-when-50-agents-access-50-apis-mb3</guid>
      <description>&lt;p&gt;There are two debates happening right now:&lt;/p&gt;

&lt;p&gt;CLI vs MCP - should agents call existing CLIs or use an MCP server? And API vs MCP - does wrapping a REST API in an MCP server add value, or just complexity?&lt;/p&gt;

&lt;p&gt;Both focus on how agents call tools. What both aren't asking is, who holds the credentials when they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fifty agents, fifty sets of keys
&lt;/h2&gt;

&lt;p&gt;When one developer runs one agent on one laptop, credentials are simple. You store them locally, maybe rotate them, and move on.&lt;/p&gt;

&lt;p&gt;But that's not where we're heading. Dozens of agents per team, each needing access to Slack, GitHub, Jira, Office 365, that legacy CRM, multiple SaaS tools, and all your internal APIs.&lt;/p&gt;

&lt;p&gt;Some of those have CLIs. Most don't - they're SaaS products with REST APIs. If you're lucky - who knows how many production systems still use a global, password-protected admin account.&lt;/p&gt;

&lt;p&gt;So every agent needs a separate API key, OAuth token, or username/password pair. For each downstream system. On every machine. And if you've ever managed API keys for a team, you know where this goes. Keys in &lt;code&gt;.env&lt;/code&gt; files, shared over Slack, committed to repos, never rotated.&lt;/p&gt;

&lt;p&gt;Now hand that problem to fifty autonomous agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened to SSO?
&lt;/h2&gt;

&lt;p&gt;Most organizations with any sense of security have established SSO, spent years consolidating identity. Every SaaS tool, every internal system, every third-party integration flows through one identity provider. &lt;/p&gt;

&lt;p&gt;When someone leaves, you disable a single account. When compliance asks about access controls, there's one answer - and you know exactly where to find it.&lt;/p&gt;

&lt;p&gt;And now, agents are about to blow a wide open hole into everything you've built. Whether your agent calls a CLI, hits a REST API, or talks to an MCP server, it needs credentials. And if those credentials live on the agent's machine, they live outside your identity boundary.&lt;/p&gt;

&lt;p&gt;Imagine a contractor wrapping up on Friday. You disable their SSO account, but their laptop still has three agents with API keys for your CRM, your internal docs, and your deployment pipeline. Those keys don't expire with the SSO account. Those agents can continue calling your APIs long after the contractor has moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote MCP servers are identity boundaries
&lt;/h2&gt;

&lt;p&gt;This is where remote MCP servers earn their place in both debates.&lt;/p&gt;

&lt;p&gt;The CLI vs MCP crowd argues about token efficiency. The API vs MCP crowd argues about unnecessary abstraction. Neither side is talking about the nightmare of decentralized credential management.&lt;/p&gt;

&lt;p&gt;Charles Chen makes this point well in &lt;a href="https://chrlschn.dev/blog/2026/03/mcp-is-dead-long-live-mcp/" rel="noopener noreferrer"&gt;MCP is Dead; Long Live MCP!&lt;/a&gt;. Most of the debate ignores the difference between MCP over &lt;code&gt;stdio&lt;/code&gt; (local, and yeah, mostly pointless compared to raw &lt;code&gt;curl&lt;/code&gt; or a CLI) and MCP over streamable HTTP (remote, centralized). Once MCP runs as a centralized server, users authenticate via OAuth and never touch the downstream keys. &lt;/p&gt;

&lt;p&gt;As he puts it: "An engineer leaves your team? Revoke their OAuth token and access to the MCP server; they never had access to other keys and secrets to start with."&lt;/p&gt;

&lt;p&gt;Now take that one step further. In most organizations, that OAuth isn't standalone - it flows through SSO. The MCP server becomes an identity boundary. Your users never store any API keys, custom tokens, or service accounts. One auth mechanism instead of one per machine per agent per API.&lt;/p&gt;

&lt;p&gt;Disable the SSO account, and every agent loses access. To everything.&lt;/p&gt;

&lt;p&gt;But we already learned this, right? Every microservice managing its own database credentials was a nightmare until we centralized secrets management. Agent credentials are the same problem, just one layer up.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
    <item>
      <title>3 Things I Wish I Knew Before Setting Up a UV Workspace</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Fri, 27 Feb 2026 17:57:09 +0000</pubDate>
      <link>https://dev.to/aws/3-things-i-wish-i-knew-before-setting-up-a-uv-workspace-30j6</link>
      <guid>https://dev.to/aws/3-things-i-wish-i-knew-before-setting-up-a-uv-workspace-30j6</guid>
      <description>&lt;p&gt;I love &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt;, it's so much better than &lt;code&gt;pip&lt;/code&gt;, but I'm still learning the ins and outs. Today I was setting up a Python monorepo with &lt;a href="https://docs.astral.sh/uv/concepts/workspaces/" rel="noopener noreferrer"&gt;uv workspaces&lt;/a&gt; and ran into a few issues, the fixes of which were trivial once I knew about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Give the Root a Distinct Name
&lt;/h2&gt;

&lt;p&gt;First, a virtual root (&lt;code&gt;package = false&lt;/code&gt;) still needs a &lt;code&gt;[project] name&lt;/code&gt; - and it can't match any member package.&lt;/p&gt;

&lt;p&gt;I had both the root and my core package using the same name, e.g. &lt;code&gt;my-app&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app/                   # workspace root
  pyproject.toml          # name = "my-app" &amp;lt;- problem!
  packages/
    core/
      pyproject.toml      # name = "my-app"
      src/core/
    cli/
      pyproject.toml      # name = "my-app-cli"
      src/cli/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I ran &lt;code&gt;uv sync&lt;/code&gt;, it refused outright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ uv sync
error: Two workspace members are both named `my-app`:
  `/path/to/my-app` and `/path/to/my-app/packages/core`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though the root has &lt;code&gt;package = false&lt;/code&gt;, uv still registers its &lt;code&gt;name&lt;/code&gt; as a workspace member identity. Same name, two members, no way to disambiguate.&lt;/p&gt;

&lt;p&gt;The fix - give the root a workspace-specific name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Root pyproject.toml&lt;/span&gt;
&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-app-workspace"&lt;/span&gt;  &lt;span class="c"&gt;# NOT "my-app"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.12&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nn"&gt;[tool.uv]&lt;/span&gt;
&lt;span class="py"&gt;package&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[tool.uv.workspace]&lt;/span&gt;
&lt;span class="py"&gt;members&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"packages/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[dependency-groups]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"pytest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"ruff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to note: &lt;code&gt;package = false&lt;/code&gt; means "don't install me", not "don't need a name". And dev dependencies go in &lt;code&gt;[dependency-groups]&lt;/code&gt; (&lt;a href="https://peps.python.org/pep-0735/" rel="noopener noreferrer"&gt;PEP 735&lt;/a&gt;), not &lt;code&gt;[project.dependencies]&lt;/code&gt; - the root is virtual, so project dependencies are just metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use &lt;code&gt;workspace = true&lt;/code&gt; for Inter-Package Deps
&lt;/h2&gt;

&lt;p&gt;When one workspace package depends on another, you need two things: a normal dependency declaration &lt;em&gt;and&lt;/em&gt; a &lt;a href="https://docs.astral.sh/uv/concepts/workspaces/#workspace-sources" rel="noopener noreferrer"&gt;&lt;code&gt;[tool.uv.sources]&lt;/code&gt;&lt;/a&gt; entry telling uv to resolve it locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# packages/cli/pyproject.toml&lt;/span&gt;
&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-app-cli"&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[tool.uv.sources]&lt;/span&gt;
&lt;span class="py"&gt;my-app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;workspace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without the &lt;code&gt;[tool.uv.sources]&lt;/code&gt; entry, &lt;code&gt;uv sync&lt;/code&gt; fails with a helpful but initially confusing error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ uv sync
  x Failed to build `my-app-cli @ file:///path/to/packages/cli`
  |-- Failed to parse entry: `my-app`
  \-- `my-app` is included as a workspace member, but is missing
      an entry in `tool.uv.sources`
      (e.g., `my-app = { workspace = true }`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At least uv tells you exactly what to add.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;[project.dependencies]&lt;/code&gt; list stays &lt;a href="https://peps.python.org/pep-0621/" rel="noopener noreferrer"&gt;PEP 621&lt;/a&gt; compliant, so any standard Python tool can read it. The &lt;code&gt;[tool.uv.sources]&lt;/code&gt; table is uv-specific and only affects resolution. And &lt;code&gt;uv sync&lt;/code&gt; installs the local package as editable automatically - changes are immediately visible without reinstalling.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use &lt;code&gt;importlib&lt;/code&gt; Mode for pytest
&lt;/h2&gt;

&lt;p&gt;When running &lt;code&gt;pytest&lt;/code&gt; across a workspace where multiple packages have &lt;code&gt;tests/&lt;/code&gt; directories with same-named test files (e.g. both have &lt;code&gt;test_helpers.py&lt;/code&gt;), pytest's default import mode breaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ uv run pytest packages/ -v
collected 1 item / 1 error

ERROR collecting packages/core/tests/test_helpers.py
import file mismatch:
imported module 'test_helpers' has this __file__ attribute:
  /path/to/packages/cli/tests/test_helpers.py
which is not the same as the test file we want to collect:
  /path/to/packages/core/tests/test_helpers.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pytest's default &lt;a href="https://docs.pytest.org/en/stable/explanation/pythonpath.html#import-modes" rel="noopener noreferrer"&gt;&lt;code&gt;prepend&lt;/code&gt; import mode&lt;/a&gt; treats both &lt;code&gt;test_helpers.py&lt;/code&gt; as the same module. It imports the first one, caches it, then errors when the second file doesn't match.&lt;/p&gt;

&lt;p&gt;The fix - add &lt;code&gt;importlib&lt;/code&gt; mode to your root &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Root pyproject.toml&lt;/span&gt;
&lt;span class="nn"&gt;[tool.pytest.ini_options]&lt;/span&gt;
&lt;span class="py"&gt;addopts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"--import-mode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;importlib&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ uv run pytest packages/ -v
packages/cli/tests/test_helpers.py::test_cli_helper PASSED    [ 50%]
packages/core/tests/test_helpers.py::test_core_helper PASSED  [100%]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Don't add &lt;code&gt;__init__.py&lt;/code&gt; to your test directories as a workaround - with &lt;code&gt;importlib&lt;/code&gt; mode, that can actually cause a &lt;em&gt;silent&lt;/em&gt; bug where pytest resolves both files to the same cached module and runs the wrong tests without any error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't uv-specific - it's a Python monorepo thing. But uv workspaces make monorepos easy to set up, so you're likely to hit it early.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.astral.sh/uv/concepts/workspaces/" rel="noopener noreferrer"&gt;uv Workspaces docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://peps.python.org/pep-0735/" rel="noopener noreferrer"&gt;PEP 735 - Dependency Groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://peps.python.org/pep-0621/" rel="noopener noreferrer"&gt;PEP 621 - Project metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/explanation/goodpractices.html" rel="noopener noreferrer"&gt;pytest - Good Integration Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>uv</category>
      <category>monorepo</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to Use Strands Agents' Built-In Session Persistence</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Tue, 17 Feb 2026 16:41:10 +0000</pubDate>
      <link>https://dev.to/aws/til-strands-agents-has-built-in-session-persistence-3nhl</link>
      <guid>https://dev.to/aws/til-strands-agents-has-built-in-session-persistence-3nhl</guid>
      <description>&lt;p&gt;Today I learned that the &lt;a href="https://github.com/strands-agents/sdk-python?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents SDK&lt;/a&gt; has a built-in persistence layer for conversation history.&lt;/p&gt;

&lt;p&gt;Pass a &lt;code&gt;SessionManager&lt;/code&gt; to the &lt;code&gt;Agent&lt;/code&gt; constructor, and every message and state change is persisted automatically through lifecycle hooks. No manual save/load calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;session_demo.py&lt;/code&gt; and run it with &lt;code&gt;uv run session_demo.py&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;# /// script&lt;/code&gt; block is &lt;a href="https://peps.python.org/pep-0723/" rel="noopener noreferrer"&gt;PEP 723 inline metadata&lt;/a&gt; - &lt;code&gt;uv run&lt;/code&gt; reads it to install dependencies automatically, no venv or pip needed. All you need is &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS credentials configured&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /// script
# requires-python = "&amp;gt;=3.10"
# dependencies = ["strands-agents"]
# ///
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.session.file_session_manager&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileSessionManager&lt;/span&gt;

&lt;span class="n"&gt;SESSION_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-abc-123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;STORAGE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./sessions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# defaults to /tmp/strands/sessions
&lt;/span&gt;
&lt;span class="c1"&gt;# First agent instance - ask a question
&lt;/span&gt;&lt;span class="n"&gt;agent1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;global.anthropic.claude-haiku-4-5-20251001-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FileSessionManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SESSION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;STORAGE_DIR&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;prompt1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the capital of France?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;agent1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Second agent instance - same session_id, loads conversation from disk
&lt;/span&gt;&lt;span class="n"&gt;agent2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;global.anthropic.claude-haiku-4-5-20251001-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FileSessionManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SESSION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;STORAGE_DIR&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;prompt2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What did I just ask you?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;agent2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;agent1&lt;/code&gt; and &lt;code&gt;agent2&lt;/code&gt; are separate &lt;code&gt;Agent&lt;/code&gt; instances - they share no memory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent2&lt;/code&gt; can answer "What did I just ask you?" because &lt;code&gt;FileSessionManager&lt;/code&gt; restored the conversation from disk when the second instance was created&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;agent_id&lt;/code&gt; identifies which agent's state to save and restore - required when using a session manager&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What Gets Persisted
&lt;/h2&gt;

&lt;p&gt;The session manager saves three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conversation history&lt;/strong&gt; - all user and assistant messages (the &lt;code&gt;messages/&lt;/code&gt; directory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent state&lt;/strong&gt; - a JSON-serializable key-value dict you can use for your own data (&lt;code&gt;agent.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session metadata&lt;/strong&gt; - timestamps and session type (&lt;code&gt;session.json&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After running the script, here's what's on disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sessions/
└── session_user-abc-123
    ├── agents
    │   └── agent_assistant
    │       ├── agent.json
    │       └── messages
    │           ├── message_0.json
    │           ├── message_1.json
    │           ├── message_2.json
    │           └── message_3.json
    ├── multi_agents
    └── session.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each message is a separate JSON file:&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;"message"&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;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&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;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What's the capital of France?"&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="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-17T14:45:31.439081+00:00"&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;User and assistant turns alternate through &lt;code&gt;message_0.json&lt;/code&gt; to &lt;code&gt;message_3.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-In Backends
&lt;/h2&gt;

&lt;p&gt;The example uses &lt;code&gt;FileSessionManager&lt;/code&gt;, but the SDK ships three backends:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Manager&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FileSessionManager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local development, single-process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S3SessionManager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Production, distributed, multi-container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RepositorySessionManager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom backend (implement &lt;code&gt;SessionRepository&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Tips and Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Troubleshooting tips
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;uv: command not found&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Install uv: &lt;code&gt;curl -LsSf https://astral.sh/uv/install.sh | sh&lt;/code&gt; (macOS/Linux) or &lt;code&gt;powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"&lt;/code&gt; (Windows). See &lt;a href="https://docs.astral.sh/uv/getting-started/installation/" rel="noopener noreferrer"&gt;uv installation docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;NoCredentialError&lt;/code&gt; or &lt;code&gt;Unable to locate credentials&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
AWS credentials aren't configured. Run &lt;code&gt;aws configure&lt;/code&gt; to set up a default profile, or export &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;. See &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS CLI configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AccessDeniedException&lt;/code&gt; when calling the model&lt;/strong&gt;&lt;br&gt;
Your AWS credentials don't have permission to invoke the Bedrock model. Make sure your IAM user or role has &lt;code&gt;bedrock:InvokeModel&lt;/code&gt; and &lt;code&gt;bedrock:InvokeModelWithResponseStream&lt;/code&gt; permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good to know
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;FileSessionManager&lt;/code&gt; is not safe for concurrent same-session writes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two API requests with the same &lt;code&gt;session_id&lt;/code&gt; writing to &lt;code&gt;FileSessionManager&lt;/code&gt; concurrently can corrupt the session data. The storage layer has no locking - it reads, appends, and writes the full JSON file without coordination.&lt;/p&gt;

&lt;p&gt;For development, this is fine. Single-process, single-user development (CLI, local testing) will never hit this. Sequential requests to the same session are safe.&lt;/p&gt;

&lt;p&gt;For production, you have three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Per-session locking in your API layer - serialize requests per session_id before they reach the Agent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;S3SessionManager&lt;/code&gt; - uses atomic S3 operations for safe concurrent writes&lt;/li&gt;
&lt;li&gt;A custom &lt;code&gt;SessionRepository&lt;/code&gt; - implement your own with proper concurrency handling (database-backed, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;agent_id&lt;/code&gt; is required with a session manager&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you omit &lt;code&gt;agent_id&lt;/code&gt; when using a &lt;code&gt;SessionManager&lt;/code&gt;, you'll get &lt;code&gt;ValueError: agent_id needs to be defined.&lt;/code&gt; The session system uses it as a directory key to separate state for different agents within the same session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;FileSessionManager&lt;/code&gt; defaults to &lt;code&gt;/tmp&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without an explicit &lt;code&gt;storage_dir&lt;/code&gt;, sessions are written to &lt;code&gt;/tmp/strands/sessions&lt;/code&gt; - which most operating systems wipe on reboot. Set it to a project-local path like &lt;code&gt;./sessions&lt;/code&gt; or &lt;code&gt;.data/sessions&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/agents/session-management/?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Session Management docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/agents/conversation-management/?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Conversation Management docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>agents</category>
      <category>aws</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Sun, 15 Feb 2026 11:20:33 +0000</pubDate>
      <link>https://dev.to/dennistraub/-31b6</link>
      <guid>https://dev.to/dennistraub/-31b6</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/aws" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&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%2Forganization%2Fprofile_image%2F1726%2F1f5cc5bc-5f61-428d-9d35-ba0c39f8af2d.png" alt="AWS" width="500" height="500"&gt;
      &lt;div class="ltag__link__user__pic"&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%2Fuser%2Fprofile_image%2F348349%2F213d7254-998a-413f-b7af-c96c087508b3.png" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/aws/from-zero-to-agentic-coding-running-claude-code-with-amazon-bedrock-1f00" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;From Zero to Agentic Coding: Running Claude Code with Amazon Bedrock&lt;/h2&gt;
      &lt;h3&gt;Gunnar Grosch for AWS ・ Feb 15&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#aws&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>aws</category>
      <category>ai</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Your Chatbot is the BlackBerry of the 2020s</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Fri, 13 Feb 2026 01:58:38 +0000</pubDate>
      <link>https://dev.to/aws/why-your-chatbot-is-the-blackberry-of-the-2020s-b5</link>
      <guid>https://dev.to/aws/why-your-chatbot-is-the-blackberry-of-the-2020s-b5</guid>
      <description>&lt;p&gt;This is my third major technological shift, and every time I hear the same question echo through the C-Suites:&lt;/p&gt;

&lt;p&gt;"But where's the ROI?"&lt;/p&gt;

&lt;p&gt;In the 90s, we put our print brochures and yellow pages on the web - and called it digital transformation. In the 2000s, we put tiny keyboards or a Windows start button on a phone and called it mobile computing.&lt;/p&gt;

&lt;p&gt;Both times, the answer wasn't better brochures or smaller buttons. The answer was Google and Facebook. It was the iPhone and Android. New ideas, by companies that didn't try to replicate the old world - with all its constraints - on a new medium. They built something entirely new. They embraced the new medium and built what couldn't have existed before.&lt;/p&gt;




&lt;p&gt;And now we're doing it again, with AI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every demo is a chatbot.&lt;/li&gt;
&lt;li&gt;Every pilot is "adding AI to our existing workflow."&lt;/li&gt;
&lt;li&gt;Every enterprise use case tries to optimize what already exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;But this time, it kind of works. And that's the trap.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;The web's brochureware visibly broke. The stylus on Windows Mobile was physically painful. But AI chatbots deliver just enough value to feel like progress.&lt;/p&gt;

&lt;p&gt;We're settling for 10% of what's possible and call it "revolutionizing the [industry of your choice]".&lt;/p&gt;

&lt;p&gt;But the real inflection point isn't better support agents or travel booking chatbots. It's when we stop replicating what's already there and start building something that never existed before.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How a subtle MCP server bug almost cost me $230 a month</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Wed, 11 Feb 2026 18:23:12 +0000</pubDate>
      <link>https://dev.to/aws/how-a-subtle-mcp-server-bug-almost-cost-me-230-a-month-22ij</link>
      <guid>https://dev.to/aws/how-a-subtle-mcp-server-bug-almost-cost-me-230-a-month-22ij</guid>
      <description>&lt;p&gt;An important part of my job is to collect and distill feedback into recommendations for product and engineering teams. Sure, not quite as glamorous as traveling the world, but it's a lot of fun: I'm getting paid to experiment with brand new tech - and I get to directly influence the developer experience of our products.&lt;/p&gt;

&lt;p&gt;But - just like every job - there's also a lot of routine work involved, including the boring type. And if there's anything my ADHD brain hates - with a passion! - it's boring routine work. And there's one thing right at the top of the list: processing tasks in a project management tool.&lt;/p&gt;

&lt;p&gt;So I built an AI agent to help me: triage tasks, add context, draft comments, move items around - all through an MCP server.&lt;/p&gt;

&lt;p&gt;As I said. Routine work. Boring and predictable. Right?&lt;/p&gt;

&lt;p&gt;Right. Until I noticed the time it needed for what should be simple updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Calls, Zero Updates
&lt;/h2&gt;

&lt;p&gt;The MCP server has an &lt;code&gt;update_task&lt;/code&gt; tool, and the agent called it with a &lt;code&gt;custom_fields&lt;/code&gt; parameter. The server took the request, processed it, and returned success. &lt;/p&gt;

&lt;p&gt;But when the agent continued, the custom fields were unchanged. It tried updating again - with a different format. Success. But nothing changed. Third attempt. Success. Still nothing.&lt;/p&gt;

&lt;p&gt;3 success responses. Zero successful updates. And the API never told the agent that anything was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking It Down
&lt;/h2&gt;

&lt;p&gt;So after multiple failed attempts, the agent started investigating on its own. It checked whether it had the right access permissions. Or if it can use &lt;code&gt;curl&lt;/code&gt; to bypass the MCP layer entirely. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; worked, showing that the problem wasn't permissions. So it must be the tool itself.&lt;/p&gt;

&lt;p&gt;After some more back and forth, the agent discovered that &lt;code&gt;create_batch_request&lt;/code&gt; - a completely different MCP tool - is the only way to update custom fields. The &lt;code&gt;update_task&lt;/code&gt; tool accepts the &lt;code&gt;custom_fields&lt;/code&gt; parameter without complaint, but the parameter isn't actually in the tool's schema. The tool silently drops it, updates everything else, and returns a success message.&lt;/p&gt;

&lt;p&gt;Maybe a small issue, if it happened only once.&lt;/p&gt;

&lt;p&gt;But my logs showed 16 silently failed attempts across 7 tasks. The same cycle every time: try, get "success," see nothing changed, investigate, try again, finally find a workaround.&lt;/p&gt;

&lt;p&gt;The agent kept hitting the same wall because the API never told it the wall existed. &lt;/p&gt;

&lt;p&gt;A crash would have been so much better - the agent would see the error and immediately try a different tool. Instead, it got a success response and had to figure out through downstream verification that the "successful" call hadn't been sucessfull after all.&lt;/p&gt;

&lt;p&gt;Each time a new agent instance worked on a task, it went through the same process, wasting ~93K tokens just to figure out that there is a problem - and how to solve it. Learning wasn't possible, because there was no error to learn from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Look at the Math
&lt;/h2&gt;

&lt;p&gt;Every number below comes directly from my session logs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wasted tokens per failed attempt&lt;/td&gt;
&lt;td&gt;~93,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average failed attempts per task&lt;/td&gt;
&lt;td&gt;2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per attempt (Claude Opus at $5/MTok)&lt;/td&gt;
&lt;td&gt;~$0.47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost per task&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$1.08&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now imagine a 5-person team with 10 tasks per person per day.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Timeframe&lt;/th&gt;
&lt;th&gt;Per person&lt;/th&gt;
&lt;th&gt;Team of 5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per day (10 tasks)&lt;/td&gt;
&lt;td&gt;$10.80&lt;/td&gt;
&lt;td&gt;$54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per month (22 working days)&lt;/td&gt;
&lt;td&gt;$238&lt;/td&gt;
&lt;td&gt;$1,190&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per year&lt;/td&gt;
&lt;td&gt;$2,856&lt;/td&gt;
&lt;td&gt;$14,280&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is one parameter, in one MCP tool, on a single workflow. And even that scales really fast.&lt;/p&gt;

&lt;p&gt;Two things are worth mentioning: this models the cost &lt;em&gt;before&lt;/em&gt; the workaround is discovered. Once you know to use &lt;code&gt;create_batch_request&lt;/code&gt;, the waste drops to zero. And it assumes every task hits the bug - which was true in my case, since every triage task needed custom field updates.&lt;/p&gt;

&lt;p&gt;The point isn't the exact dollar figure. It's that silent failures delay discovery - possibly indefinitely.&lt;/p&gt;

&lt;p&gt;A crash costs one attempt. The agent sees the error, adjusts, moves on. But silent acceptance costs multiple attempts, every time, until you realize there's a problem - if you realize it at all.&lt;/p&gt;

&lt;p&gt;In software engineering, there's a concept called "graceful degradation". But if your API - MCP server or not - accepts parameters it can't handle and returns success, you're not being graceful. You're being expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You can Do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you build APIs or MCP tools:&lt;/strong&gt; Add input validation. Reject or warn on unrecognized parameters. My data says that one check would have saved every token and every dollar in this story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you build agents:&lt;/strong&gt; Verify state after every state-changing call. Don't trust success responses - confirm the change actually happened. And make sure your agent surfaces anything it didn't expect.&lt;/p&gt;




&lt;p&gt;Some say we don't need to worry about architecture anymore, because AI agents are able to figure things out.&lt;/p&gt;

&lt;p&gt;But I think it's the opposite: Software architecture principles become more important with AI, not less.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is Part 1 of "The Inconsistency Tax" - a 3-part series on what happens when AI agents meet inconsistent APIs. Next: why these failures aren't random, and why "just wrapping an API in an MCP server" doesn't automatically make it agent-ready.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>From Local MCP Server to AWS Deployment in Two Commands - The Code-Only Version</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Mon, 26 Jan 2026 17:30:18 +0000</pubDate>
      <link>https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-code-only-5c4d</link>
      <guid>https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-code-only-5c4d</guid>
      <description>&lt;p&gt;In this walkthrough, I'll show you the simplest way to deploy an MCP server on AWS, using the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt; - and how to call it with your IAM credentials - no complicated OAuth setup required.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is a code-only walkthrough. For detailed explanations, see the &lt;a href="https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-ag4"&gt;full tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Python and AWS setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To follow this walkthrough, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python 3.11&lt;/a&gt; or higher,&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt; package manager (or pip),&lt;/li&gt;
&lt;li&gt;an &lt;a href="https://go.aws/3ZcwOK0" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;and the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; installed and configured.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run these commands to verify the prerequisites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;--version&lt;/span&gt;
uv &lt;span class="nt"&gt;--version&lt;/span&gt;     
aws &lt;span class="nt"&gt;--version&lt;/span&gt;
aws sts get-caller-identity &lt;span class="nt"&gt;--no-cli-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To troubleshoot, please follow the links above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AgentCore Starter Toolkit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aws.github.io/bedrock-agentcore-starter-toolkit/api-reference/cli.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock AgentCore Starter Toolkit&lt;/a&gt; will simplify the entire process to two commands, &lt;code&gt;agentcore configure&lt;/code&gt; and &lt;code&gt;agentcore deploy&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;To install it, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; bedrock-agentcore-starter-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Create a small MCP server
&lt;/h2&gt;

&lt;p&gt;Prepare a new Python project for the MCP server:&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;# Start with an empty folder for your project&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;mcp-on-agentcore-runtime
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-on-agentcore-runtime

&lt;span class="c"&gt;# Create and cd into a subdirectory for the MCP server&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;mcp-server
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-server

&lt;span class="c"&gt;# Initialize a new Python project and add the `mcp` package:&lt;/span&gt;
uv init &lt;span class="nt"&gt;--bare&lt;/span&gt;
uv add mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the MCP server&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;# Create a simple MCP server in `server.py`:&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; server.py
import random
from mcp.server.fastmcp import FastMCP

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def roll_d20(number_of_dice: int = 1) -&amp;gt; dict:
    """Rolls one or more 20-sided dice (d20)"""
    if number_of_dice &amp;lt; 1:
        return {
            "error": f"number_of_dice must be at least 1"
        }

    rolls = [random.randint(1, 20) for _ in range(number_of_dice)]
    return {
        "number_of_dice": number_of_dice,
        "rolls": rolls,
        "total": sum(rolls)
    }

def main():
    mcp.run(transport="streamable-http")

if __name__ == "__main__":
    main()
&lt;/span&gt;&lt;span class="no"&gt;
EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Test Locally
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In the first terminal:&lt;/strong&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;# Start the MCP server:&lt;/span&gt;
uv run server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In a second terminal:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;List the tools:&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;# Send a list tools request&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/mcp &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ 
           "jsonrpc": "2.0",
           "id": 1,
           "method": "tools/list"
         }'&lt;/span&gt;

&lt;span class="c"&gt;# Response:&lt;/span&gt;
&lt;span class="c"&gt;# event: message&lt;/span&gt;
&lt;span class="c"&gt;# data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{...}]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the tool:&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;# Send a tool call request&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/mcp &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
           "id": 1,
           "jsonrpc": "2.0",
           "method": "tools/call",
           "params": {
             "name": "roll_d20",
             "arguments": { "number_of_dice": 2 }
           }
         }'&lt;/span&gt;

&lt;span class="c"&gt;# Response:&lt;/span&gt;
&lt;span class="c"&gt;# event: message&lt;/span&gt;
&lt;span class="c"&gt;# data: {"jsonrpc":"2.0","id":1,"result":{"content":[{...}]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you see connection errors, ensure the server is running in the other terminal&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In the first terminal:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press &lt;code&gt;Ctrl/Cmd+C&lt;/code&gt; to stop the server&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Configure the deployment AWS
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate back up to the project root&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="c"&gt;# Configure the MCP server deployment.&lt;/span&gt;
&lt;span class="c"&gt;# This will use the account and region configured in the AWS CLI&lt;/span&gt;
agentcore configure &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; mcp-server/server.py &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--requirements-file&lt;/span&gt; mcp-server/pyproject.toml &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--disable-memory&lt;/span&gt; &lt;span class="nt"&gt;--disable-otel&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--non-interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--deployment-type&lt;/span&gt; container &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--protocol&lt;/span&gt; MCP &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--name&lt;/span&gt; my_mcp_server

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;
&lt;span class="c"&gt;# Config saved to: .../.bedrock_agentcore.yaml&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;

&lt;span class="c"&gt;# If you want to view the configuration, you can run:&lt;/span&gt;
agentcore status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Deploy to AWS
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Now you can launch the deployment process&lt;/span&gt;
agentcore deploy

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# Launching Bedrock AgentCore (codebuild mode - RECOMMENDED)&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;
&lt;span class="c"&gt;# Setting up AWS resources (ECR repository, execution roles)&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;
&lt;span class="c"&gt;# Deployment completed successfully&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single command orchestrates the entire deployment process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It creates an ECR Repository to store the container image,&lt;/li&gt;
&lt;li&gt;an IAM Role with permissions for the runtime to pull images from ECR, execute your container, and write logs to CloudWatch,&lt;/li&gt;
&lt;li&gt;and an IAM Role for CodeBuild to build the container and push it to ECR.&lt;/li&gt;
&lt;li&gt;Then it zips and uploads your server files to S3,&lt;/li&gt;
&lt;li&gt;runs the build process, pushes the image,&lt;/li&gt;
&lt;li&gt;and deploys the MCP server to AgentCore Runtime&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The server is now ready to accept MCP requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Test with the AWS CLI
&lt;/h2&gt;

&lt;p&gt;Get the MCP server ARN:&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;# Extract the server's ARN from `.bedrock_agentcore.yaml`:&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MCP_SERVER_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'agent_arn:'&lt;/span&gt; .bedrock_agentcore.yaml | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\"\'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# To see if it worked, type:&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"MCP_SERVER_ARN=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$MCP_SERVER_ARN&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List the tools:&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;# Create a file with the list tools request&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; list-tools-request.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/list"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# To list the tools, send the request to the AgentCore Runtime&lt;/span&gt;
&lt;span class="c"&gt;# Note that, even though this is called `invoke-agent-runtime`,&lt;/span&gt;
&lt;span class="c"&gt;# it will call the deployed MCP server.&lt;/span&gt;
aws bedrock-agentcore invoke-agent-runtime &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--agent-runtime-arn&lt;/span&gt; &lt;span class="nv"&gt;$MCP_SERVER_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--content-type&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--accept&lt;/span&gt; &lt;span class="s2"&gt;"application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-cli-pager&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; fileb://list-tools-request.json &lt;span class="se"&gt;\&lt;/span&gt;
    ./list-tools-output.txt

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;
&lt;span class="c"&gt;# mcpSessionId: [MCP Session ID]&lt;/span&gt;
&lt;span class="c"&gt;# runtimeSessionId: [AgentCore Runtime ID - same as MCP Session ID]&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;

&lt;span class="c"&gt;# View the results:&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;list-tools-output.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the tool:&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;# Create a file with the tool call request&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tool-call-request.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "roll_d20",
    "arguments": { 
      "number_of_dice": 2 
    }
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;aws bedrock-agentcore invoke-agent-runtime &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--agent-runtime-arn&lt;/span&gt; &lt;span class="nv"&gt;$MCP_SERVER_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--content-type&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--accept&lt;/span&gt; &lt;span class="s2"&gt;"application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-cli-pager&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; fileb://tool-call-request.json &lt;span class="se"&gt;\&lt;/span&gt;
     ./tool-call-output.txt

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;
&lt;span class="c"&gt;# mcpSessionId: [MCP Session ID]&lt;/span&gt;
&lt;span class="c"&gt;# runtimeSessionId: [AgentCore Runtime ID - same as MCP Session ID]&lt;/span&gt;
&lt;span class="c"&gt;# [...]&lt;/span&gt;

&lt;span class="c"&gt;# View the results:&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;tool-call-output.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We're using the AWS CLI because &lt;code&gt;curl&lt;/code&gt; won't work anymore. The endpoint is protected by AWS IAM and can only be called with valid credentials.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 6: Connect an AI Agent
&lt;/h2&gt;

&lt;p&gt;Extract the information required to build the MCP server URL:&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;# Extract the required information from `.bedrock_agentcore.yaml`:&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUNTIME_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'agent_id:'&lt;/span&gt; .bedrock_agentcore.yaml | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\"\'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'account:'&lt;/span&gt; .bedrock_agentcore.yaml | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\"\'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'region:'&lt;/span&gt; .bedrock_agentcore.yaml | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\"\'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# To see if it worked, type:&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RUNTIME_ID=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$RUNTIME_ID&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AWS_ACCOUNT_ID=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AWS_REGION=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prepare a new Python project for the AI agent:&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;# Create and cd into a subdirectory for the AI agent&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;agent
&lt;span class="nb"&gt;cd &lt;/span&gt;agent

&lt;span class="c"&gt;# Initialize a new Python project:&lt;/span&gt;
uv init &lt;span class="nt"&gt;--bare&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the MCP Proxy for AWS (this enables IAM-based auth for MCP - &lt;a href="https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o"&gt;quick intro to using it with other frameworks, like LangChain, Llamaindex, etc.&lt;/a&gt;) and the &lt;a href="https://bit.ly/strands-agents-sdk" rel="noopener noreferrer"&gt;Strands Agents SDK&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;uv add mcp-proxy-for-aws strands-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the agent:&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;# Create a simple MCP server in `agent.py`:&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; agent.py
from strands import Agent
from strands.tools.mcp import MCPClient
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client

runtime_id = "&lt;/span&gt;&lt;span class="nv"&gt;$RUNTIME_ID&lt;/span&gt;&lt;span class="sh"&gt;"
account_id = "&lt;/span&gt;&lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt;&lt;span class="sh"&gt;"
region = "&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="sh"&gt;"

def main():
    host = f"https://bedrock-agentcore.{region}.amazonaws.com"
    path = f"runtimes/{runtime_id}/invocations"
    query_params = f"qualifier=DEFAULT&amp;amp;accountId={account_id}"
    url = f"{host}/{path}?{query_params}"

    mcp_client_factory = lambda: aws_iam_streamablehttp_client(
        terminate_on_close=False,
        aws_service="bedrock-agentcore",
        aws_region=region,
        endpoint=url
    )

    prompt = "Roll 3 dice"

    with MCPClient(mcp_client_factory) as mcp_client:
        mcp_tools = mcp_client.list_tools_sync()
        agent = Agent(tools=mcp_tools)

        print(f"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;User: {prompt}")

        print("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;Agent:")
        agent(prompt)

        print()

if __name__ == "__main__":
    main()
&lt;/span&gt;&lt;span class="no"&gt;
EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run agent.py

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# User: Roll 3 dice&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# Agent:&lt;/span&gt;
&lt;span class="c"&gt;# The agent will call the roll_d20 tool and display the results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You may see a deprecation warning: &lt;code&gt;DeprecationWarning: Use 'streamable_http_client' instead&lt;/code&gt;. This warning can be safely ignored. The official MCP library recently renamed their client. By the time you read this, it may already be fixed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;When you're done experimenting, destroy the deployment to avoid ongoing costs:&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;# Navigate back to the project root&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="c"&gt;# This command removes all resources created during deployment&lt;/span&gt;
agentcore destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;If you learned something new, it would be great if you could like this post. For detailed explanations of each step, see the &lt;a href="https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-ag4"&gt;full tutorial&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>aws</category>
      <category>ai</category>
    </item>
    <item>
      <title>From Local MCP Server to AWS Deployment in Two Commands</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Thu, 22 Jan 2026 14:26:41 +0000</pubDate>
      <link>https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-ag4</link>
      <guid>https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-ag4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a full tutorial with detailed explanations. For a quick walkthrough, see the &lt;a href="https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-code-only-5c4d"&gt;code-only version&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;“What's the simplest way to deploy an MCP server on AWS?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last week a friend asked me this, and I realized: if you already have a local MCP server, it's really just two CLI commands - &lt;code&gt;agentcore configure&lt;/code&gt; and &lt;code&gt;agentcore launch&lt;/code&gt;. The entire workflow - from local development to production - takes just a few minutes.&lt;/p&gt;

&lt;p&gt;Let me show you how.&lt;/p&gt;

&lt;p&gt;To get started, we'll prepare a small example MCP server and test it locally to make sure it works. Once that's done, we're going to directly deploy it to &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock AgentCore Runtime&lt;/a&gt;, the serverless hosting environment for Agents and MCP servers on AWS. &lt;/p&gt;

&lt;p&gt;In just a few minutes you'll have a working, scalable, and secure MCP server on AWS, directly accessible with &lt;a href="https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o"&gt;valid IAM credentials&lt;/a&gt; - no complicated OAuth setup needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AgentCore Runtime?
&lt;/h2&gt;

&lt;p&gt;Before we get started, let's have a quick look at AgentCore Runtime and why it is such an obvious choice.&lt;/p&gt;

&lt;p&gt;Many MCP servers need to maintain session state between requests while keeping the individual sessions strictly isolated. Traditional serverless options like AWS Lambda provide session isolation, but they are stateless, which means you'd have to manage sessions yourself (typically via DynamoDB or similar). Container services like ECS can maintain state, but you'd have to implement session isolation yourself or run a separate container for every single session - which can become quite costly over time.&lt;/p&gt;

&lt;p&gt;AgentCore Runtime solves both problems: it's serverless (you pay only for processing time), but unlike Lambda, it maintains session state across multiple calls. Every session runs in a completely isolated execution environment. It's specifically designed for agent workloads and handles the infrastructure automatically.&lt;/p&gt;

&lt;p&gt;And with that, let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow this tutorial, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python 3.11&lt;/a&gt; or higher&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;Astral uv&lt;/a&gt; package manager (or pip, but if you aren't using uv yet, I &lt;em&gt;really&lt;/em&gt; recommend switching)&lt;/li&gt;
&lt;li&gt;An AWS account with Bedrock AgentCore access (&lt;a href="https://go.aws/3ZcwOK0" rel="noopener noreferrer"&gt;there's a free tier&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; installed and configured&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create Your MCP Server
&lt;/h2&gt;

&lt;p&gt;Let's start with a simple dice-rolling server to demonstrate the deployment process. We'll use FastMCP, which provides a decorator-based API for building MCP servers quickly.&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;# Setup your project directory ...&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-project 

&lt;span class="c"&gt;# ... and a subdirectory for the server&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-project/mcp-server

&lt;span class="c"&gt;# Then navigate to the subdirectory an initialize with uv&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project/mcp-server
uv init &lt;span class="nt"&gt;--bare&lt;/span&gt;
uv add mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uv init --bare&lt;/code&gt; command creates a minimal project structure without interactive prompts. This gives us a clean &lt;code&gt;pyproject.toml&lt;/code&gt; for dependency management.&lt;/p&gt;

&lt;p&gt;Here's what we're building: a simple dice-rolling MCP server that demonstrates the deployment workflow.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;server.py&lt;/code&gt; inside the &lt;code&gt;mcp-server&lt;/code&gt; directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mcp-server/server.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stateless_http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;roll_d20&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_dice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Rolls one or more 20-sided dice (d20)&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number_of_dice&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number_of_dice must be at least 1, got: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number_of_dice&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;rolls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_dice&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rolls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number_of_dice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;number_of_dice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rolls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rolls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;streamable-http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastMCP initialization:&lt;/strong&gt; The &lt;code&gt;host="0.0.0.0"&lt;/code&gt; binds to all network interfaces, which is necessary for containerized deployments. The &lt;code&gt;stateless_http=True&lt;/code&gt; flag is critical - it tells FastMCP to use HTTP-based transport instead of maintaining persistent connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool registration:&lt;/strong&gt; The &lt;code&gt;@mcp.tool()&lt;/code&gt; decorator automatically registers your function as an MCP tool. The function's docstring becomes the tool description, and type hints define the input schema. MCP clients will discover this tool and can invoke it with structured arguments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transport:&lt;/strong&gt; &lt;code&gt;mcp.run(transport="streamable-http")&lt;/code&gt; starts the server using the streamable HTTP transport. This is the standard MCP transport for HTTP-based deployments and is compatible with AgentCore Runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Test Locally
&lt;/h2&gt;

&lt;p&gt;Before deploying, let's verify the server works. Testing locally helps catch issues early and ensures your server logic is correct before dealing with deployment complexities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the MCP server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO:     Started server process [55952]
INFO:     Waiting for application startup.
StreamableHTTP session manager started
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastMCP uses Uvicorn under the hood, which is why you see Uvicorn logs. The server is now listening on port 8000 and ready to accept MCP requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  List the available tools
&lt;/h3&gt;

&lt;p&gt;In another terminal, test the server using the MCP protocol. MCP uses JSON-RPC 2.0 over HTTP, so we can test it without an MCP client by sending JSON-RPC requests directly using &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tools/list&lt;/code&gt; method is a standard MCP method that returns all available tools. The heredoc syntax (&lt;code&gt;&amp;lt;&amp;lt; 'EOF'&lt;/code&gt;) lets us inline the JSON payload without creating separate files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/mcp &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; @- &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the &lt;code&gt;roll_d20&lt;/code&gt; tool in the response with its description and input schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Send a tool call request
&lt;/h3&gt;

&lt;p&gt;Now let's call the tool. The &lt;code&gt;tools/call&lt;/code&gt; method invokes a specific tool with the provided arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/mcp &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; @- &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "roll_d20",
    "arguments": {
      "number_of_dice": 2
    }
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response will contain the tool's return value - in this case, the dice rolls and total.&lt;/p&gt;

&lt;p&gt;Stop the local server with &lt;code&gt;CTRL+C&lt;/code&gt; when done. This local testing confirms your server works correctly before we package it for deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Configure for AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;The Bedrock AgentCore Starter Toolkit handles the deployment configuration. It generates Dockerfiles, IAM roles, and all the infrastructure code needed to deploy your server. Install it in your project's root directory:&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;# Navigate to the project root&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="c"&gt;# Initialize a new Python project&lt;/span&gt;
uv init &lt;span class="nt"&gt;--bare&lt;/span&gt;
uv add bedrock-agentcore-starter-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're creating a new project at the project root because the toolkit needs to manage the deployment configuration separately from the MCP server. This separation keeps the MCP server code clean and makes it easier to manage multiple deployments.&lt;/p&gt;

&lt;p&gt;Configure your agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run agentcore configure &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; mcp-server/server.py &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--requirements-file&lt;/span&gt; mcp-server/pyproject.toml &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--disable-memory&lt;/span&gt; &lt;span class="nt"&gt;--disable-otel&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--non-interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--deployment-type&lt;/span&gt; container &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--protocol&lt;/span&gt; MCP &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--name&lt;/span&gt; my_mcp_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand each parameter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--entrypoint&lt;/code&gt; points to your server's main file. AgentCore runs this inside the container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--requirements-file&lt;/code&gt; tells the toolkit where to find dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--disable-memory --disable-otel&lt;/code&gt; disables optional features. Memory persists state across sessions (not needed for MCP servers). OTEL enables observability. We're disabling both to keep this simple.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--non-interactive&lt;/code&gt; skips interactive CLI prompts and uses defaults.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--deployment-type container&lt;/code&gt; packages your server as a container image using CodeBuild - no local Docker required.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--protocol MCP&lt;/code&gt; tells AgentCore this is an MCP server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name&lt;/code&gt; sets the MCP server name.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In some cases, you may see warnings about container engines or platform mismatches. These are safe to ignore - when using &lt;code&gt;--deployment-type container&lt;/code&gt;, AgentCore will use CodeBuild for cloud-based builds. CodeBuild can run on ARM64 (the architecture AgentCore Runtime uses), so even if your local machine is x86_64, the cloud build will work correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The configuration creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.bedrock_agentcore.yaml&lt;/code&gt; - The main configuration file that stores all deployment settings. Be very careful when editing this directly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.bedrock_agentcore/my_mcp_server/Dockerfile&lt;/code&gt; - The container definition that packages your server. The toolkit generates this based on your entrypoint and dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp-server/.dockerignore&lt;/code&gt; - Build exclusions to keep the container image small.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Deploy to AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;Now let's deploy the server to AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run agentcore launch &lt;span class="nt"&gt;--agent&lt;/span&gt; my_mcp_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single command orchestrates the entire deployment process.&lt;/p&gt;

&lt;p&gt;Here's what happens under the hood:&lt;/p&gt;

&lt;p&gt;1: &lt;strong&gt;ECR Repository Creation&lt;/strong&gt; - Amazon Elastic Container Registry (ECR) stores your container images. The toolkit automatically creates a new repository.&lt;/p&gt;

&lt;p&gt;2: &lt;strong&gt;IAM Role Setup&lt;/strong&gt; - The toolkit creates two IAM roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Execution Role&lt;/strong&gt; - Grants permissions for the runtime to pull images from ECR, execute your container, and write logs to CloudWatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeBuild Role&lt;/strong&gt; - Allows CodeBuild to build your container and push it to ECR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3: &lt;strong&gt;Container Build&lt;/strong&gt; - CodeBuild builds your container in the cloud. This is why you don't need Docker locally - CodeBuild handles the build process.&lt;/p&gt;

&lt;p&gt;The build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses the generated Dockerfile&lt;/li&gt;
&lt;li&gt;Installs dependencies from your &lt;code&gt;pyproject.toml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Packages your server code&lt;/li&gt;
&lt;li&gt;Creates an ARM64 image (AgentCore Runtime's architecture)&lt;/li&gt;
&lt;li&gt;Pushes the image to your ECR repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4: &lt;strong&gt;Runtime Deployment&lt;/strong&gt; - AgentCore Runtime creates a new runtime instance using your container image.&lt;/p&gt;

&lt;p&gt;The runtime is now ready to accept MCP requests.&lt;/p&gt;

&lt;p&gt;The build takes about 25-30 seconds. When complete, you'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╭────────────────────────────── Deployment Success ───────────────────────────────╮
│ Agent Details:                                                                  │
│ Agent Name: my_mcp_server                                                       │
│ Agent ARN: arn:aws:bedrock-agentcore:REGION:ACCOUNT_ID:runtime/RUNTIME_ID       │
│ ...                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────╯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note down the the following information - you'll need it to connect to your server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent ARN&lt;/strong&gt; - The Full ARN of your deployed server. This uniquely identifies your runtime across all AWS accounts and regions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then extract the following information from the ARN:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Region&lt;/strong&gt; - Directly after &lt;code&gt;arn:aws:bedrock-agentcore:&lt;/code&gt;, e.g. &lt;code&gt;us-west-2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Account ID&lt;/strong&gt; - A 12-digit number, right next to the region. Region and account ID are required for IAM-based authentication when connecting clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime ID&lt;/strong&gt; - The last part of the ARN, directly following &lt;code&gt;runtime/&lt;/code&gt;, e.g., &lt;code&gt;my_mcp_server-abcde12345&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ARN format is: &lt;code&gt;arn:aws:bedrock-agentcore:{region}:{account-id}:runtime/{runtime-id}&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Test the Deployed Server
&lt;/h2&gt;

&lt;p&gt;Test your deployed server using the AWS CLI. The &lt;code&gt;invoke-agent-runtime&lt;/code&gt; command sends requests to your deployed server, similar to how we tested locally with curl.&lt;/p&gt;

&lt;p&gt;We'll use process substitution (&lt;code&gt;&amp;lt;(...)&lt;/code&gt;) to inline the JSON payloads without creating separate files:&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;# Set your server ARN&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SERVER_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:REGION:ACCOUNT:runtime/RUNTIME_ID"&lt;/span&gt;

&lt;span class="c"&gt;# List available tools&lt;/span&gt;
aws bedrock-agentcore invoke-agent-runtime &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--agent-runtime-arn&lt;/span&gt; &lt;span class="nv"&gt;$SERVER_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--content-type&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--accept&lt;/span&gt; &lt;span class="s2"&gt;"application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; fileb://&amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; list-tools.txt

&lt;span class="c"&gt;# Call the tool&lt;/span&gt;
aws bedrock-agentcore invoke-agent-runtime &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--agent-runtime-arn&lt;/span&gt; &lt;span class="nv"&gt;$SERVER_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--content-type&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--accept&lt;/span&gt; &lt;span class="s2"&gt;"application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; fileb://&amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "roll_d20",
    "arguments": {
      "number_of_dice": 2
    }
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; call-tool.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What you'll see:&lt;/strong&gt; The AWS CLI opens a pager showing HTTP response metadata (status codes, headers, including the MCP session ID). Press &lt;code&gt;q&lt;/code&gt; to exit. The actual JSON-RPC response is written to the output files - check &lt;code&gt;list-tools.txt&lt;/code&gt; for the tool list and &lt;code&gt;call-tool.txt&lt;/code&gt; for the dice roll results.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice that the requests are identical to your local tests - the MCP request is the same whether running locally or on AgentCore Runtime. The only difference is the endpoint and authentication method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 6: Connect an Agent (Optional)
&lt;/h2&gt;

&lt;p&gt;Now we can connect an AI agent to the deployed server using the official &lt;a href="https://github.com/aws/mcp-proxy-for-aws" rel="noopener noreferrer"&gt;IAM MCP client&lt;/a&gt;, which handles MCP request creation and IAM authentication automatically. This demonstrates how real-world agents would connect to your server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We're going to use the &lt;a href="https://github.com/strands-ai/strands-agents" rel="noopener noreferrer"&gt;Strands Agents SDK&lt;/a&gt; as an example framework, but this pattern works with any agent framework that supports MCP. Check out this post, where I show you &lt;a href="https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o"&gt;how to use the IAM MCP client with LangChain, LlamaIndex, and Microsoft's Agent Framework&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While still in the project root, add the following packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add mcp-proxy-for-aws strands-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mcp-proxy-for-aws&lt;/code&gt; is an official AWS wrapper around the standard MCP client. The standard client expects OAuth, but this one signs requests with AWS credentials using SigV4, making it compatible with IAM authentication on AWS. The &lt;code&gt;strands-agents&lt;/code&gt; package provides the agent framework we'll use for this example.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;test_agent.py&lt;/code&gt; and add the runtime ID, AWS account ID, and region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using Strands Agents SDK for this example
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.tools.mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;

&lt;span class="c1"&gt;# Set your MCP server details from Step 4
&lt;/span&gt;&lt;span class="n"&gt;RUNTIME_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[YOUR RUNTIME ID]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[YOUR ACCOUNT ID]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[YOUR AWS REGION]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Build the MCP server URL
&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://bedrock-agentcore.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.amazonaws.com/runtimes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RUNTIME_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/invocations?qualifier=DEFAULT&amp;amp;accountId=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Initializing MCP client with IAM-based auth for:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mcp_client_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;aws_iam_streamablehttp_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;terminate_on_close&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcp_client_factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mcp_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_tools_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;query_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What tools do you have available?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Q: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query_1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;query_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roll three dice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Q: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query_2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand the key parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endpoint URL construction:&lt;/strong&gt; The URL follows AgentCore's invocation endpoint pattern. The &lt;code&gt;qualifier=DEFAULT&lt;/code&gt; parameter specifies which endpoint version to use (AgentCore supports multiple versions for gradual rollouts).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client factory pattern:&lt;/strong&gt; The Strands Agents SDK's &lt;code&gt;MCPClient&lt;/code&gt; expects a factory function (a lambda that returns a client). This allows the framework to manage connection lifecycle - creating new connections when needed and cleaning them up properly.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;aws_iam_streamablehttp_client&lt;/code&gt; function creates an MCP client that automatically signs all requests with your AWS credentials as well as the region and AWS service the server is hosted in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool discovery:&lt;/strong&gt; &lt;code&gt;mcp_client.list_tools_sync()&lt;/code&gt; fetches all available tools from your server. The agent then receives these tools and can invoke them based on user queries. When the agent sees "Roll three dice," it recognizes this matches the &lt;code&gt;roll_d20&lt;/code&gt; tool and calls it with &lt;code&gt;number_of_dice=3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session management:&lt;/strong&gt; The &lt;code&gt;terminate_on_close=False&lt;/code&gt; parameter tells the client not to explicitly terminate the MCP session when closing. AgentCore Runtime manages session lifecycle automatically, so this isn't necessary.&lt;/p&gt;

&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run test_agent.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You may see a deprecation warning: &lt;code&gt;DeprecationWarning: Use 'streamable_http_client' instead.&lt;/code&gt; The official MCP client library recently renamed their HTTP client, and the IAM client is being updated to match. This warning can be safely ignored. By the time you read this, it may already be fixed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent will connect using your AWS credentials and use the deployed MCP server's tools. &lt;/p&gt;

&lt;p&gt;This demonstrates the complete flow: your server is deployed, accessible via IAM, and integrated with an AI agent framework. &lt;/p&gt;

&lt;p&gt;The same pattern works with other frameworks like LangChain or LlamaIndex - &lt;a href="https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o"&gt;just swap the agent framework&lt;/a&gt; while keeping the IAM MCP client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Cleanup
&lt;/h2&gt;

&lt;p&gt;When you're done experimenting, destroy the deployment to avoid ongoing costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run agentcore destroy &lt;span class="nt"&gt;--agent&lt;/span&gt; my_mcp_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command removes all resources created during deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AgentCore runtime&lt;/strong&gt; - The running MCP server instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR repository and images&lt;/strong&gt; - Container images stored in ECR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeBuild project&lt;/strong&gt; - The build configuration (builds are ephemeral, but the project definition persists)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM roles&lt;/strong&gt; - Both the execution role and CodeBuild role&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 artifacts&lt;/strong&gt; - Build artifacts stored in S3 buckets created by CodeBuild&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The toolkit is careful about cleanup - it won't delete resources that were created outside the toolkit. You'll be prompted to confirm before deletion, as this operation is irreversible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We've Learned
&lt;/h2&gt;

&lt;p&gt;This tutorial covered the complete deployment workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Development&lt;/strong&gt; - Build and test MCP servers locally using FastMCP. Testing locally helps catch issues before deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt; - The AgentCore Starter Toolkit generates all infrastructure code (Dockerfiles, IAM roles, etc.) from the configuration. This abstraction lets you focus on your server logic rather than deployment details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud Deployment&lt;/strong&gt; - AgentCore Runtime uses CodeBuild for container builds, so you don't need Docker locally. The runtime automatically manages state, session isolation, and infrastructure - you just provide the container image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; - The AWS CLI's &lt;code&gt;invoke-agent-runtime&lt;/code&gt; command lets you test deployed servers using the same MCP protocol as local testing. IAM authentication happens automatically via your AWS credentials.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration&lt;/strong&gt; - The IAM MCP client enables any agent framework to connect to AgentCore-hosted MCP servers using AWS credentials, eliminating the need for OAuth infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrate with other frameworks&lt;/strong&gt; - Check out &lt;a href="https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o"&gt;No OAuth Required: An MCP Client For AWS IAM&lt;/a&gt; to learn how to connect LangChain, LlamaIndex, or other agent frameworks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy via AgentCore Gateway&lt;/strong&gt; - Use &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock AgentCore Gateway&lt;/a&gt; to turn existing APIs into MCP servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore more complex MCP servers&lt;/strong&gt; - Add multiple tools, resources, and prompts to your server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock AgentCore Runtime&lt;/a&gt; provides serverless execution with stateful sessions. You pay only for processing time, even though your server maintains its state between requests. This session-based model makes it the perfect for agentic workloads, including MCP.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you learned something new, it would be great if you could like this post. For a quick walkthrough without much explanation, see the &lt;a href="https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-code-only-5c4d"&gt;code-only version&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>aws</category>
    </item>
    <item>
      <title>Yay, I've been featured on the top 7 for the very first time! Thanks DEV ❤️</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Tue, 25 Nov 2025 20:08:54 +0000</pubDate>
      <link>https://dev.to/dennistraub/yay-ive-been-featured-on-the-top-7-for-the-very-first-time-thanks-dev-4i04</link>
      <guid>https://dev.to/dennistraub/yay-ive-been-featured-on-the-top-7-for-the-very-first-time-thanks-dev-4i04</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-e4i" class="crayons-story__hidden-navigation-link"&gt;Top 7 Featured DEV Posts of the Week&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/devteam"&gt;
            &lt;img alt="The DEV Team logo" 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%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/jess" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" alt="jess profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jess" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jess Lee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jess Lee
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3055576" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jess" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jess Lee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/devteam" class="crayons-story__secondary fw-medium"&gt;The DEV Team&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-e4i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 25 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-e4i" id="article-link-3055576"&gt;
          Top 7 Featured DEV Posts of the Week
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/top7"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;top7&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-e4i" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;31&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-e4i#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              6&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>top7</category>
      <category>discuss</category>
    </item>
    <item>
      <title>No OAuth Required: An MCP Client For AWS IAM</title>
      <dc:creator>Dennis Traub</dc:creator>
      <pubDate>Tue, 18 Nov 2025 19:17:50 +0000</pubDate>
      <link>https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o</link>
      <guid>https://dev.to/aws/no-oauth-required-an-mcp-client-for-aws-iam-k1o</guid>
      <description>&lt;p&gt;When Anthropic published the &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;&lt;/a&gt;, I immediately started experimenting with deployment options on AWS:&lt;/p&gt;

&lt;p&gt;First, I tried running MCP servers as AWS Lambda functions. A great solution in terms of simplicity and cost, but it also meant I had to manually manage session state across invocations.&lt;/p&gt;

&lt;p&gt;Next, I deployed them as containers on Amazon ECS, which immediately solved session state management for me, but I had to pay for a running server, even when it sat idle.&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;&lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=ef8ca202-7071-4ec3-aff2-78ef3bddfabf&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt;&lt;/strong&gt; was released, which turned out to be exactly the solution I was looking for: A serverless runtime, automatically handling session state, while only charging for the actual processing time of MCP requests - the best of both worlds. And, as a native AWS service, it supports IAM, which perfectly fits into my existing stack.&lt;/p&gt;

&lt;p&gt;But when I tried to actually connect an agent to a server, I ran into a wall:&lt;/p&gt;

&lt;p&gt;No matter which agent framework I used, they all expected OAuth. This meant setting up a separate Cognito stack just to connect an agent to its tools. But I'd like to use IAM credentials like I do for everything else on AWS.&lt;/p&gt;

&lt;p&gt;This is why I built the &lt;strong&gt;MCP client for IAM&lt;/strong&gt;, a drop-in replacement that can be used with any Python framework using the official MCP SDK's &lt;code&gt;streamablehttp_client&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No Cognito setup, no token management, no custom signing code. This client automatically signs MCP requests with your agent's existing AWS credentials.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to use it with popular AI agent frameworks like LangChain, Strands Agents, LlamaIndex, and Microsoft's Agent Framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Need to Get Started
&lt;/h2&gt;

&lt;p&gt;The MCP client for IAM is open source and part of the official &lt;strong&gt;MCP Proxy for AWS&lt;/strong&gt;. You can find it on &lt;a href="https://github.com/aws/mcp-proxy-for-aws" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://pypi.org/project/mcp-proxy-for-aws/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you already have an MCP server with IAM auth on AWS, all you need is to install the package in your client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;mcp-proxy-for-aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Now you can replace MCP's default &lt;code&gt;streamablehttp_client&lt;/code&gt; with the following import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have an MCP server on AWS yet, check out this quick tutorial: &lt;a href="https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-ag4"&gt;From Local MCP Server to AWS Deployment in Two Commands&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration Patterns
&lt;/h2&gt;

&lt;p&gt;Different frameworks manage MCP connections differently. The client supports two integration patterns to work with the following approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Client Factory Integration
&lt;/h3&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%2F6h460exebrfqdjxtg593.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%2F6h460exebrfqdjxtg593.png" alt=" " width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use with:&lt;/strong&gt; Frameworks that accept a factory function returning an MCP client, like the Strands Agents SDK and Microsoft's Agent Framework. The &lt;code&gt;aws_iam_streamablehttp_client&lt;/code&gt; is passed as a factory to the framework, which then handles the connection lifecycle internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example - Strands Agents:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;

&lt;span class="n"&gt;mcp_client_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;aws_iam_streamablehttp_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# The URL of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# The region of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;  &lt;span class="c1"&gt;# The underlying AWS service, e.g. "bedrock-agentcore"
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcp_client_factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_tools_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example - Microsoft's Agent Framework:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;

&lt;span class="n"&gt;mcp_client_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;aws_iam_streamablehttp_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# The URL of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# The region of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;  &lt;span class="c1"&gt;# The underlying AWS service, e.g. "bedrock-agentcore"
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mcp_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MCPStreamableHTTPTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MCP Tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_mcp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcp_client_factory&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Direct MCP Session Integration
&lt;/h3&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%2F64mh2ugqp63c5set1zzj.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%2F64mh2ugqp63c5set1zzj.png" alt=" " width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use with:&lt;/strong&gt; Frameworks that require direct access to the MCP sessions, like LangChain and LlamaIndex. The &lt;code&gt;aws_iam_streamablehttp_client&lt;/code&gt; provides the authenticated transport streams, which are then used to create an MCP &lt;code&gt;ClientSession&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example - LangChain:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;

&lt;span class="n"&gt;mcp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;aws_iam_streamablehttp_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# The URL of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# The region of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;  &lt;span class="c1"&gt;# The underlying AWS service, e.g. "bedrock-agentcore"
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id_callback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mcp_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;load_mcp_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_langchain_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example - LlamaIndex:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;

&lt;span class="n"&gt;mcp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;aws_iam_streamablehttp_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# The URL of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# The region of the MCP server
&lt;/span&gt;    &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;  &lt;span class="c1"&gt;# The underlying AWS service, e.g. "bedrock-agentcore"
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id_callback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mcp_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;McpToolSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_tool_list_async&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReActAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Running Examples
&lt;/h2&gt;

&lt;p&gt;Explore complete working examples for different frameworks in the &lt;a href="https://github.com/aws/mcp-proxy-for-aws/tree/main/examples/mcp-client" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benefits of Using this Client
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Simple local development:&lt;/strong&gt; &lt;br&gt;
Use your AWS CLI credentials directly. No Cognito setup, no token management, no separate auth infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard AWS patterns:&lt;/strong&gt; &lt;br&gt;
Cross-account access via IAM role assumption works the same way it does for every other AWS service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM-based access control:&lt;/strong&gt; &lt;br&gt;
Control which agents can connect to which MCP servers using IAM policies. No changes to server code required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Universal compatibility across AWS:&lt;/strong&gt; &lt;br&gt;
The MCP client for IAM works with AgentCore Gateway, AgentCore Runtime, and any other AWS service exposing MCP over HTTPS with IAM authentication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check out this simple way to &lt;a href="https://dev.to/aws/from-local-mcp-server-to-aws-deployment-in-two-commands-ag4"&gt;deploy your own MCP server on AWS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Have a look at the &lt;a href="https://github.com/aws/mcp-proxy-for-aws/tree/main/examples/mcp-client" rel="noopener noreferrer"&gt;complete examples on GitHub&lt;/a&gt; for working code with a selection of frameworks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Further resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The package on &lt;a href="https://pypi.org/project/mcp-proxy-for-aws/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; - Installation and changelog&lt;/li&gt;
&lt;li&gt;The source on &lt;a href="https://github.com/aws/mcp-proxy-for-aws" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - Contribute or report issues&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;What MCP servers are you planning to build? I'd love to hear about your use cases in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
