-
Notifications
You must be signed in to change notification settings - Fork 1k
docs: add guide for MCP Apps integration in A2UI surface #995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| # 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. | ||
|
|
||
| ## 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 | ||
|
|
||
| 1. **Sandbox Proxy (`sandbox.html`)**: An intermediate `iframe` served 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.ts`). | ||
| - Host origin validation: Validates that messages come from the expected host origin. | ||
| 2. **Embedded App (Inner Iframe)**: The innermost `iframe`. Injected dynamically via `srcdoc` with restricted permissions. | ||
| - Permissions: `sandbox="allow-scripts allow-forms allow-popups allow-modals"` (**MUST NOT** include `allow-same-origin`). | ||
| - Isolation: Removes access to `localStorage`, `sessionStorage`, `IndexedDB`, and cookies due to unique origin. | ||
|
|
||
| ### Architecture Diagram | ||
|
|
||
| ```mermaid | ||
| graph TD | ||
| subgraph Host Application (A2UI) | ||
| A[A2UI Page] --> B[Host Component e.g., McpApp] | ||
| end | ||
| subgraph Sandbox Proxy (Same-Origin) | ||
| B -->|Message Relay| C[iframe sandbox.html] | ||
| end | ||
| subgraph Embedded App (Cross-Origin/Isolated) | ||
| 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: | ||
|
|
||
| ```typescript | ||
| 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, you send an A2UI message that translates to this custom node. | ||
|
|
||
| ```json | ||
| { | ||
| "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: | ||
|
|
||
| ```json | ||
| { | ||
| "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_METHOD` message from the proxy. | ||
| - **Bridging**: An `AppBridge` handles message relaying. Developers (specifically the MCP App Developer inside the untrusted iframe) can call tools on the MCP server using `bridge.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 (uv)** (version 3.12 or higher suggested) | ||
| - **Node.js (npm)** (version 18 or higher recommended) | ||
|
|
||
| ## Samples | ||
|
|
||
| There are two primary samples demonstrating MCP Apps integration: | ||
|
|
||
| ### 1. Contact Multi-Surface 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/contact_multiple_surfaces/` | ||
| - Command: `uv run .` (requires `GEMINI_API_KEY` in `.env`) | ||
| - **Lit Client App**: | ||
| - Path: `samples/client/lit/contact/` | ||
| - Command: `npm run dev` (requires building the Lit renderer first) | ||
| - URL: `http://localhost:5173/` | ||
|
|
||
| **What to expect**: A contact page where actions prompt an app interface on specific interactions. | ||
|
|
||
| ### 2. MCP Apps (Calculator) (Angular) | ||
|
|
||
| This sample verifies the sandbox with an Angular-based client, an MCP Proxy Agent, and a remote MCP Server. | ||
|
|
||
| - **MCP Server (Calculator)**: | ||
| - Path: `samples/agent/mcp/mcp-apps-calculator/` | ||
| - Command: `uv run .` (runs on port 8000) | ||
| - **MCP Apps Proxy Agent**: | ||
| - Path: `samples/agent/adk/mcp_app_proxy/` | ||
| - Command: `uv run .` (requires `GEMINI_API_KEY` in `.env`) | ||
| - **Angular Client App**: | ||
| - Path: `samples/client/angular/` | ||
| - Command: `npm start -- mcp_calculator` (requires `npm run build:sandbox` and `npm install`) | ||
| - URL: `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. | ||
|
|
||
| ## 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. | ||
|
|
||
| Example usage: | ||
| `http://localhost:4200/?disable_security_self_test=true` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,7 @@ nav: | |
| - Authoring Custom Components: guides/authoring-components.md | ||
| - Theming & Styling: guides/theming.md | ||
| - A2UI over MCP: guides/a2ui_over_mcp.md | ||
| - MCP Apps integration in A2UI Surface: guides/mcp-apps-in-a2ui-surface.md | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The navigation entry title "MCP Apps integration in A2UI Surface" is inconsistent with the document's main heading "# MCP Apps Integration in A2UI Surfaces". For better consistency and user experience, it's recommended to align the navigation title with the document's actual title. - MCP Apps Integration in A2UI Surfaces: guides/mcp-apps-in-a2ui-surface.md
Comment on lines
58
to
+59
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mcp apps in a2ui surfaces |
||
| - Reference: | ||
| - Component Gallery: reference/components.md | ||
| - Message Reference: reference/messages.md | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
inputBindinglogic forcontentandtitlecan be simplified using the nullish coalescing operator (??). This improves readability and conciseness, and explicitly handles cases where the property might benullby converting it toundefined, aligning with common Angular input binding patterns.For example,
('content' in properties && properties['content']) || undefinedcan be replaced withproperties['content'] ?? undefined.