MCP Apps Integration in A2UI Surfaces¶
This guide explains how Model Context Protocol (MCP) Applications are integrated and displayed within the A2UI surface, along with the security model and testing guidelines.
NOTE: Looking for the core A2UI-over-MCP protocol? See A2UI over MCP for how to return A2UI JSON payloads from MCP tool calls.
Overview¶
The Model Context Protocol (MCP) allows MCP servers to deliver rich, interactive HTML-based user interfaces to hosts. A2UI provides a secure environment to run these third-party applications.

Double-Iframe Isolation Pattern¶
To run untrusted third-party code securely, A2UI utilizes a double-iframe isolation pattern. This approach isolates raw DOM injection from the main application while maintaining a structured JSON-RPC channel.
Security Rationale¶
Standard single-iframe sandboxing with allow-scripts is often bypassed if combined with allow-same-origin, which defeats the containerization. Any iframe with allow-scripts and allow-same-origin can escape its sandbox by programmatically interacting with its parent DOM or removing its own sandbox attribute.
To prevent this, A2UI strictly excludes allow-same-origin for the inner iframe where the third-party application runs.
The Architecture¶
- Sandbox Proxy (
sandbox.html): An intermediateiframeserved from the same origin. It isolates raw DOM injection from the main app while maintaining a structured JSON-RPC channel.- Permissions: Do not sandbox in the host template (e.g.,
mcp-app.tsormcp-apps-component.ts). - Host origin validation: Validates that messages come from the expected host origin.
- Permissions: Do not sandbox in the host template (e.g.,
- Embedded App (Inner Iframe): The innermost
iframe. Injected dynamically viasrcdocwith restricted permissions.- Permissions:
sandbox="allow-scripts allow-forms allow-popups allow-modals"(MUST NOT includeallow-same-origin). - Isolation: Removes access to
localStorage,sessionStorage,IndexedDB, and cookies due to unique origin.
- Permissions:
Architecture Diagram¶
flowchart TD
subgraph "Host Application"
A[A2UI Page] --> B["Host Component e.g., McpApp"]
end
subgraph "Sandbox Proxy"
B -->|Message Relay| C[iframe sandbox.html]
end
subgraph "Embedded App"
C -->|Dynamic Injection| D[inner iframe untrusted content]
end
Usage / Code Example¶
The MCP Apps component typically resolves to a custom node in the A2UI catalog. Here is how a developer might use it in their code.
1. Register within the Catalog¶
You must register the component in your catalog application. For example, in Angular:
import { Catalog } from '@a2ui/angular';
import { inputBinding } from '@angular/core';
export const DEMO_CATALOG = {
McpApp: {
type: () => import('./mcp-app').then((r) => r.McpApp),
bindings: ({ properties }) => [
inputBinding(
'content',
() => ('content' in properties && properties['content']) || undefined,
),
inputBinding('title', () => ('title' in properties && properties['title']) || undefined),
],
},
} as Catalog;
2. Usage in A2UI Message¶
In the Host or Agent context, send an A2UI message that translates to this custom node.
{
"type": "custom",
"name": "McpApp",
"properties": {
"content": "<h1>Hello, World!</h1>",
"title": "My MCP App"
}
}
If the content is complex or requires encoding, you can pass a URL-encoded string:
{
"type": "custom",
"name": "McpApp",
"properties": {
"content": "url_encoded:%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E",
"title": "My MCP App"
}
}
Communication Protocol¶
Communication between the Host and the embedded inner iframe is facilitated via a structured JSON-RPC channel over postMessage.
- Events: The Host Component listens for a
SANDBOX_PROXY_READY_METHODmessage from the proxy. - Bridging: An
AppBridgehandles message relaying. Developers (specifically the MCP App Developer inside the untrusted iframe) can call tools on the MCP server usingbridge.callTool(). - The Host: Resolves callbacks (e.g., specific resizing, Tool results).
Limitations¶
Because allow-same-origin is strictly omitted for the innermost iframe, the following conditions apply:
- The MCP app cannot use localStorage, sessionStorage, IndexedDB, or cookies. Each application runs with a unique origin.
- Direct DOM manipulation by the parent is blocked. All interactions must proceed via message passing.
Prerequisites¶
To run the samples, ensure you have the following installed:
- Python 3.10+ — Required for the agent and MCP server backends
- uv — Fast Python package manager (used to run all Python samples)
- Node.js 18+ and npm — Required for building and running the client apps
- A
GEMINI_API_KEY— Required by all ADK-based agents. Get one from Google AI Studio
⚠️ Environment variable setup: You can either export
GEMINI_API_KEYin your shell or create a.envfile in each agent directory. The agents usedotenvto load.envfiles automatically.
Samples¶
There are two primary samples demonstrating MCP Apps integration. Each sample requires running multiple terminals — one for each backend service and one for the client.
1. MCP App Standalone Sample (Lit & ADK Agent)¶
This sample verifies the sandbox with a Lit-based client and an ADK-based A2A agent.
- A2A Agent Server:
- Path:
samples/agent/adk/mcp-apps-in-a2ui-sample/ - Command:
uv run .(requiresGEMINI_API_KEYin.env)
- Path:
- Lit Client App:
- Path:
samples/client/lit/mcp-apps-in-a2ui-sample/ - Command:
npm install && npm run dev(requires building the Lit renderer first) - URL:
http://localhost:5173/
- Path:
What to expect: A simple interface loading the MCP App, with a button to trigger an action handled by the agent.
2. MCP Apps (Calculator + Pong) (Angular)¶
Step 3: Open in Browser¶
Open your browser and navigate to http://localhost:5173. You should see the A2UI interface loading the MCP App.
What to expect: A page loading the MCP App in a sandboxed iframe. Clicking the "Call Agent Tool" button inside the iframe will trigger an action that is handled by the agent.
Sample 2: MCP Apps (Calculator + Pong) (Angular Client + MCP Server + Proxy Agent)¶
This sample verifies the sandbox with an Angular-based client, an MCP Proxy Agent, and a remote MCP Server. It requires three backend processes.
Step 1: Start the MCP Server (Calculator)¶
¶
The client starts at http://localhost:5173/.
Step 2: Start the Agent¶
In a separate terminal, navigate to the agent directory and start the agent:
The agent will run on http://localhost:8000.
Step 3: Open in Browser¶
Open your browser and navigate to http://localhost:5173. You should see the A2UI interface loading the MCP App.
What to expect: A page loading the MCP App in a sandboxed iframe. Clicking the "Call Agent Tool" button inside the iframe will trigger an action that is handled by the agent.
Sample 2: MCP Apps (Calculator + Pong) (Angular Client + MCP Server + Proxy Agent)¶
This sample verifies the sandbox with an Angular-based client, an MCP Proxy Agent, and a remote MCP Server. It requires three backend processes.
Step 1: Start the MCP Server (Calculator)¶
e3c17f1f (docs: add npm install step to MCP guide)
The MCP server starts on http://localhost:8000 using SSE transport.
Step 2: Start the MCP Apps Proxy Agent¶
In a new terminal:
The proxy agent starts on http://localhost:10006 by default.
Step 3: Build and Start the Angular Client¶
In a new terminal:
cd samples/client/angular/
# Build the renderers (required — Angular depends on local renderer packages)
npm run build:renderer
npm install --include=dev
npm run build:sandbox
npm start -- mcp_calculator
⚠️
--include=devis required: The Angular CLI (@angular/cli) is a dev dependency. Without--include=dev,ng servewon't be available.⚠️
build:rendererandbuild:sandboxare both required:build:renderercompiles the A2UI renderer packages that the Angular app depends on.build:sandboxbundles the sandbox proxy into the Angular project's public assets. Without either, the app won't work.
The client starts at http://localhost:4200/.
Step 4: Open in Browser¶
Navigate to:
What to expect: A set of smart chips to load calculator app or pong app will be rendered. Both apps run in their own sandboxed iframes.
| Calculator App | Pong App |
|---|---|
![]() |
![]() |
URL Options for Testing¶
For testing purposes, you can opt-out of the security self-test by using specific URL query parameters.
disable_security_self_test=true¶
This query parameter allows you to bypass the security self-test that verifies iframe isolation. This is useful for debugging and testing environments where the double-iframe setup may not pass strict origin checks (e.g., localhost development).
Example usage:
Troubleshooting¶
| Problem | Solution |
|---|---|
GEMINI_API_KEY environment variable not set |
Export the key or add a .env file in the agent directory |
Python version error on contact_lookup agent |
Install Python 3.13+ (required by that sample's pyproject.toml) |
npm run build:renderer fails |
Make sure you ran npm install first in samples/client/lit/ |
| Angular client shows blank page | Ensure you ran npm run build:sandbox before npm start |
| MCP app iframe doesn't load | Check that both the MCP server (port 8000) and proxy agent (port 10006) are running |
ng serve not found |
Run npm install --include=dev to install dev dependencies including @angular/cli |
| "URL with hostname not allowed" | Angular 21 restricts allowed hosts. Use localhost (the default) — do not pass --host 0.0.0.0 |
| Security self-test fails in dev | Add ?disable_security_self_test=true to the URL |

