<?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>Forem: Ashita Prasad</title>
    <description>The latest articles on Forem by Ashita Prasad (@ashita).</description>
    <link>https://forem.com/ashita</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%2F850613%2Fb2250c8e-3ebf-48c5-a975-cce5ec391d4c.jpg</url>
      <title>Forem: Ashita Prasad</title>
      <link>https://forem.com/ashita</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ashita"/>
    <language>en</language>
    <item>
      <title>How I built MCP Apps based Sales Analytics Agentic UI &amp; deployed it on Amazon Bedrock AgentCore</title>
      <dc:creator>Ashita Prasad</dc:creator>
      <pubDate>Wed, 01 Apr 2026 05:38:59 +0000</pubDate>
      <link>https://forem.com/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i</link>
      <guid>https://forem.com/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Learn how to add interactive forms, charts, visualization &amp;amp; PDF viewer - natively in your AI Agent chat window using MCP Apps.&lt;/p&gt;

&lt;p&gt;AI Agents are getting smarter with each passing day. But, their interfaces? Not so much. But, what if there is a way to turn the AI chat from a place where you converse into a place where you can actually work? &lt;/p&gt;

&lt;p&gt;That's where MCP Apps, an extension of Model Context Protocol, comes to our rescue. &lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/ashita/a-practical-guide-to-building-mcp-apps-1bfm"&gt;previous article&lt;/a&gt;, we explored the foundational building blocks of MCP Apps in detail and witnessed how it provides a way to deliver rich, bidirectional UIs. Through a series of minimal examples, we covered various concepts such as the handshake protocol and host-aware theming, to CSP policies and calling tools from within an iframe.&lt;/p&gt;

&lt;p&gt;Now, it's time to stitch those pieces together into something real.&lt;/p&gt;

&lt;p&gt;In this article, we'll walk through a complete, production-style MCP Apps chatflow (&lt;em&gt;chat + workflow&lt;/em&gt;) and build &lt;strong&gt;an interactive sales analytics UI&lt;/strong&gt; where a user can select sales region, pick a metric like revenue or conversion rate, fetch live data, visualize it with charts, and create &amp;amp; download a PDF report - &lt;strong&gt;all without ever leaving the AI Agent chat window&lt;/strong&gt;. We will also learn how to deploy the developed TypeScript MCP server powering our sales analytics MCP Apps on &lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=2c88fb57-1294-4a6d-8290-a7dfb773a582&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find the entire source code of this article in the Github repo link provided below 👇&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ashitaprasad" rel="noopener noreferrer"&gt;
        ashitaprasad
      &lt;/a&gt; / &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow" rel="noopener noreferrer"&gt;
        sample-mcp-apps-chatflow
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Sample MCP Apps Chatflow&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Sample MCP server that renders interactive sales analytics UIs inside an MCP Apps-compatible chat client. It includes a sales metric selector, chart-based visualization, and PDF report generation.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Articles&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This project is part of the MCP Apps article series published on dev.to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ashita/a-practical-guide-to-building-mcp-apps-1bfm" rel="nofollow"&gt;Part 1: How I render interactive UI in my AI Agent chatflows using MCP Apps&lt;/a&gt; - Covers the core architectural patterns for declaring UI resources, practical design principles, and how to handle sandboxed host–server communication.&lt;/li&gt;
&lt;li&gt;
&lt;a href=""&gt;Part 2: How I built MCP Apps based Sales Analytics Agentic UI &amp;amp; deployed it on Amazon Bedrock AgentCore&lt;/a&gt; - Walks through a complete, production-style MCP Apps chatflow (chat + workflow) and build an interactive sales analytics UI where a user can select sales region, pick a metric like revenue or conversion rate, fetch live data, visualize it with charts, and create &amp;amp; download a PDF report - all without…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The Problem: When chat isn't enough!
&lt;/h2&gt;

&lt;p&gt;It all started when my friend, who is a sales analyst, started exploring AI Agents after reading everywhere that agents can unlock productivity by automating workflows and are also capable of performing data analysis.&lt;/p&gt;

&lt;p&gt;He prepared sales analytics reports on a monthly and quarterly basis and felt AI Agents could accelerate this workflow greatly. He started his Agentic journey by uploading CSVs, interacting with the AI Agent &amp;amp; assigning it tasks. The agent would frequently ask him clarifying questions, like the required sales metrics, sales regions he was interested in and more follow-up questions. Often, the AI agent would assume certain parameters and produce random results which meant he had to start the entire workflow all over again.&lt;/p&gt;

&lt;p&gt;Although the resulting analysis was accurate, but from the presentation standpoint a simple markdown result was quite underwhelming. After discussing his entire Agentic Experience (AX), I realised that the core friction was due to the &lt;strong&gt;limitations of plain chat&lt;/strong&gt;. While my friend kept answering and re-prompting, the model kept guessing and re-analyzing. &lt;strong&gt;The experience never quite graduated from "assistant response" to an actual "working interface"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And, MCP Apps solved exactly this gap!&lt;/p&gt;

&lt;p&gt;Instead of returning only text, the server returns a rich, interactive HTML interface rendered directly inside the chat which lets the user &lt;em&gt;drive&lt;/em&gt; the experience like an application, not just an answer.&lt;/p&gt;

&lt;p&gt;Let's see how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A Chatflow powered by MCP Apps
&lt;/h2&gt;

&lt;p&gt;An end-to-end demo of the complete chatflow is shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv259jsabz68cdyshof3.gif" 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%2Fvv259jsabz68cdyshof3.gif" alt="End-to-end Sales Analytics Chatflow" width="640" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our chatflow involves &lt;strong&gt;three MCP Apps&lt;/strong&gt; (UI resources) orchestrated by &lt;strong&gt;four tools&lt;/strong&gt;. Each MCP App is a self-contained HTML page served by the MCP server, running in a sandboxed iframe, communicating with the host via &lt;code&gt;postMessage&lt;/code&gt; JSON-RPC.&lt;/p&gt;

&lt;p&gt;The new Agentic experience (AX) unfolds like a real workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User asks for sales insights.&lt;/li&gt;
&lt;li&gt;AI agent opens a Sales Metric Selector MCP App which allows user to easily select the sales metric, reporting period, reporting year &amp;amp; sales regions, instead of forcing the user to specify everything in text.&lt;/li&gt;
&lt;li&gt;On clicking Submit, the app fetches data through an internal MCP tool and adds it as a structured JSON data in the context.&lt;/li&gt;
&lt;li&gt;With the relevant data added in context, user can prompt to generate a visualization or prepare a PDF report.&lt;/li&gt;
&lt;li&gt;Upon requesting for a visualization, the Sales Visualization MCP App is rendered which presents the entire data in an insightful &amp;amp; interactive visualization dashboard.&lt;/li&gt;
&lt;li&gt;Upon requesting for a PDF report, the AI agent triggers a tool which creates a PDF report server side, and then displays it via a PDF Report Viewer MCP App. The MCP app displays the pdf in the form of an interactive PDF and allows the user to download the report.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most important thing about this whole new experience is not just that these steps happen, but they happen &lt;strong&gt;inside one continuous chat experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let us now go through the individual tools and take a look at some of their salient features:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;select-sales-metric&lt;/code&gt; tool
&lt;/h3&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/7f8577249d07e81715400ff6a7325e9a1e622928/src/index.ts#L31-L74" rel="noopener noreferrer"&gt;Tool Registration&lt;/a&gt; and &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/main/src/ui/sales-form.ts" rel="noopener noreferrer"&gt;MCP App&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshet1uvc3shcakrv9mdh.gif" 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%2Fshet1uvc3shcakrv9mdh.gif" alt="Select Data" width="720" height="973"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where the interactive chatflow begins. When the user prompts - "get sales data", instead of asking the user to specify every parameter via text, the agent calls this tool. VS Code (Host) then reads the linked resource and renders a rich HTML form with dropdown menus, toggle buttons, search region &amp;amp; multi-select state cards inside an iframe.&lt;/p&gt;

&lt;p&gt;The Sales metric selector MCP App resides in &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/main/src/ui/sales-form.ts" rel="noopener noreferrer"&gt;&lt;code&gt;sales-form.ts&lt;/code&gt;&lt;/a&gt;, where server-side data such as the list of valid sales regions (Indian states), sales metric and top sales regions are serialized directly into the HTML template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;metricSelect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;metricOptions&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/select&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;
&lt;span class="p"&gt;..&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indianStates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indianStates&lt;/span&gt;&lt;span class="p"&gt;)};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topStates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topStates&lt;/span&gt;&lt;span class="p"&gt;)};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The makes the UI entirely self-contained as the page arrives already primed with the valid states and metrics. And, no further external API calls are required just to render the form or for data validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1. &lt;code&gt;get-sales-data&lt;/code&gt; tool
&lt;/h3&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/7f8577249d07e81715400ff6a7325e9a1e622928/src/index.ts#L80-L106" rel="noopener noreferrer"&gt;Tool Registration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisbjr96s1wm57j0j98kg.gif" 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%2Fisbjr96s1wm57j0j98kg.gif" alt="Add data to context" width="800" height="902"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user clicks "Submit" button in the Sales metric selector MCP App, the form doesn't just update the context — it calls another MCP tool from within the iframe itself by executing the below communication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolResult&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;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tools/call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-sales-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedStates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentPeriod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentYear&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;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toolResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;structuredContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We trigger the data fetch manually via this tool as it allows us to have a deterministic control over the most critical element ("data") and we can explicitly define inputs without relying on agent reasoning. The tool is only visible to the app, and not the agent so it cannot call it accidentally. Only the MCP App iframe can invoke it via tools/call. This is a &lt;a href="https://dev.to/ashita/a-practical-guide-to-building-mcp-apps-1bfm#:~:text=9.%20Calling%20MCP%20Tools"&gt;pattern I introduced in my previous article&lt;/a&gt;, and here you can see how powerful it becomes in a real workflow.&lt;/p&gt;

&lt;p&gt;After fetching the result, &lt;a href="https://dev.to/ashita/a-practical-guide-to-building-mcp-apps-1bfm#:~:text=5.%20Updating%20Model%20Context%20via%20MCP%20App"&gt;&lt;code&gt;ui/update-model-context&lt;/code&gt; request is sent to the host&lt;/a&gt; with the entire structured data (user input + data obtained for report) is added into the conversation context, so the agent can use it in subsequent turns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui/update-model-context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;structuredContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;selections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentPeriod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentYear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedStates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;selectedStateNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedStateNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;report&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;report&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;span class="nf"&gt;updateStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;✅ Context updated successfully.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;visualize-sales-data&lt;/code&gt; tool
&lt;/h3&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/7f8577249d07e81715400ff6a7325e9a1e622928/src/index.ts#L108-L203" rel="noopener noreferrer"&gt;Tool Registration&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/main/src/ui/sales-visualization.ts" rel="noopener noreferrer"&gt;MCP App&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fen12goohb3ekcd19l31v.gif" 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%2Fen12goohb3ekcd19l31v.gif" alt="MCP App Visualization Dashboard" width="600" height="817"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the Agent has the structured data in its context, when the user prompts "visualize", the agent calls &lt;code&gt;visualize-sales-data&lt;/code&gt; tool which renders a visualization dashboard inside the chat highlighting some key stats. Three additional complementary chart views are also presented in the dashboard for identifying deeper trends:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A stacked bar or line view for period-by-period comparison.&lt;/li&gt;
&lt;li&gt;A doughnut chart to gauge a state's contribution share (%) in the sales metric.&lt;/li&gt;
&lt;li&gt;A horizontal bar chart for ranked state comparison.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two implementation details are especially worth noticing:&lt;/p&gt;

&lt;h4&gt;
  
  
  i) CSP is not a footnote, it is part of the Security Contract
&lt;/h4&gt;

&lt;p&gt;The visualization MCP App depends on chart.js from a CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://dev.to/ashita/a-practical-guide-to-building-mcp-apps-1bfm#:~:text=8.%20Content%20Security%20Policy%20(CSP)"&gt;As we discussed it in our previous article&lt;/a&gt;, the resource domain dependency is declared explicitly in the UI resource metadata while registering it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;csp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;resourceDomains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net&lt;/span&gt;&lt;span class="dl"&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;p&gt;Without this declaration, the host sandbox will block the script. This demonstrates one of the most practical aspects of MCP Apps - &lt;strong&gt;the app states what it needs, and the host enforces that contract&lt;/strong&gt;. An app cannot silently inherit broad network access just because it is an embedded HTML.&lt;/p&gt;

&lt;h4&gt;
  
  
  ii) MCP App receives tool input through notification
&lt;/h4&gt;

&lt;p&gt;When the host invokes the &lt;code&gt;visualize-sales-data&lt;/code&gt; tool, it forwards the structured payload to the iframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui/notifications/tool-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;structuredContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reportData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;selections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;renderDashboard&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;p&gt;This hydrates the UI with all the data it needs to render summary cards and charts. There is no string parsing or scraping of the tool response text. Just clean structured data flowing from tool to host to app.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;show-sales-pdf-report&lt;/code&gt; tool
&lt;/h3&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/7f8577249d07e81715400ff6a7325e9a1e622928/src/index.ts#L205-L305" rel="noopener noreferrer"&gt;Tool Registration&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/main/src/ui/sales-pdf-report.ts" rel="noopener noreferrer"&gt;MCP App&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v0eo98qexa2iriiaapn.gif" 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%2F0v0eo98qexa2iriiaapn.gif" alt="Preview PDF" width="600" height="817"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the user has explored the interactive visualization dashboard, a PDF report generation can be triggered via &lt;code&gt;show-sales-pdf-report&lt;/code&gt; tool using &lt;code&gt;"generate pdf"&lt;/code&gt; prompt. &lt;/p&gt;

&lt;p&gt;The following actions happen after the tool is triggered: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server generates a PDF report using &lt;code&gt;jsPDF&lt;/code&gt; and &lt;code&gt;jspdf-autotable&lt;/code&gt;, and sends it to the client as a &lt;code&gt;base64 string&lt;/code&gt; via &lt;code&gt;structuredContent&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;The PDF viewer MCP App decodes the base64 payload and renders it using PDF.js (from CDN). This interactive PDF viewer includes page navigation (previous/next) &amp;amp; zoom controls (keyboard shortcuts included).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user also has the option to save the PDF report via a "Download PDF" button as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faws2412nzrmmids4z3vx.gif" 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%2Faws2412nzrmmids4z3vx.gif" alt="Download PDF" width="720" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On clicking the "Download PDF" button a &lt;code&gt;ui/download-file&lt;/code&gt; request is triggered which sends the resource (PDF) to VS Code which displays the save dialog.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui/download-file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;contents&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;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file:///&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pdfFileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pdfBase64&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;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;That pattern is important as sandboxed iframes should not get to ability to save any file without informing the host which thereby informs the end user. Thus, the host stays in control, and the user gets a normal, expected download flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serving MCP Apps Over HTTP
&lt;/h2&gt;

&lt;p&gt;Currently, we are running the MCP server locally using Express with the Streamable HTTP transport - a departure from the stdio approach demonstrated in the &lt;a href="https://dev.to/ashita/a-practical-guide-to-building-mcp-apps-1bfm"&gt;previous article&lt;/a&gt;. This lets us run the server as an HTTP endpoint as we are moving closer to a deployable shape, making it ideal for web-based clients and remote deployments.&lt;/p&gt;

&lt;p&gt;We can run the MCP server locally on node by executing:&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="nb"&gt;cd&lt;/span&gt; /path/to/sample-mcp-apps-chatflow

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Development mode (with hot reload)&lt;/span&gt;
npm run dev

&lt;span class="c"&gt;# Test with MCP Inspector&lt;/span&gt;
npm run inspector:http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which successfully displays&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="o"&gt;&amp;gt;&lt;/span&gt; sample-mcp-apps-chatflow@1.0.0 dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tsx src/index.ts

🚀 MCP Apps Chatflow server running at http://localhost:3000
📡 MCP endpoint: http://localhost:3000/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To connect to this endpoint from VS Code Insiders, add the following config to &lt;code&gt;.vscode/mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&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;"sample-mcp-apps-chatflow"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying to Amazon Bedrock AgentCore Runtime (AgentCore CLI)
&lt;/h2&gt;

&lt;p&gt;What is the value of building an awesome MCP server if you can't take it beyond the localhost? To be truly useful, it needs to be hosted so that your team or your users can connect to it from anywhere, without having to run it on their own system.&lt;/p&gt;

&lt;p&gt;That's where &lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=2c88fb57-1294-4a6d-8290-a7dfb773a582&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt; comes in. It provides a managed, scalable runtime for your MCP server with built-in OAuth and session isolation.&lt;/p&gt;

&lt;p&gt;Let us go ahead and deploy our TypeScript MCP server to Amazon Bedrock AgentCore Runtime.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This guide uses the &lt;a href="https://github.com/aws/agentcore-cli" rel="noopener noreferrer"&gt;AgentCore CLI&lt;/a&gt; (&lt;code&gt;@aws/agentcore&lt;/code&gt;), the current Node.js-based CLI. If you have the older Python-based &lt;code&gt;bedrock-agentcore-starter-toolkit&lt;/code&gt; installed, uninstall it first to avoid command conflicts -- both tools use the &lt;code&gt;agentcore&lt;/code&gt; command name.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;pip uninstall bedrock-agentcore-starter-toolkit   &lt;span class="c"&gt;# if installed via pip&lt;/span&gt;
pipx uninstall bedrock-agentcore-starter-toolkit  &lt;span class="c"&gt;# if installed via pipx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Software prerequisites
&lt;/h3&gt;

&lt;p&gt;Make sure you have the following softwares installed in your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;AWS CLI&lt;/em&gt; with configured credentials (&lt;code&gt;aws sts get-caller-identity&lt;/code&gt; must work)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Node.js 20+&lt;/em&gt; and npm&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Docker&lt;/em&gt; (or podman/finch) for container builds&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html" rel="noopener noreferrer"&gt;&lt;em&gt;AWS CDK&lt;/em&gt;&lt;/a&gt; bootstrapped in your target account/region (&lt;code&gt;npx cdk bootstrap aws://ACCOUNT_ID/REGION&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;VS Code&lt;/em&gt; or any other MCP Apps compatible Host or even terminal for testing the deployed MCP server&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 - Prepare the server for AgentCore Runtime
&lt;/h3&gt;

&lt;p&gt;AgentCore Runtime expects your MCP server to bind to &lt;code&gt;0.0.0.0:8000&lt;/code&gt; and expose a &lt;code&gt;POST /mcp&lt;/code&gt; endpoint using Streamable HTTP transport. The container must target &lt;code&gt;linux/arm64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/ashitaprasad/sample-mcp-apps-chatflow/blob/main/src/index.ts" rel="noopener noreferrer"&gt;&lt;code&gt;src/index.ts&lt;/code&gt;&lt;/a&gt;, update the port and host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🚀 MCP Apps Chatflow server running at http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`📡 MCP endpoint: http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mcp`&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;
  
  
  Step 2: Add a Dockerfile
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;--platform=linux/arm64 node:22-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ ./src/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Production stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; --platform=linux/arm64 node:22-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist/ ./dist/&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOST=0.0.0.0&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;.dockerignore&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;node_modules
dist
agentcore
.git
*.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it builds and starts locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm64 &lt;span class="nt"&gt;-t&lt;/span&gt; sales-mcp-apps &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm64 &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 sales-mcp-apps
&lt;span class="c"&gt;# Test with MCP Inspector:&lt;/span&gt;
npx @modelcontextprotocol/inspector http://localhost:8000/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Install the AgentCore CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @aws/agentcore
agentcore &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create the AgentCore project
&lt;/h3&gt;

&lt;p&gt;From the project root, scaffold the AgentCore configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; salesmcpapps &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;--build&lt;/span&gt; Container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-agent&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skip-python-setup&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skip-git&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agentcore/
  agentcore.json      # Project and runtime configuration
  aws-targets.json    # AWS account and region target
  .cli/               # CLI state (deployed-state.json)
  cdk/                # CDK app for deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure the runtime
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;agentcore create --no-agent&lt;/code&gt; command creates an empty &lt;code&gt;runtimes&lt;/code&gt; array. You must manually add the runtime configuration to &lt;code&gt;agentcore/agentcore.json&lt;/code&gt;. The important fields are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;codeLocation&lt;/code&gt;&lt;/strong&gt; - path to the directory containing your Dockerfile, &lt;strong&gt;relative to the project root&lt;/strong&gt; (the parent of &lt;code&gt;agentcore/&lt;/code&gt;). If your Dockerfile is in the project root, use &lt;code&gt;"."&lt;/code&gt;. If you isolate your app code in a subdirectory (recommended to avoid CDK bundling issues), use that path (e.g., &lt;code&gt;"app"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;entrypoint&lt;/code&gt;&lt;/strong&gt; - the file that starts your server (e.g., &lt;code&gt;"dist/index.js"&lt;/code&gt;). Required even for Container builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;runtimeVersion&lt;/code&gt;&lt;/strong&gt; - must be &lt;code&gt;"NODE_22"&lt;/code&gt; for Node.js projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;protocol&lt;/code&gt;&lt;/strong&gt; - &lt;code&gt;"MCP"&lt;/code&gt; for MCP servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example &lt;code&gt;agentcore/agentcore.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.agentcore.aws.dev/v1/agentcore.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"salesmcpapps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"managedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CDK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&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;"agentcore:created-by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agentcore-cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"agentcore:project-name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"salesmcpapps"&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;"runtimes"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"salesmcpapps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"entrypoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"codeLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MCP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memories"&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;"credentials"&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;"evaluators"&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;"onlineEvalConfigs"&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;"agentCoreGateways"&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;"policyEngines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure deployment target
&lt;/h4&gt;

&lt;p&gt;Edit &lt;code&gt;agentcore/aws-targets.json&lt;/code&gt; with your account ID and region:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Production deployment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_ACCOUNT_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If your Dockerfile is in the project root alongside &lt;code&gt;agentcore/&lt;/code&gt;, the CDK asset bundler may hit path-length errors because it recursively copies the &lt;code&gt;cdk.out&lt;/code&gt; directory into itself. To avoid this, put your application code (Dockerfile, package.json, src/, tsconfig.json) in a subdirectory (e.g., &lt;code&gt;app/&lt;/code&gt;) and set &lt;code&gt;"codeLocation": "app"&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5: Set up Cognito authentication
&lt;/h3&gt;

&lt;p&gt;The AgentCore CLI does not have a built-in command to set up Cognito (unlike the deprecated toolkit's &lt;code&gt;agentcore identity setup-cognito&lt;/code&gt;). You need to create the Cognito resources manually.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a Cognito User Pool
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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;us-east-1

&lt;span class="c"&gt;# Create user pool&lt;/span&gt;
&lt;span class="nv"&gt;POOL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cognito-idp create-user-pool &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pool-name&lt;/span&gt; &lt;span class="s2"&gt;"salesmcpapps-pool"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'UserPool.Id'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Pool ID: &lt;/span&gt;&lt;span class="nv"&gt;$POOL_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Add a domain for the OAuth token endpoint&lt;/span&gt;
aws cognito-idp create-user-pool-domain &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-pool-id&lt;/span&gt; &lt;span class="nv"&gt;$POOL_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain&lt;/span&gt; &lt;span class="s2"&gt;"salesmcpapps-auth"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create a Resource Server and App Client
&lt;/h4&gt;

&lt;p&gt;For machine-to-machine authentication (client_credentials flow):&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 resource server with a custom scope&lt;/span&gt;
aws cognito-idp create-resource-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-pool-id&lt;/span&gt; &lt;span class="nv"&gt;$POOL_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--identifier&lt;/span&gt; &lt;span class="s2"&gt;"salesmcpapps-auth"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Sales MCP Apps"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt; &lt;span class="s1"&gt;'[{"ScopeName":"invoke","ScopeDescription":"Invoke MCP server"}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt;

&lt;span class="c"&gt;# Create app client with client_credentials flow&lt;/span&gt;
&lt;span class="nv"&gt;CLIENT_OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cognito-idp create-user-pool-client &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-pool-id&lt;/span&gt; &lt;span class="nv"&gt;$POOL_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--client-name&lt;/span&gt; &lt;span class="s2"&gt;"salesmcpapps-client"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--generate-secret&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--explicit-auth-flows&lt;/span&gt; ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-o-auth-flows&lt;/span&gt; client_credentials &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-o-auth-scopes&lt;/span&gt; &lt;span class="s2"&gt;"salesmcpapps-auth/invoke"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-o-auth-flows-user-pool-client&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--supported-identity-providers&lt;/span&gt; COGNITO &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$CLIENT_OUTPUT&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys,json; print(json.load(sys.stdin)['UserPoolClient']['ClientId'])"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$CLIENT_OUTPUT&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys,json; print(json.load(sys.stdin)['UserPoolClient']['ClientSecret'])"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Client ID: &lt;/span&gt;&lt;span class="nv"&gt;$CLIENT_ID&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;"Client Secret: &lt;/span&gt;&lt;span class="nv"&gt;$CLIENT_SECRET&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;"Discovery URL: https://cognito-idp.&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/&lt;/span&gt;&lt;span class="nv"&gt;$POOL_ID&lt;/span&gt;&lt;span class="s2"&gt;/.well-known/openid-configuration"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add JWT auth to agentcore.json
&lt;/h4&gt;

&lt;p&gt;Update the runtime in &lt;code&gt;agentcore/agentcore.json&lt;/code&gt; to add &lt;code&gt;authorizerType&lt;/code&gt; and &lt;code&gt;authorizerConfiguration&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"salesmcpapps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entrypoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"codeLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtimeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MCP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorizerType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CUSTOM_JWT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorizerConfiguration"&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;"customJwtAuthorizer"&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;"discoveryUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cognito-idp.us-east-1.amazonaws.com/YOUR_POOL_ID/.well-known/openid-configuration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allowedClients"&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="s2"&gt;"YOUR_CLIENT_ID"&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;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6 - Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore deploy &lt;span class="nt"&gt;--target&lt;/span&gt; default &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Synthesize a CloudFormation template via CDK&lt;/li&gt;
&lt;li&gt;Build your Docker container via AWS CodeBuild and push it to Amazon ECR&lt;/li&gt;
&lt;li&gt;Create the AgentCore Runtime&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The deployment takes roughly 5-10 minutes. On completion, the Runtime ARN is saved to &lt;code&gt;agentcore/.cli/deployed-state.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 - Test the deployment
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Get a Bearer token
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://salesmcpapps-auth.auth.&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;.amazoncognito.com/oauth2/token"&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;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=client_credentials&amp;amp;client_id=&lt;/span&gt;&lt;span class="nv"&gt;$CLIENT_ID&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;client_secret=&lt;/span&gt;&lt;span class="nv"&gt;$CLIENT_SECRET&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;scope=salesmcpapps-auth/invoke"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys,json; print(json.load(sys.stdin)['access_token'])"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Invoke the MCP server
&lt;/h4&gt;

&lt;p&gt;URL-encode the Runtime ARN and call the invocations endpoint:&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;# Get runtime ARN from deployed state&lt;/span&gt;
&lt;span class="nv"&gt;RUNTIME_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json
with open('agentcore/.cli/deployed-state.json') as f:
    state = json.load(f)
rt = list(state['targets']['default']['resources']['runtimes'].values())[0]
print(rt['runtimeArn'])
"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# URL-encode the ARN&lt;/span&gt;
&lt;span class="nv"&gt;ENCODED_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import urllib.parse; print(urllib.parse.quote('&lt;/span&gt;&lt;span class="nv"&gt;$RUNTIME_ARN&lt;/span&gt;&lt;span class="s2"&gt;', safe=''))"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Test MCP initialize&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://bedrock-agentcore.&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/runtimes/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ENCODED_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invocations"&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;"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;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&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;"User-Agent: test-client/1.0"&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":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a response with &lt;code&gt;serverInfo&lt;/code&gt;, &lt;code&gt;capabilities&lt;/code&gt; including &lt;code&gt;resources&lt;/code&gt; and &lt;code&gt;tools&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  List tools and resources
&lt;/h4&gt;

&lt;p&gt;After initialize, you can send &lt;code&gt;tools/list&lt;/code&gt; and &lt;code&gt;resources/list&lt;/code&gt; requests (include the &lt;code&gt;mcp-session-id&lt;/code&gt; header from the initialize response).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8 - Connect from VS Code
&lt;/h3&gt;

&lt;p&gt;In VS Code with an &lt;a href="https://modelcontextprotocol.io/extensions/client-matrix" rel="noopener noreferrer"&gt;MCP Apps-compatible client&lt;/a&gt;, create a &lt;code&gt;.vscode/mcp.json&lt;/code&gt; in your workspace:&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;"servers"&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;"sales-mcp-apps"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://bedrock-agentcore.REGION.amazonaws.com/runtimes/DOUBLE_ENCODED_ARN/invocations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_TOKEN"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important: double-encode the &lt;code&gt;/&lt;/code&gt; in the ARN.&lt;/strong&gt; VS Code's URL parser decodes &lt;code&gt;%2F&lt;/code&gt; back to &lt;code&gt;/&lt;/code&gt; before sending the request, which breaks the ARN path segment. Use &lt;code&gt;%252F&lt;/code&gt; instead of &lt;code&gt;%2F&lt;/code&gt; so that after VS Code decodes once, the server receives the correct &lt;code&gt;%2F&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if your ARN is:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/my-server-abc123
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The URL in &lt;code&gt;mcp.json&lt;/code&gt; should be:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-east-1%3A123456789012%3Aruntime%252Fmy-server-abc123/invocations
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Note &lt;code&gt;%252F&lt;/code&gt; (not &lt;code&gt;%2F&lt;/code&gt;) before the runtime name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once connected, VS Code will discover the tools and render the MCP Apps UIs (sales metric selector, visualization charts, PDF report viewer) as interactive HTML panels in the chat as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff12snhvgvifwlu0oi13f.gif" 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%2Ff12snhvgvifwlu0oi13f.gif" alt="Adding Remote MCP server in VS Code" width="760" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjofn6yo9jmwhppfxd6a3.gif" 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%2Fjofn6yo9jmwhppfxd6a3.gif" alt="Running Remote MCP server App in VS Code" width="760" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean up
&lt;/h3&gt;

&lt;p&gt;To remove deployed resources, delete the CloudFormation stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation delete-stack &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; AgentCore-salesmcpapps-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also clean up Cognito:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cognito-idp delete-user-pool-domain &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-pool-id&lt;/span&gt; &lt;span class="nv"&gt;$POOL_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain&lt;/span&gt; &lt;span class="s2"&gt;"salesmcpapps-auth"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt;

aws cognito-idp delete-user-pool &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-pool-id&lt;/span&gt; &lt;span class="nv"&gt;$POOL_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;MCP Apps is no longer a theoretical specification - it's a practical framework for building rich, interactive experiences inside AI chatflows. This sales analytics case study demonstrates the full spectrum: forms for structured input, Chart.js for visualization, jsPDF for document generation, and the MCP Apps protocol gluing it all together.&lt;/p&gt;

&lt;p&gt;The patterns described here — app-only tools, structured content flow, CSP declarations, server-side PDF generation - are directly transferable to your own use cases. Whether you're building a CRM dashboard, a data annotation tool, or an interactive report builder, the approach remains the same. We also saw how Amazon Bedrock AgentCore can be used for deploying MCP remote servers securely.&lt;/p&gt;

&lt;p&gt;I would love to hear your experience with MCP Apps or about any issues you faced while going through this article. Please mention it in the comment section below and I will definitely address it. Also, in case you have any other suggestion, feel free to add it in the comments.&lt;/p&gt;

&lt;p&gt;by Ashita Prasad (&lt;a href="https://github.com/ashitaprasad" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/ashitaprasad/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://x.com/ashitaprasad" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.instagram.com/ashitaprasad.in" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: The opinions expressed here are my own and do not necessarily represent those of current or past employers. Please note that you are solely responsible for your judgement on checking facts. This post does not monetize via any advertising.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>mcp</category>
    </item>
    <item>
      <title>How I render interactive UI in my AI Agent chatflows using MCP Apps</title>
      <dc:creator>Ashita Prasad</dc:creator>
      <pubDate>Thu, 12 Mar 2026 01:59:36 +0000</pubDate>
      <link>https://forem.com/ashita/a-practical-guide-to-building-mcp-apps-1bfm</link>
      <guid>https://forem.com/ashita/a-practical-guide-to-building-mcp-apps-1bfm</guid>
      <description>&lt;p&gt;You know the feeling of building an incredibly smart AI agent that chats flawlessly, but the second your user needs to interact with a complex dataset, fill out a structured form, or view a real-time chart, that conversation hits a dead end. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;And you realize - Plain text just isn't enough!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Right now, the only way around this is forcing users out of the chat and into an external web app. &lt;em&gt;&lt;strong&gt;For you&lt;/strong&gt;&lt;/em&gt;, that means suddenly wrestling with custom APIs, building redundant authentication layers, and taping together fragile state management. Whereas, &lt;em&gt;&lt;strong&gt;for your users&lt;/strong&gt;&lt;/em&gt;, the seamless conversational flow shatters, context vanishes, and the experience instantly feels disjointed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And, instead of &lt;strong&gt;building something magical&lt;/strong&gt;, you end up buried in development overhead just to show a simple graph.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was going through a similar experience recently while building an Agentic chatflow and then I came across &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/" rel="noopener noreferrer"&gt;MCP Apps&lt;/a&gt;. Built as an extension of the open-source &lt;a href="https://github.com/modelcontextprotocol" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;, MCP Apps give you a standardized escape route from the plain-text trap and let your MCP server deliver rich, bidirectional UIs - like interactive dashboards, data collection forms, and real-time visualizations—rendered securely and natively &lt;em&gt;right inside your AI host&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Are you ready to fix your AI Agent Chat-flow? In this 2-part step-by-step tutorial series, I will explain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Foundations of MCP Apps (&lt;em&gt;This article&lt;/em&gt;)&lt;/strong&gt; - Covering the core architectural patterns for declaring UI resources, practical design principles, and how to handle sandboxed host–server communication.&lt;/li&gt;
&lt;li&gt;How you can deploy a real-world TypeScript MCP App Server on Amazon Bedrock AgentCore. 👇
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" class="crayons-story__hidden-navigation-link"&gt;How I built MCP Apps based Sales Analytics Agentic UI &amp;amp; deployed it on Amazon Bedrock AgentCore&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="/aws"&gt;
            &lt;img alt="AWS 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%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png" class="crayons-logo__image" width="320" height="320"&gt;
          &lt;/a&gt;

          &lt;a href="/ashita" 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%2F850613%2Fb2250c8e-3ebf-48c5-a975-cce5ec391d4c.jpg" alt="ashita profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ashita" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ashita Prasad
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ashita Prasad
                
              
              &lt;div id="story-author-preview-content-3425847" 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="/ashita" 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%2F850613%2Fb2250c8e-3ebf-48c5-a975-cce5ec391d4c.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ashita Prasad&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="/aws" class="crayons-story__secondary fw-medium"&gt;AWS&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 1&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/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" id="article-link-3425847"&gt;
          How I built MCP Apps based Sales Analytics Agentic UI &amp;amp; deployed it on Amazon Bedrock AgentCore
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&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/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" 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/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&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/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&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;
            15 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;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Project directory structure
&lt;/h3&gt;

&lt;p&gt;Let us create the following directory structure for our MCP server project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  index.ts
  ui/
    greeting.ts
    host-style-variables.ts
    ...
    tools-call.ts
  utils/
    apply-host-context.ts
    message-handler.ts
    rpc-client.ts
    shared-styles.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I have created this practical directory structure for building MCP Apps so that it is not just functional, but coherent and maintainable, where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/index.ts&lt;/code&gt; is the MCP server entry point where we register all tools and UI resources,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ui&lt;/code&gt; contains UI resources for each MCP App, and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/utils&lt;/code&gt; contains the shared utilities for design and communication used across UI resources, making them maintainable and consistent. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Shared design system for consistency &amp;amp; maintainability
&lt;/h3&gt;

&lt;p&gt;As MCP Apps provide the visual interface to interact with MCP tools, a consistent design system across all UI resources provides a better user experience. In this project, &lt;code&gt;utils/shared-styles.ts&lt;/code&gt; contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design tokens for background, text, border, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;light-dark(...)&lt;/code&gt; fallbacks so the same app can adapt cleanly across themes.&lt;/li&gt;
&lt;li&gt;Typography tokens, spacing utilities, and border radius primitives.&lt;/li&gt;
&lt;li&gt;Reusable UI components for cards, badges, buttons, inputs, textareas, logs, and tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A snapshot of &lt;code&gt;shared-styles.ts&lt;/code&gt; file is provided below:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In absence of &lt;code&gt;shared-styles.ts&lt;/code&gt;, each app would feel disconnected and require re-implementation of styling, spacing, components and form controls.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Other utilities
&lt;/h3&gt;

&lt;p&gt;We also define some more utility files that contain the common scripts used across all MCP apps defined in the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;utils/rpc-client.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/c02d51c73c9e7fff960fd3254b086b38" rel="noopener noreferrer"&gt;Link to code file&lt;/a&gt;): Creates the JSON-RPC client embedded in each HTML template that manages request IDs, tracks pending promises, and provides &lt;code&gt;request&lt;/code&gt; and &lt;code&gt;notify&lt;/code&gt; helpers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utils/message-handler.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/a18e1f8c2cf520f7b30d6323244e964f" rel="noopener noreferrer"&gt;Link to code file&lt;/a&gt;): Handles inbound messages, resolves pending RPC calls, and responds to host context changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A deeper look into MCP Apps via examples
&lt;/h2&gt;

&lt;p&gt;Now, we are ready to dive deeper into the various concepts involved while building an MCP App.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Anatomy of a minimal MCP App
&lt;/h3&gt;

&lt;p&gt;Let us get started with creating our first MCP App.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1 - Resource and Tool Registration
&lt;/h4&gt;

&lt;p&gt;Add the following code in the &lt;code&gt;index.ts&lt;/code&gt; file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 

&lt;p&gt;In this file,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui://mcp-apps-spec-examples&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is the base URI of the UI resources.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html;profile=mcp-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is the official mime-type of the HTML content which is loaded by the host as a sandboxed iframe.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mcp-apps-spec-examples&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&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;p&gt;initializes a new Model Context Protocol (MCP) Server instance.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/greeting`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A static greeting with no interaction.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GREETING_HTML&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;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;registers a new UI resource named &lt;code&gt;"greeting"&lt;/code&gt; with the unique identifier &lt;code&gt;"ui://mcp-apps-spec-examples/greeting"&lt;/code&gt;. The HTML content is provided by &lt;code&gt;GREETING_HTML()&lt;/code&gt; which we will cover shortly.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A greeting tool with no interaction.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resourceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/greeting`&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;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Greeting executed.&lt;/span&gt;&lt;span class="dl"&gt;"&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;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;registers a tool linked to the resource &lt;code&gt;"ui://mcp-apps-spec-examples/greeting"&lt;/code&gt; through &lt;code&gt;_meta.ui.resourceUri&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This pattern is the backbone of MCP Apps as the server declares the view, the tool points at the view, and the host knows exactly what to load.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 2 - UI Resource Creation (App Template)
&lt;/h4&gt;

&lt;p&gt;Let us now define the MCP App resource &lt;code&gt;GREETING_HTML()&lt;/code&gt; in &lt;code&gt;ui/greeting.ts&lt;/code&gt;. It is a static HTML with no user interaction. &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This UI runs in a sandboxed iframe and communicates with the host through auditable JSON-RPC messages. &lt;/p&gt;

&lt;p&gt;When the UI calls &lt;code&gt;ui/initialize&lt;/code&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/initialize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;protocolVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-11-21&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/notifications/initialized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Handshake complete – UI is live.&lt;/span&gt;&lt;span class="dl"&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;p&gt;The host returns &lt;code&gt;hostContext&lt;/code&gt; and capabilities (covered later in detail), post which the UI sends &lt;code&gt;ui/notifications/initialized&lt;/code&gt; to complete the handshake. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the minimum viable MCP App which is just HTML + the MCP Apps handshake.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 3 - Running the MCP App
&lt;/h4&gt;

&lt;p&gt;To witness the MCP app in action, &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We will first compile the TypeScript code via:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Download a &lt;a href="https://modelcontextprotocol.io/clients" rel="noopener noreferrer"&gt;host that supports MCP Apps extension&lt;/a&gt;. We will use &lt;a href="https://code.visualstudio.com/insiders" rel="noopener noreferrer"&gt;VS Code Insiders&lt;/a&gt; to test our MCP Apps. &lt;/li&gt;
&lt;li&gt;Open a new folder in VS Code Insiders and create &lt;code&gt;.vscode/mcp.json&lt;/code&gt; file which runs the MCP server locally:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Now, using the chat prompt &lt;code&gt;show greeting&lt;/code&gt; we can trigger the tool call rendering the MCP App as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh39rz4kl6zfvq4dqkvg.gif" 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%2Fqh39rz4kl6zfvq4dqkvg.gif" alt="Greeting App" width="760" height="854"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Host-aware MCP App Theme
&lt;/h3&gt;

&lt;p&gt;You can witness in the above demo that the MCP App card theme is currently not blending with the host theme. To provide a consistent user experience it is important to make an embedded app look native instead of bolted on iframe. The MCP Apps specification provides this provision during the MCP Apps protocol handshake - When the View (App) sends an &lt;code&gt;ui/initialize&lt;/code&gt; request to the host, the host responds with its current &lt;code&gt;hostContext&lt;/code&gt; (theme + style tokens) as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--color-background-primary = var(--vscode-editor-background)
--color-background-secondary = var(--vscode-sideBar-background)
....
--color-text-primary = var(--vscode-foreground)
--color-text-secondary = var(--vscode-descriptionForeground)
....
--font-weight-semibold = 600
--font-weight-bold = bold
....
--font-text-xs-line-height = 1.5
--font-text-sm-line-height = 1.5
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These host-provided CSS variables are practical styling input that a UI can inspect and render using &lt;code&gt;applyHostContextScript()&lt;/code&gt; function defined in &lt;a href="https://gist.github.com/ashitaprasad/a9fd4e3e87c44d9a447d78cc7e0a77bd" rel="noopener noreferrer"&gt;&lt;code&gt;utils/apply-host-context.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let us modify &lt;code&gt;ui/greeting.ts&lt;/code&gt; to execute &lt;code&gt;applyHostContext(res?.hostContext);&lt;/code&gt; to update the CSS theme and make it host-aware:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;applyHostContextScript&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils/apply-host-context.js&lt;/span&gt;&lt;span class="dl"&gt;"&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;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;applyHostContextScript&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/initialize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;protocolVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-11-21&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;applyHostContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hostContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/notifications/initialized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Applied Host Context &amp;amp; Handshake complete.&lt;/span&gt;&lt;span class="dl"&gt;'&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;p&gt;(&lt;a href="https://gist.github.com/ashitaprasad/2610182e225d8d83964840ae87fb81b5" rel="noopener noreferrer"&gt;Link to full code&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Now, using the same chat prompt we can trigger the tool call rendering the MCP App with host theme applied on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbh7vouifbf7j724tvqmy.gif" 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%2Fbh7vouifbf7j724tvqmy.gif" alt="Greeting App - Host Context" width="800" height="898"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Discovering Host Capabilities
&lt;/h3&gt;

&lt;p&gt;An MCP App should not assume that every host supports the same set of features. &lt;code&gt;hostCapabilities&lt;/code&gt; are sent to the View when it sends an &lt;code&gt;ui/initialize&lt;/code&gt; request to the host which describes the capabilities the host supports like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openLinks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host supports opening external URLs/links from within the MCP app in the host's browser or external application.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serverTools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can proxy tool calls where the host will re-fetch the tool list and update its UI accordingly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serverResources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host can proxy resource reads and refresh its resource listing when the server's resource set changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host supports the MCP logging capability, allowing the server to emit structured log messages back to the host.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sandbox.permissions.clipboardWrite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;App runs in a sandboxed environment, but has been granted the permission to copy content to the user's clipboard.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updateModelContext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host can accept context updates pushed from the app. The supported content types can be - audio, image, structuredContent (JSON), etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;downloadFile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;App can trigger a file download in the host environment (e.g., save to Downloads).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Host capabilities can be obtained from the host response to &lt;code&gt;'ui/initialize'&lt;/code&gt; as shown below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/initialize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;protocolVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-11-21&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;applyHostContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hostContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/notifications/initialized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;caps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hostCapabilities&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// host capabilities&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let us add the UI Resource (&lt;a href="https://gist.github.com/ashitaprasad/d226e3e486014456e77e6a648a005b67" rel="noopener noreferrer"&gt;&lt;code&gt;ui/host-capabilities.ts&lt;/code&gt;&lt;/a&gt;) and register the resource &amp;amp; tool as shown &lt;a href="https://gist.github.com/ashitaprasad/99117361fb3234708416ff177cf053b0" rel="noopener noreferrer"&gt;here&lt;/a&gt;. On calling the tool, the MCP app will get rendered showing all the host capabilities as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp3j6pmr65sotq805b3g.gif" 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%2Fjp3j6pmr65sotq805b3g.gif" alt="Host capabilities" width="760" height="854"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Example Host Capability - Open Link
&lt;/h3&gt;

&lt;p&gt;Let us explore the host capability of directly navigating from the iframe by opening an external URL.&lt;/p&gt;

&lt;p&gt;Create the App UI resource &lt;code&gt;ui/open-link.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/bb19371cb446185affed895b982c1eee" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It contains the &lt;code&gt;openLink()&lt;/code&gt; function which triggers the &lt;code&gt;ui/open-link&lt;/code&gt; request with the URL parameter. This lets the host know that the app is requesting it to open the specified URL.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openLink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urlInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui/open-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;✅ Host accepted the request for:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❌ Host denied or error:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;p&gt;Let us register this resource and the corresponding tool &lt;code&gt;"open-link-from-app"&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/7dd630d54f27481ef7b4a5f36f82ea86" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;After entering the prompt, the agent displays the MCP App as shown below:&lt;br&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%2F9q8zhus53bg94t7pm0jd.gif" 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%2F9q8zhus53bg94t7pm0jd.gif" alt="ui/open-link example" width="760" height="854"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On clicking the &lt;code&gt;Open&lt;/code&gt; button, the MCP App requests the host to open the URL and the host opens a confirmation dialog which demonstrates the security-first approach where "apps request, hosts decide".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyw9rbo67nc1sw5yw53n.gif" 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%2Feyw9rbo67nc1sw5yw53n.gif" alt="ui/open-link confirmation dialog" width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon pressing the &lt;code&gt;Open&lt;/code&gt; confirmation button, the requested website is opened in the default browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyui6gsfd7twzu4i4du0a.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%2Fyui6gsfd7twzu4i4du0a.png" alt="ui/open-link display website" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Updating Model Context via MCP App
&lt;/h3&gt;

&lt;p&gt;Interactions with an MCP App can often generate useful context (of various content types) that can be directly added to the host's model context. This helps with preference capture, session refinement, and app-guided workflows.&lt;/p&gt;

&lt;p&gt;Let us create &lt;code&gt;ui/update-model-context.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/2bc42293173ae74c27bb0ae84847e42e" rel="noopener noreferrer"&gt;code&lt;/a&gt;), which adds the text entered by the user to the model context via &lt;code&gt;ui/update-model-context&lt;/code&gt; request:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui/update-model-context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&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;Let us register the resource &amp;amp; tool (&lt;a href="https://gist.github.com/ashitaprasad/ebea4c7cc9c7a50795bb1642bc145c61" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Upon triggering the tool, the MCP App is displayed as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuaj2rwg92tvnzq54psd8.gif" 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%2Fuaj2rwg92tvnzq54psd8.gif" alt="Update Model Context" width="800" height="1457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the user enters the text and presses the &lt;code&gt;Update Context&lt;/code&gt; button, the updated context can be seen in the chat box.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Adding Message to the Chat Box
&lt;/h3&gt;

&lt;p&gt;MCP App also provides the ability to send content back into the host's chat box, to enable user to edit it before sending it to the chat flow.&lt;/p&gt;

&lt;p&gt;Let us create &lt;code&gt;ui/ui-message.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/b92c80d8312355f2fd4265ea07f1be54" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;To send the message to chat box, &lt;code&gt;ui/message&lt;/code&gt; request can be triggered with the text content.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui/message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&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;Let's register the resource &amp;amp; tool (&lt;a href="https://gist.github.com/ashitaprasad/3a3ada4d2fe56806e106df2d961811dc" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Upon triggering the tool, the MCP App gets displayed as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2aylms3ygguidtrvpctb.gif" 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%2F2aylms3ygguidtrvpctb.gif" alt="ui/message" width="600" height="1092"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It allows user to communicate a text message from the MCP App to the host's chat box. This opens up new possibilities as the App is no longer a visual tool, and is a part of the conversation loop.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Resizing the iframe
&lt;/h3&gt;

&lt;p&gt;As the content of an MCP App expands, there is a provision to send a size-changed notification to the host to ensure that the frame is sized correctly.&lt;/p&gt;

&lt;p&gt;Let us create &lt;code&gt;ui/size-changed.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/5c72f24a8853d385bae63d6db82e2a0f" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We will use &lt;code&gt;ResizeObserver&lt;/code&gt; to report any changes to the dimensions of an Element's content or border box and send &lt;code&gt;ui/notifications/size-changed&lt;/code&gt; so the host can resize the frame.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResizeObserver&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;notifyCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui/notifications/size-changed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sizeInfo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notifications sent: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;notifyCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  (current: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;×&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;ro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let us now register the resource &amp;amp; the tool (&lt;a href="https://gist.github.com/ashitaprasad/7ea4432f0474a51eb054c5bb8e7381d3" rel="noopener noreferrer"&gt;code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We can press the &lt;code&gt;+Add Item&lt;/code&gt; button to add new data rows. The updated value of scroll width and scroll height are notified to the host so that it can update the iframe height accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxgm8bevi6qqht8ppbyt.gif" 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%2Fuxgm8bevi6qqht8ppbyt.gif" alt="Size changed" width="760" height="1187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature solves a real UX pain point as the host can get the required size and grow or shrink the iframe accordingly.&lt;/p&gt;
&lt;h3&gt;
  
  
  8. Content Security Policy (CSP)
&lt;/h3&gt;

&lt;p&gt;So far we have been working with Apps that have self-contained CSS &amp;amp; JS, and are not making any request to fetch data using external API.&lt;/p&gt;

&lt;p&gt;As MCP Apps are executed inside a sandbox by the host to avoid any security risks, Content Security Policy (CSP) is the mechanism for explicitly defining which resources the MCP App is allowed to load or execute. This prevents malicious scripts or external resources from being injected into the MCP App, which in turn protects the host.&lt;/p&gt;

&lt;p&gt;The typical flow of applying CSP in an MCP App is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;MCP server returns a tool response with UI metadata.&lt;/li&gt;
&lt;li&gt;The host renders the MCP App in a sandboxed iframe.&lt;/li&gt;
&lt;li&gt;The host applies CSP rules.&lt;/li&gt;
&lt;li&gt;The MCP App can only access approved resources.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This external access is declared through the metadata during resource registration. The resource must include &lt;code&gt;_meta.ui.csp&lt;/code&gt; so that the host can allow MCP App access to only the specific domains. Different types of domains that can be provided are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectDomains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Origins the MCP App can reach via network/data requests (&lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;XHR&lt;/code&gt;, or &lt;code&gt;WebSocket&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resourceDomains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Origins allowed to load static assets like images, scripts, stylesheets, fonts, media.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frameDomains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Origins for nested iframes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;baseUriDomains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allowed base URIs for the document&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let us register &lt;code&gt;csp-example&lt;/code&gt;, a sample resource and tool (&lt;a href="https://gist.github.com/ashitaprasad/506d4ed332ee6563a69383915849bef2" rel="noopener noreferrer"&gt;code&lt;/a&gt;), where&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;csp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;connectDomains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://httpbin.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;resourceDomains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net&lt;/span&gt;&lt;span class="dl"&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;p&gt;registers the allowed domains.&lt;/p&gt;

&lt;p&gt;Now create &lt;code&gt;ui/csp-example.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/4e6f8bf01b1b0f421062a77b889024ad" rel="noopener noreferrer"&gt;code&lt;/a&gt;), where&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;loads the external &lt;code&gt;dayjs&lt;/code&gt; javascript library that is used to format the current date using &lt;code&gt;dayjs().format('dddd, MMMM D YYYY')&lt;/code&gt; method. &lt;/p&gt;

&lt;p&gt;Also, when we press the button &lt;code&gt;Fetch&lt;/code&gt;, the MCP App  executes &lt;code&gt;runDirectFetch()&lt;/code&gt; to fetch the latest content from REST API endpoint &lt;code&gt;https://httpbin.org/get&lt;/code&gt; as shown below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runDirectFetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Testing direct fetch from browser…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://httpbin.org/get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Direct fetch successful! (CSP allows this)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Direct fetch blocked: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;p&gt;Let us now execute tool which loads the MCP App as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhkh4vz2pi6jb2zjcv9e.gif" 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%2Fhhkh4vz2pi6jb2zjcv9e.gif" alt="CSP Demo" width="720" height="1125"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  9. Calling MCP Tools
&lt;/h3&gt;

&lt;p&gt;One of the key innovations of MCP Apps is the ability provided to invoke another MCP tool using the &lt;code&gt;tools/call&lt;/code&gt; method from inside an MCP App making it an interaction surface for broader tool orchestration.&lt;/p&gt;

&lt;p&gt;Let us create and register a new tool &lt;code&gt;eval-tool&lt;/code&gt; on the MCP server which evaluates a string expression:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Once you create a new build and restart the server, you can verify that this tool is now visible to the agent. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkn8i0r7i5p8aku797o8k.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%2Fkn8i0r7i5p8aku797o8k.png" alt="Visible to Agent" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MCP Apps spec, provides a provision to restrict this visibility of any tool to only the MCP Apps available on the server using &lt;code&gt;_meta.ui.visibility&lt;/code&gt; field which defaults to &lt;code&gt;["model", "app"]&lt;/code&gt; that means that the tool is visible to agent and the MCP Apps. &lt;/p&gt;

&lt;p&gt;You can hide any tool from the agent but make it callable by MCP apps via &lt;code&gt;tools/call&lt;/code&gt;, by setting its &lt;code&gt;visibility&lt;/code&gt; value to &lt;code&gt;["app"]&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/f6f524c435148132b30cdd34fd2c364d" rel="noopener noreferrer"&gt;code&lt;/a&gt;) as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;"&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;p&gt;Let us verify the same. The tool is no longer visible to the agent as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgtu1r4cv0ufx1psr8vi.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%2Fmgtu1r4cv0ufx1psr8vi.png" alt="Visible only to app" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature now enables UI-only interactions (refresh buttons, form submissions) without exposing implementation details to the agent/model. &lt;/p&gt;

&lt;p&gt;Let us create &lt;code&gt;ui/tools-call.ts&lt;/code&gt; (&lt;a href="https://gist.github.com/ashitaprasad/5baa591b6a9cfdb7714d86e386673c31" rel="noopener noreferrer"&gt;code&lt;/a&gt;). When the user presses the &lt;code&gt;Call tools/call&lt;/code&gt; button, it makes a &lt;code&gt;tools/call&lt;/code&gt; request and populates the result as shown below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tools/call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eval-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expression&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;structuredContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let us now run the MCP App to evaluate some mathematical expressions as shown below: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6bm7yx4j6y4hs18z5qg.gif" 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%2Fn6bm7yx4j6y4hs18z5qg.gif" alt="tools/call example" width="800" height="859"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now witness how powerful the MCP App interface can be for invoking deeper tool workflows.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;Today, we deep dived into some of the foundational building blocks of an MCP App. With these learnings, in the next article we will look at a real world agentic workflow use case demonstrating the power of MCP Apps. &lt;/p&gt;

&lt;p&gt;I would love to hear your experience with MCP Apps or about any issues you faced while going through this tutorial. Please mention it in the comment section below and I will definitely address it. Also, in case you have any other suggestion, feel free add it in the comments.&lt;/p&gt;

&lt;p&gt;Do check out the next article in this series:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" class="crayons-story__hidden-navigation-link"&gt;How I built MCP Apps based Sales Analytics Agentic UI &amp;amp; deployed it on Amazon Bedrock AgentCore&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="/aws"&gt;
            &lt;img alt="AWS 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%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png" class="crayons-logo__image" width="320" height="320"&gt;
          &lt;/a&gt;

          &lt;a href="/ashita" 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%2F850613%2Fb2250c8e-3ebf-48c5-a975-cce5ec391d4c.jpg" alt="ashita profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ashita" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ashita Prasad
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ashita Prasad
                
              
              &lt;div id="story-author-preview-content-3425847" 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="/ashita" 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%2F850613%2Fb2250c8e-3ebf-48c5-a975-cce5ec391d4c.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ashita Prasad&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="/aws" class="crayons-story__secondary fw-medium"&gt;AWS&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 1&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/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" id="article-link-3425847"&gt;
          How I built MCP Apps based Sales Analytics Agentic UI &amp;amp; deployed it on Amazon Bedrock AgentCore
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&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/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i" 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/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&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/aws/how-i-built-mcp-apps-based-sales-analytics-agentic-ui-deployed-it-on-amazon-bedrock-agentcore-4e9i#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&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;
            15 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;
&lt;br&gt;


&lt;p&gt;&lt;em&gt;by Ashita Prasad (&lt;a href="https://github.com/ashitaprasad" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/ashitaprasad/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://x.com/ashitaprasad" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.instagram.com/ashitaprasad.in" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: The opinions expressed here are my own and do not necessarily represent those of current or past employers. Please note that you are solely responsible for your judgement on checking facts. This post does not monetize via any advertising.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Flutter Gems - April 2022 Update 💙 🎉</title>
      <dc:creator>Ashita Prasad</dc:creator>
      <pubDate>Wed, 20 Apr 2022 09:26:41 +0000</pubDate>
      <link>https://forem.com/ashita/flutter-gems-april-2022-update-51i4</link>
      <guid>https://forem.com/ashita/flutter-gems-april-2022-update-51i4</guid>
      <description>&lt;p&gt;I am super excited to announce the latest major update of Flutter Gems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In you are new to the Flutter ecosystem or have not heard about Flutter Gems before, it is a curated package guide for Dart + Flutter ecosystem where the packages are neatly segregated based on functionality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;👇👇👇👇👇👇 &lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://fluttergems.dev/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffluttergems.dev%2Fmedia%2Fbanner.png" height="414" class="m-0" width="748"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://fluttergems.dev/" rel="noopener noreferrer" class="c-link"&gt;
            Flutter Gems - A Curated List of Top Dart and Flutter packages
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Flutter Gems is a curated list of top Dart and Flutter packages that are categorized based on functionality. Flutter Gems is also a visual alternative to pub.dev
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffluttergems.dev%2Fmedia%2Ffavicon.png" width="48" height="48"&gt;
          fluttergems.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;So what’s new in this update:&lt;/p&gt;

&lt;h3&gt;
  
  
  1200+ new packages, 12 new categories
&lt;/h3&gt;

&lt;p&gt;We have now added 1200+ new packages to Flutter Gems which brings the total package count to 4000+ 🎉.&lt;/p&gt;

&lt;p&gt;These packages are spread across 157 categories, and the following 12 new categories have been introduced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/storybook/" rel="noopener noreferrer"&gt;Storybook or Component Driven UI Development&lt;/a&gt; packages which help Flutter developers in building UI components and widgets in isolation so that you can easily design, test and iterate to build the perfect component.&lt;/li&gt;
&lt;li&gt;Persistent Storage packages have now been split into &lt;a href="https://fluttergems.dev/sql-database/" rel="noopener noreferrer"&gt;SQL Database packages&lt;/a&gt; (embedded or on-device SQL database packages and utilities) &amp;amp; &lt;a href="https://fluttergems.dev/nosql-database/" rel="noopener noreferrer"&gt;NoSQL Database packages&lt;/a&gt; (embedded or on-device Document Datastore &amp;amp; Key-Value Store packages).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/health-fitness/" rel="noopener noreferrer"&gt;Health &amp;amp; Fitness&lt;/a&gt; packages assist in connecting your Flutter app to HealthKit, GoogleFit and other APIs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/web3-crypto-blockchain/" rel="noopener noreferrer"&gt;Web3, Crypto &amp;amp; Blockchain&lt;/a&gt; is the hottest new addition. Don’t forget to bookmark it, we will definitely see a lot of new developments in this field.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/crop-image/" rel="noopener noreferrer"&gt;Image Cropping&lt;/a&gt; is one of the most commonly used Image manipulation activity that is undertaken in an app. We now have a dedicated category to solve all your cropping needs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/dual-screen-folding-device-ui/" rel="noopener noreferrer"&gt;Dual Screen and Folding Device UI&lt;/a&gt; packages help you utilize the full potential of the extra screen so that you can craft wonderful User Experience.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/widget-generation-rendering/" rel="noopener noreferrer"&gt;Widget Rendering and UI Generation&lt;/a&gt; packages help you in building UI on the fly. Just supply the requisite JSON or specified format (cloud or device) and let the magic happen!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/device-preview-screenshot/" rel="noopener noreferrer"&gt;Device Preview, Screen Capture &amp;amp; Screenshot&lt;/a&gt; packages help you add the ability to capture screenshots via your app.&lt;/li&gt;
&lt;li&gt;Packages which can help you add &lt;a href="https://fluttergems.dev/hooks/" rel="noopener noreferrer"&gt;React like Hooks in Flutter&lt;/a&gt;, so that you can have a robust and simple way to manage the widget lifecycle by reducing duplication and increasing code sharing.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/code-generator-serialization/" rel="noopener noreferrer"&gt;Code Generation &amp;amp; Serialization (JSON, other formats)&lt;/a&gt; packages&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fluttergems.dev/odt-doc-docx/" rel="noopener noreferrer"&gt;MS Word, ODT, Google Doc&lt;/a&gt; packages&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Brand New UI
&lt;/h3&gt;

&lt;p&gt;We have refreshed our website to provide better user experience on larger screens (reduced scrolling and better drawer navigation).&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved Search
&lt;/h3&gt;

&lt;p&gt;We introduced a new search tool in our mid-January update to filter categories based on keyword or package name. In this release we have made further improvements in the tool.&lt;/p&gt;

&lt;p&gt;You can also follow us on Twitter for latest updates. 👇&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1516445877556056067-97" src="https://platform.twitter.com/embed/Tweet.html?id=1516445877556056067"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1516445877556056067-97');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1516445877556056067&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>programming</category>
      <category>mobile</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
