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.
Looking for the core A2UI-over-MCP protocol? See A2UI over MCP for how to return A2UI JSON payloads from MCP tool calls.
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.
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.
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.
- 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:
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
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.
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;In the Host or Agent context, you 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 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).
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.
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 exportGEMINI_API_KEYin your shell or create a.envfile in each agent directory. The agents usedotenvto load.envfiles automatically.# Option 1: Export in shell export GEMINI_API_KEY="your-api-key-here" # Option 2: Create .env file in the agent directory echo 'GEMINI_API_KEY=your-api-key-here' > .env
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.
This sample verifies the sandbox with a Lit-based client and an ADK-based A2A agent.
cd samples/agent/adk/contact_lookup/
export GEMINI_API_KEY="your-key" # or use a .env file
uv run .
⚠️ Python version: This agent requires Python ≥ 3.13 (see itspyproject.toml). Ifuv run .fails with a Python version error, ensure you have Python 3.13+ available.
The agent starts on http://localhost:10003 by default.
In a new terminal, build the renderers (required before the client can run):
cd samples/client/lit/
npm install
npm run build:renderer
⚠️ First-time build: Thebuild:rendererscript builds three packages (web_core,markdown-it, andlitrenderer) in sequence. This may take a minute on the first run.
cd samples/client/lit/
npm run serve:shellThe client starts at http://localhost:5173/.
💡 Shortcut: You can run agent + client together with:
cd samples/client/lit/ npm run demo:contactThis builds the renderer and starts both the shell and the contact_lookup agent concurrently.
What to expect: A contact page where actions prompt an app interface on specific interactions.
This sample verifies the sandbox with an Angular-based client, an MCP Proxy Agent, and a remote MCP Server. It requires three backend processes.
cd samples/agent/mcp/mcp-apps-calculator/
uv run .The MCP server starts on http://localhost:8000 using SSE transport.
In a new terminal:
cd samples/agent/adk/mcp_app_proxy/
export GEMINI_API_KEY="your-key" # or use a .env file
uv run .The proxy agent starts on http://localhost:10006 by default.
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/.
Navigate to:
http://localhost:4200/?disable_security_self_test=true
What to expect: A basic calculator will be rendered. You can execute arithmetic calculations cleanly through the sandbox.
For testing purposes, you can opt-out of the security self-test by using specific URL query parameters.
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:
http://localhost:4200/?disable_security_self_test=true
| 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 |
