← AI Agents Open Protocols
🤖 AI Agents

MCP Apps: Delivering UI Components from Tool Servers

Source: mortalapps.com
TL;DR
  • MCP Apps extend the Model Context Protocol to deliver interactive HTML/JS components directly from tool servers.
  • Solves the limitation of static text-based tool outputs by providing rich, stateful user interfaces.
  • Critical for production environments requiring complex data visualization, interactive forms, or human-in-the-loop approvals.
  • Enables a 'write once, render anywhere' model for agentic tools across different host applications.
  • Requires strict iframe sandboxing and postMessage-based communication to maintain security boundaries.

Why This Matters

MCP Apps represent the next evolution in the Model Context Protocol, enabling servers to deliver interactive components rather than just raw text or JSON. By utilizing mcp apps ui tools, developers can bridge the gap between static LLM responses and rich, functional interfaces. This approach was invented to solve the UI fragmentation problem: previously, every host application (such as a custom agent dashboard or a desktop client) had to manually implement rendering logic for every possible tool output. If a tool returned a complex financial ledger, the host needed specific code to draw that table. MCP Apps flip this model, allowing the tool server to provide the UI logic itself. In production, ignoring the standardized delivery of UI components leads to high maintenance overhead and inconsistent user experiences across different platforms. Furthermore, without a standardized sandboxing protocol, executing server-provided UI code poses significant security risks, including cross-site scripting (XSS) and data exfiltration. Use MCP Apps when your agent needs to present complex data that requires user interaction, such as interactive charts, multi-step configuration wizards, or real-time monitoring dashboards. It is the preferred alternative to custom-coded host widgets because it decouples the UI lifecycle from the host application's release cycle, allowing tool developers to iterate on the interface independently of the agent platform.

Core Concepts

The MCP App ecosystem relies on several fundamental concepts to ensure interoperability and security:

  • App Manifest: A JSON definition returned by the server describing the UI component's entry point, required permissions, and versioning.
  • Iframe Sandboxing: The primary security mechanism where the host renders the server-provided UI in an isolated environment with restricted capabilities (e.g., no top-level navigation, no same-origin access).
  • postMessage Bridge: The asynchronous communication channel between the host and the sandboxed UI, used for state synchronization and event handling.
  • Resource URIs: Standardized identifiers used by the UI to request additional data or assets from the MCP server via the host.
  • Host Handshake: The initial sequence where the host validates the UI component and establishes the communication protocol version.
  • State Hydration: The process of passing initial data from the agent's context into the UI component upon initialization.

How It Works

The lifecycle of an MCP App involves a coordinated sequence between the Agent, the Host Application, and the MCP Tool Server.

1. Tool Discovery and Execution

The process begins when the Agent decides to call a tool that supports UI output. The Host sends a tools/call request to the MCP Server. The server executes the logic and returns a response containing a ui field. This field includes either the raw HTML/JS content or a URI pointing to the component's assets.

2. Sandbox Initialization

Upon receiving the UI metadata, the Host creates a sandboxed <iframe>. The host applies strict Content Security Policy (CSP) headers and the sandbox attribute (typically allow-scripts). This ensures the UI component cannot access the host's cookies, local storage, or internal DOM.

3. The Handshake Phase

The Host injects a lightweight 'bridge' script into the iframe. The UI component sends an MCP_APP_READY message via window.parent.postMessage. The Host responds with MCP_APP_INIT, providing the initial state, theme preferences (dark/light mode), and localized strings.

4. Interactive Loop

As the user interacts with the UI (e.g., clicking a button to approve a transaction), the UI component sends messages back to the Host. The Host can either update its internal state, trigger a new tool call to the MCP Server, or pass the user's input back to the Agent's reasoning loop. If the UI component needs fresh data, it requests a Resource URI, which the Host fetches from the MCP Server on the UI's behalf.

5. Failure Handling

If the UI component fails to load or the handshake times out, the Host must fall back to a text-only representation of the tool's output. This ensures the Agent can still function even if the rich UI layer is unavailable or blocked by network policies.

Architecture

The architecture consists of three primary nodes: the MCP Tool Server, the Host Application, and the Sandboxed UI Component. The Tool Server acts as the source of truth, providing both the data and the UI manifest. The Host Application serves as the orchestrator; it manages the lifecycle of the agent, the communication with the server via JSON-RPC, and the rendering of the UI sandbox. The Sandboxed UI is the execution environment for the component. Data flows from the Server to the Host via the MCP protocol, then from the Host to the UI via the postMessage bridge. User interactions flow in reverse: from the UI to the Host via postMessage, and potentially from the Host back to the Server to trigger further actions. The execution starts with the Agent's tool call and ends when the Host terminates the sandbox session.

Defining the UI Schema

An MCP App is defined within the tool's output using a specific schema. The server must return a content type of application/vnd.mcp.app+json. This object contains the root (the main HTML file), assets (a list of supporting JS/CSS files), and capabilities (such as whether the app requires camera access or file system markers). In production, it is recommended to use content hashing for assets to ensure cache integrity and prevent tampering during transit.

The Sandboxing Model

Security is paramount when executing code provided by a remote server. The Host must implement the iframe with the following attributes: sandbox="allow-scripts allow-forms". Crucially, allow-same-origin should be omitted unless the UI is served from the exact same domain as the host, which is rare in MCP architectures. This isolation prevents the UI from accessing the Host's localStorage or document.cookie. Additionally, the Host should implement a strict Content Security Policy (CSP) that restricts the UI from making direct network requests (XHR/Fetch) to any domain other than the designated MCP server proxy.

Bidirectional Communication via postMessage

The communication bridge uses a request-response pattern over postMessage. Every message must include a correlationId to track asynchronous operations. For example, if the UI requests a resource, it sends a message: { type: 'RESOURCE_REQ', uri: 'mcp://server/data', id: '123' }. The Host intercepts this, validates the URI against the server's allowed resources, fetches the data, and returns: { type: 'RESOURCE_RES', data: {...}, id: '123' }. This indirection allows the Host to enforce access control and rate limiting on behalf of the UI.

State Management and Persistence

MCP Apps are typically ephemeral, but they often need to persist state across agent turns. The protocol supports a state object that the Host maintains. When the UI component is unmounted, it can send a STATE_SAVE message. The Host stores this blob (often in a database like Redis or PostgreSQL) and re-injects it if the Agent re-invokes the same UI component later in the conversation. This is critical for multi-step workflows where a user might start a configuration, ask the agent a clarifying question, and then return to the UI to finish.

Security Boundaries and CSP

To prevent data exfiltration, the Host should dynamically generate a CSP for each UI instance. The script-src should only allow the specific hashes of the scripts provided in the App Manifest. The connect-src should be set to 'none', forcing all data requests through the Host's postMessage bridge. This 'air-gapping' of the UI component ensures that even if the MCP server is compromised, the UI cannot secretly send user data to a third-party malicious endpoint.

Code Example

Python MCP Server returning an interactive UI component for a weather tool.
Python
import os
from mcp.server.fastmcp import FastMCP

# Initialize MCP Server
mcp = FastMCP("weather-service")

@mcp.tool()
def get_weather_forecast(location: str):
    # Business logic to fetch weather data
    data = {"temp": 72, "condition": "Sunny", "forecast": [70, 71, 75]}
    
    # Return tool output with UI component
    return {
        "content": [
            {
                "type": "text",
                "text": f"The weather in {location} is {data['condition']} at {data['temp']}°F."
            },
            {
                "type": "application/vnd.mcp.app+json",
                "app": {
                    "root": "index.html",
                    "initial_state": data,
                    "assets": ["https://cdn.example.com/weather-widget.js"]
                }
            }
        ]
    }

if __name__ == "__main__":
    # Use environment variables for configuration
    port = int(os.environ.get("MCP_PORT", 8080))
    mcp.run(transport="streamable-http", host="0.0.0.0", port=port)
Expected Output
A tool response containing both a text summary and a UI manifest for the host to render.
JavaScript Host-side logic for handling the postMessage bridge and sandboxing.
Javascript
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts';
iframe.srcdoc = `<html><body><div id="root"></div><script src="${appManifest.assets[0]}"></script></body></html>`;

// Listen for messages from the sandboxed UI
window.addEventListener('message', (event) => {
    // Security check: Ensure message comes from our iframe
    if (event.source !== iframe.contentWindow) return;

    const { type, payload, correlationId } = event.data;

    switch (type) {
        case 'MCP_APP_READY':
            // Send initial state to the UI
            iframe.contentWindow.postMessage({
                type: 'MCP_APP_INIT',
                payload: appManifest.initial_state
            }, '*');
            break;
        case 'UI_ACTION':
            console.log('User performed action:', payload);
            // Handle action (e.g., update agent context)
            break;
    }
});

document.getElementById('ui-container').appendChild(iframe);
Expected Output
An initialized iframe with a secure communication channel established between the host and the UI.

Key Takeaways

MCP Apps allow tool servers to deliver rich, interactive UI components instead of just text.
Security is maintained through strict iframe sandboxing and the omission of 'allow-same-origin'.
Communication between the host and the UI is handled via a secure, asynchronous postMessage bridge.
The Host Application acts as a proxy for all data requests, ensuring the UI cannot bypass security policies.
State management is critical for multi-turn agent interactions and must be handled by the host.
Enterprise adoption requires strict CSP enforcement and RBAC-based component delivery.
A text-only fallback is mandatory to ensure agent reliability when UI rendering fails.

Frequently Asked Questions

What is the difference between an MCP Tool and an MCP App? +
An MCP Tool is a function the agent calls to perform an action. An MCP App is a UI component returned by that tool to provide an interactive interface for the user.
Can MCP Apps access the user's local file system? +
No. Due to iframe sandboxing, MCP Apps are isolated from the local file system unless the host explicitly provides a bridge for specific, user-approved files.
How do I debug an MCP App UI? +
You can use standard browser DevTools. Since the UI is in an iframe, you can inspect its DOM, network requests, and console logs separately from the host.
What happens if the MCP server goes offline while the UI is open? +
The UI will remain visible, but any subsequent requests for resources or data via the postMessage bridge will fail, and the host should display a 'Server Disconnected' error.
Can I use React or Vue to build MCP Apps? +
Yes. You can use any web framework. The server simply needs to bundle the resulting HTML/JS and serve it as part of the MCP App manifest.
Is it possible to use MCP Apps in a terminal-based agent? +
No. MCP Apps require a graphical host environment capable of rendering HTML and managing iframes. Terminal hosts will use the text-only fallback.
How does the host know which UI component to render? +
The MCP server specifies the UI manifest in the tool's response. The host looks for the 'application/vnd.mcp.app+json' content type.
Are MCP Apps safe to use with third-party servers? +
Only if strict sandboxing and CSP are enforced. Without these, a third-party server could deliver malicious JS that attempts to attack the host application.