<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Mcp-Apps on Marian Zeis Blog</title>
    <link>https://blog.zeis.de/tags/mcp-apps/</link>
    <description>Recent content in Mcp-Apps on Marian Zeis Blog</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 13 Jul 2026 09:00:00 +0200</lastBuildDate>
    <atom:link href="https://blog.zeis.de/tags/mcp-apps/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>UI5 Has Entered the Chat: Building Interactive MCP Apps for SAP</title>
      <link>https://blog.zeis.de/posts/2026-07-13-ui5-mcp-apps/</link>
      <pubDate>Mon, 13 Jul 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-07-13-ui5-mcp-apps/</guid>
      <description>&lt;p&gt;AI can appear in a user interface in several ways. It can add a focused feature such as summarization to a traditional application. It can sit next to the application as a copilot. Or the AI conversation itself can become the main interface.&lt;/p&gt;&#xA;&lt;p&gt;That last case raises an interesting question: if the AI host owns the frame, how can an MCP server provide more than text and JSON?&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://modelcontextprotocol.io/extensions/apps/overview&#34;&gt;MCP Apps&lt;/a&gt; provide an answer. They let an MCP tool return an interactive web interface that a compatible host renders inside the conversation. For our UI5con session, &lt;a href=&#34;https://www.linkedin.com/in/mike-zaschka-7395949/&#34;&gt;Mike Zaschka&lt;/a&gt; and I explored how this model fits SAP user interfaces and built five samples, starting with plain HTML and ending with an ARC-1 transport review.&lt;/p&gt;&#xA;&lt;p&gt;This post focuses on the architecture and lessons from those samples. You can &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/blob/main/UI5con2026_MCPApps.pdf&#34;&gt;read the final presentation&lt;/a&gt; or follow the code in the &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps&#34;&gt;sample repository&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-mcp-apps-add-to-mcp&#34;&gt;What MCP Apps add to MCP&lt;/h2&gt;&#xA;&lt;p&gt;A normal MCP tool already gives a model a structured way to call backend capabilities. For a small result, a text response is often the best interface. A dedicated UI becomes useful when people need to filter findings, inspect a hierarchy, compare options, or confirm an action with more context.&lt;/p&gt;&#xA;&lt;p&gt;An MCP App combines a tool result with a web resource:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;A tool declares a &lt;code&gt;ui://&lt;/code&gt; resource URI in &lt;code&gt;_meta.ui.resourceUri&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;The model calls the tool through the host.&lt;/li&gt;&#xA;&lt;li&gt;The host reads the referenced resource from the MCP server.&lt;/li&gt;&#xA;&lt;li&gt;A compatible host renders the returned HTML in a sandboxed iframe.&lt;/li&gt;&#xA;&lt;li&gt;The host passes the tool result to the app through the MCP Apps bridge.&lt;/li&gt;&#xA;&lt;li&gt;The app can request supported host operations, including calls to tools on its MCP server.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;In our samples, the model selects the tool but does not generate the interface. Each UI is prebuilt application code supplied by the MCP server. Communication between the iframe and host uses the MCP Apps protocol over a &lt;code&gt;postMessage&lt;/code&gt; transport.&lt;/p&gt;&#xA;&lt;p&gt;Host support still differs, so the &lt;a href=&#34;https://modelcontextprotocol.io/extensions/client-matrix&#34;&gt;official client support matrix&lt;/a&gt; is more reliable than a static list here.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-minimum-html-sample&#34;&gt;The minimum HTML sample&lt;/h2&gt;&#xA;&lt;p&gt;The updated presentation explains the server side in three parts: register a UI resource, link it from a tool, and return the tool result. The repository&amp;rsquo;s &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/00-html-time&#34;&gt;plain HTML time sample&lt;/a&gt; implements exactly that pattern with two tools and one UI resource.&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-register-the-ui-resource&#34;&gt;1. Register the UI resource&lt;/h3&gt;&#xA;&lt;p&gt;The server registers the built HTML under a &lt;code&gt;ui://&lt;/code&gt; URI. &lt;code&gt;RESOURCE_MIME_TYPE&lt;/code&gt; from the MCP Apps server package resolves to the HTML profile expected by compatible hosts.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;resourceUri&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ui://html-time/index.html&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;registerAppResource&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;server&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTML Time Demo&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;resourceUri&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTML Time Demo&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;description&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;A minimal MCP App.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;uri&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;readBuiltAppResource&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;00-html-time&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;uri&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The sample bundles the browser code and styles into one HTML file for convenient delivery. A single-file bundle is optional, not a protocol requirement. An app can use external assets when its resource metadata declares an appropriate content security policy.&lt;/p&gt;&#xA;&lt;h3 id=&#34;2-link-the-resource-from-a-tool&#34;&gt;2. Link the resource from a tool&lt;/h3&gt;&#xA;&lt;p&gt;The &lt;code&gt;show_time&lt;/code&gt; tool points to that resource in its metadata:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;registerAppTool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;server&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;show_time&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Show Time&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;description&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Open the basic MCP Apps time demo.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;inputSchema&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;offsetHours&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;z.number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;min&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;24&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;max&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;24&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;optional&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;_meta&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;ui&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;resourceUri&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;handler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The URI must match the registered resource exactly. This association tells an MCP Apps host that the tool has a visual companion.&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-return-text-and-structured-data&#34;&gt;3. Return text and structured data&lt;/h3&gt;&#xA;&lt;p&gt;The tool calculates a timestamp and returns two representations of the outcome:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`Current server time: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isoTime&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;structuredContent&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;isoTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;offsetHours&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;formattedTime&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;content&lt;/code&gt; gives the model and non-UI clients a concise fallback. &lt;code&gt;structuredContent&lt;/code&gt; is the stable data contract the app renders without parsing prose. The complete server implementation is in &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/blob/main/packages/00-html-time/src/register.ts&#34;&gt;&lt;code&gt;register.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;run-the-app-inside-the-host&#34;&gt;Run the app inside the host&lt;/h3&gt;&#xA;&lt;p&gt;Inside the iframe, the app connects to the host and listens for the tool result:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTML Time Demo&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;toolresult&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;renderTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;structuredContent&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;TimePayload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;connect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The interface shows the time and an &lt;strong&gt;Add 1 hour&lt;/strong&gt; button. That button calls a second tool through the bridge:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;callServerTool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;add_hour&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;arguments&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;isoTime&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;currentIsoTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;hours&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;renderTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;structuredContent&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;TimePayload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The host routes the request to the server and returns the result to the existing iframe. The app updates without asking the model to generate another interface. See the full browser code in &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/blob/main/packages/00-html-time/app/src/main.ts&#34;&gt;&lt;code&gt;main.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This small example captures the important separation of responsibilities:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The model chooses the initial tool based on the user&amp;rsquo;s request.&lt;/li&gt;&#xA;&lt;li&gt;The MCP server executes backend logic and returns text plus structured data.&lt;/li&gt;&#xA;&lt;li&gt;The host controls whether and how the UI is rendered.&lt;/li&gt;&#xA;&lt;li&gt;The app renders the data and handles local interaction.&lt;/li&gt;&#xA;&lt;li&gt;Further tool calls still travel through the host-controlled bridge.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;That is the core concept. UI5 changes the presentation layer, not the MCP Apps lifecycle. It also explains why debugging has two layers: a successful tool call does not prove that the UI resource loaded, and a loaded iframe does not prove that its scripts passed the host&amp;rsquo;s content security policy.&lt;/p&gt;&#xA;&lt;h2 id=&#34;when-a-tool-result-should-become-an-app&#34;&gt;When a tool result should become an app&lt;/h2&gt;&#xA;&lt;p&gt;MCP Apps are useful, but they should not turn every tool call into a miniature application. Text is still the fastest interface for a status, identifier, confirmation, or short explanation. A table in prose may also be enough when the user only needs to read it once.&lt;/p&gt;&#xA;&lt;p&gt;I would add an app when at least one of these is true:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The user needs to explore or filter a result without repeatedly calling the model.&lt;/li&gt;&#xA;&lt;li&gt;The structure is easier to understand visually, such as a transport hierarchy or findings grouped by file.&lt;/li&gt;&#xA;&lt;li&gt;The next action benefits from seeing context first, such as choosing a task after reviewing its objects.&lt;/li&gt;&#xA;&lt;li&gt;A long-running or multi-step workflow benefits from visible progress and guided decisions.&lt;/li&gt;&#xA;&lt;li&gt;The interaction has temporary presentation state that does not belong in the conversation.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The UI should stay focused on the result and its immediate actions. It should not recreate the host&amp;rsquo;s chat controls or hide backend behavior behind ordinary-looking buttons. If an action writes data, changes source files, or releases a transport, its effect should be clear before the request reaches the server.&lt;/p&gt;&#xA;&lt;p&gt;Because the tool still returns normal &lt;code&gt;content&lt;/code&gt;, a host without MCP Apps support can show a useful text result. The interface enhances the tool contract instead of replacing it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;bringing-ui5-into-the-iframe&#34;&gt;Bringing UI5 into the iframe&lt;/h2&gt;&#xA;&lt;p&gt;An MCP App is a web application, so it can use plain JavaScript or frameworks such as React, Vue, Svelte, or UI5 technologies.&lt;/p&gt;&#xA;&lt;p&gt;For focused SAP-oriented interfaces, &lt;a href=&#34;https://ui5.github.io/webcomponents/&#34;&gt;UI5 Web Components&lt;/a&gt; are my preferred default. They provide SAP-styled, themeable and accessible controls while remaining based on web standards and usable with different frameworks. In the final sample, the app listens for the host&amp;rsquo;s light or dark context and maps it to &lt;code&gt;sap_horizon&lt;/code&gt; or &lt;code&gt;sap_horizon_dark&lt;/code&gt;. An MCP App can use familiar controls without adopting the full SAPUI5 application model.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://ui5.sap.com/test-resources/sap/ui/integration/demokit/cardExplorer/index.html&#34;&gt;UI Integration Cards&lt;/a&gt; are attractive when the result is naturally a summary, list, or compact dashboard. Their declarative manifests reduce custom rendering code. Our card sample loads the Integration Cards runtime from &lt;code&gt;ui5.sap.com&lt;/code&gt;, so its resource declares that domain in its content security policy. An allowlist cannot override a host&amp;rsquo;s sandbox, however. The repository therefore uses portable Object and List Cards rather than an Analytical Card whose &lt;code&gt;sap.viz&lt;/code&gt; runtime may require blocked execution features.&lt;/p&gt;&#xA;&lt;p&gt;A full SAPUI5 application can also be served as an MCP App, but I would use it selectively. Fiori Elements&amp;rsquo; OData-driven page model, routing, and shell integration can be excessive inside a small conversation frame. That is a design tradeoff, not a protocol restriction.&lt;/p&gt;&#xA;&lt;p&gt;This is not an SAP-specific extension to MCP. It is the normal MCP Apps model combined with UI5. It nevertheless fits the direction described by SAP&amp;rsquo;s &lt;a href=&#34;https://architecture.learning.sap.com/docs/ai-native-north-star-architecture/user-experience-layer&#34;&gt;AI-native North Star user experience layer&lt;/a&gt;, where users express intent and the system brings relevant information and actions into context.&lt;/p&gt;&#xA;&lt;p&gt;The distinction between MCP and MCP Apps matters here. &lt;a href=&#34;https://help.sap.com/docs/joule-studio-classic/joule-studio-classic-edition/add-mcp-servers-to-your-joule-agent&#34;&gt;Joule Studio documents how to add MCP servers to a Joule agent&lt;/a&gt;, using Streamable HTTP endpoints configured through SAP BTP destinations. That allows Joule agents to use server tools, but it does not by itself mean the host renders MCP App resources. At the time of writing, I could not verify documented Joule support for the MCP Apps extension in the official client matrix.&lt;/p&gt;&#xA;&lt;h2 id=&#34;from-the-minimum-sample-to-sap-workflows&#34;&gt;From the minimum sample to SAP workflows&lt;/h2&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps&#34;&gt;sample repository&lt;/a&gt; contains five steps and a repository-wide &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/blob/main/LEARNING.md&#34;&gt;learning guide&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/00-html-time&#34;&gt;Plain HTML time&lt;/a&gt;:&lt;/strong&gt; Demonstrates the tool, resource, result, iframe, and app-triggered tool call with minimal code.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/01-ui5-webc-project-overview&#34;&gt;UI5 Web Components project overview&lt;/a&gt;:&lt;/strong&gt; Renders project metadata, libraries, and checks, synchronizes the host&amp;rsquo;s light or dark theme, and requires no external CSP domains.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/02-ui5-card-project-overview&#34;&gt;UI Integration Cards project overview&lt;/a&gt;:&lt;/strong&gt; Renders the same dataset as declarative Object and List Cards. Its external &lt;code&gt;ui5.sap.com&lt;/code&gt; runtime is explicitly declared in the resource CSP.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/03-ui5-lint-findings&#34;&gt;UI5 lint findings&lt;/a&gt;:&lt;/strong&gt; Opens the UI first, then calls back through the bridge to run the official UI5 MCP server as a child client, with a local &lt;code&gt;ui5lint&lt;/code&gt; fallback. Filtering stays local. Ignoring one line, one rule in a file, or a whole file invokes an explicit write tool, after which the app can rerun the linter.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/04-arc1-transport&#34;&gt;ARC-1 transport review&lt;/a&gt;:&lt;/strong&gt; Provides separate list and detail tools for live, host-orchestrated ARC-1 data. Clearly labelled offline fixtures are included for rehearsals when ARC-1 or SAP is unavailable.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;MCP Apps host rendering the UI5 linter demo with active findings grouped by rule and file, plus rerun and ignore actions.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-07-13-ui5-mcp-apps/images/ui5-lint-findings.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The ARC-1 example introduces an important boundary. The UI belongs to one MCP server, while ARC-1 is another. The app therefore does not secretly call an ARC-1 tool. When someone confirms a release in the UI, the app sends a message to the host asking the agent to continue the workflow. The host can then select the connected ARC-1 tool and apply its normal authorization and confirmation behavior.&lt;/p&gt;&#xA;&lt;p&gt;My rule of thumb is simple:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use &lt;code&gt;callServerTool()&lt;/code&gt; for a known tool exposed by the server that owns the app.&lt;/li&gt;&#xA;&lt;li&gt;Send a message to the host when the agent must coordinate another server or decide the next step.&lt;/li&gt;&#xA;&lt;li&gt;Keep consequential operations visible to the host&amp;rsquo;s approval model.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;host-ux-state-and-security&#34;&gt;Host UX, state, and security&lt;/h2&gt;&#xA;&lt;p&gt;The host owns the frame around an MCP App, which creates a few UX constraints that are easy to miss:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Display mode:&lt;/strong&gt; an app may request &lt;code&gt;inline&lt;/code&gt;, &lt;code&gt;fullscreen&lt;/code&gt;, or picture-in-picture, but the host declares which modes it supports and returns the mode it actually selected.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Theme:&lt;/strong&gt; the host can provide a light or dark preference and optional style variables. The UI5 Web Components sample maps that preference to &lt;code&gt;sap_horizon&lt;/code&gt; or &lt;code&gt;sap_horizon_dark&lt;/code&gt; and reacts when it changes.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Size:&lt;/strong&gt; the host can provide fixed or maximum container dimensions. The SDK reports content-size changes automatically by default, but the interface should still be responsive within the space the host grants it.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;An app-triggered &lt;code&gt;callServerTool()&lt;/code&gt; can update the existing iframe, as the time sample demonstrates. A host may nevertheless recreate the iframe for a new UI-producing call or when its lifecycle requires it. Therefore, filters and selected rows can stay local, while important results belong in structured tool output or the backend. Durable changes, such as adding a linter ignore, belong in the project or backend. Refresh authoritative data after mutations instead of relying on iframe memory.&lt;/p&gt;&#xA;&lt;p&gt;The iframe sandbox is also not a complete security model. The host controls bridge capabilities, while the server must still validate inputs and authorize tool calls. External scripts and network destinations need to be declared in the MCP App&amp;rsquo;s UI content security policy. Backend writes should be explicit and should not be disguised as harmless UI interactions.&lt;/p&gt;&#xA;&lt;p&gt;Our samples use Vite and &lt;code&gt;vite-plugin-singlefile&lt;/code&gt; to produce one built &lt;code&gt;index.html&lt;/code&gt; for each &lt;code&gt;ui://&lt;/code&gt; resource. This simplifies delivery, although bundling UI5 Web Components increases the artifact size. The repository provides both stdio and Streamable HTTP launchers. These connect the MCP client and server; the app uses the separate iframe bridge.&lt;/p&gt;&#xA;&lt;h2 id=&#34;try-the-minimum-sample&#34;&gt;Try the minimum sample&lt;/h2&gt;&#xA;&lt;p&gt;The repository requires Node.js 22. The exact commands and client configuration live in the &lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/tree/main/packages/00-html-time&#34;&gt;minimum sample README&lt;/a&gt;. From the repository root:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm ci&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm run build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm run start:00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Connect an MCP Apps-compatible HTTP client to &lt;code&gt;http://127.0.0.1:3010/mcp&lt;/code&gt;, then ask it to call &lt;code&gt;show_time&lt;/code&gt;. The text result should remain useful even in a client that does not render the UI.&lt;/p&gt;&#xA;&lt;p&gt;For a protocol-level check, start the aggregate server with &lt;code&gt;npm run start&lt;/code&gt; and run &lt;code&gt;npm run test:mcpjam&lt;/code&gt; in another terminal.&lt;/p&gt;&#xA;&lt;!-- TODO before publication: add screenshots of the Web Components overview, Integration Cards comparison, and ARC-1 transport organizer. --&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/blob/main/UI5con2026_MCPApps.pdf&#34;&gt;Final UI5con presentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps&#34;&gt;UI5 MCP Apps sample repository&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/UI5con_2026_MCPApps/blob/main/LEARNING.md&#34;&gt;Step-by-step learning guide&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://modelcontextprotocol.io/extensions/apps/overview&#34;&gt;MCP Apps overview&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://modelcontextprotocol.io/extensions/apps/build&#34;&gt;Build an MCP App&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://modelcontextprotocol.io/extensions/client-matrix&#34;&gt;MCP Apps client support&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/modelcontextprotocol/ext-apps&#34;&gt;MCP Apps SDK and examples&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://ui5.github.io/webcomponents/&#34;&gt;UI5 Web Components&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://architecture.learning.sap.com/docs/ai-native-north-star-architecture/user-experience-layer&#34;&gt;SAP AI-native North Star user experience layer&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/arc-mcp/arc-1&#34;&gt;ARC-1&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
  </channel>
</rss>
