Skip to content

Commit 4a36aad

Browse files
authored
Implement various improvements to A2UI and editor app, including Accept/Reject CTAs, MCP tool allowlisting, model selection, and dependency linking. (#1363)
1 parent f13281e commit 4a36aad

21 files changed

Lines changed: 2464 additions & 65 deletions

samples/mcp/a2ui-in-mcpapps/README.md

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ This sample demonstrates a Model Context Protocol (MCP) Application Host that is
66

77
- **`client/`**: The host container application (Angular). It hosts the outer safe iframe.
88
- **`server/`**: The MCP Server (Python/uv) that provides the micro-app resources and tools.
9-
- **`server/apps/src/`**: The source source code for the server-hosted isolated micro-app.
9+
- **`server/apps/src/`**: Source code for the **Basic** isolated micro-app.
10+
- **`server/apps/editor/`**: Source code for the **Editor** isolated micro-app.
1011

1112
## Communication Flow
1213

@@ -50,14 +51,31 @@ sequenceDiagram
5051

5152
## Prerequisites
5253

53-
- [Node.js](https://nodejs.org/) (runs the client and build scripts)
54-
- [Python 3.10+](https://www.python.org/) with `uv` (runs the MCP server)
54+
- [Node.js](https://nodejs.org/) (LTS recommended)
55+
- [Python 3.10+](https://www.python.org/) with `uv`
56+
57+
### ⚠️ IMPORTANT: Pre-build Core Dependencies
58+
59+
The sample apps link to local versions of the A2UI SDK. You **must build the core libraries** before attempting to run `npm install` inside any sample subdirectories.
60+
61+
Run the following from the repository root:
62+
63+
```bash
64+
# 1. Web Core
65+
cd renderers/web_core && npm install && npm run build && cd ../..
66+
67+
# 2. Markdown Utilities
68+
cd renderers/markdown/markdown-it && npm install && npm run build && cd ../../..
69+
70+
# 3. Angular Renderer SDK
71+
cd renderers/angular && npm install && npm run build && cd ../..
72+
```
5573

5674
---
5775

5876
## Build & Regeneration
5977

60-
This sample relies on some generated bundle artifacts. Some are committed for convenience, while others are ignored and must be built.
78+
This sample relies on generated bundle artifacts.
6179

6280
### 1. Build Client Sandbox Bridge
6381

@@ -71,19 +89,29 @@ npm run build:sandbox
7189

7290
_(Generates `client/public/sandbox_iframe/sandbox.{js,html}`)_
7391

74-
### 2. Rebuild the Server Hosted App (Optional)
92+
### 2. Rebuild Micro-Apps (Optional)
93+
94+
The server serves single-file HTML artifacts located in `server/apps/public/`. Choose the app you want to build:
95+
96+
#### Option A: The Editor App
97+
98+
```bash
99+
cd server/apps/editor
100+
npm install
101+
npm run build:all
102+
```
75103

76-
The server serves a bundled `app.html` artifact located in `server/apps/public/app.html`. If you modify the source code in `server/apps/src/`, you must regenerate this list:
104+
_(Generates `server/apps/public/editor.html`)_
77105

78-
Run this in the `server/apps/src/` directory:
106+
#### Option B: The Basic App
79107

80108
```bash
81109
cd server/apps/src
82110
npm install
83111
npm run build:all
84112
```
85113

86-
_(Runs Angular compilation and triggers `node inline.js` to single-file inline it into `server/apps/public/app.html`)_
114+
_(Generates `server/apps/public/app.html`)_
87115

88116
---
89117

samples/mcp/a2ui-in-mcpapps/client/angular.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
33
"version": 1,
44
"cli": {
5-
"packageManager": "npm"
5+
"packageManager": "npm",
6+
"analytics": "322f21ca-3fe1-45b7-b271-10523235cd8e"
67
},
78
"newProjectRoot": "projects",
89
"projects": {

samples/mcp/a2ui-in-mcpapps/client/src/app/app.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,23 @@
1818
<div class="header">
1919
<h1>Simple MCP Apps Host</h1>
2020
<p>Status: {{ status() }}</p>
21+
<div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center">
22+
<label for="app-selector" style="font-weight: bold">Select App:</label>
23+
<select
24+
id="app-selector"
25+
#appSelector
26+
(change)="onAppChange(appSelector.value)"
27+
[disabled]="isAppLoading()"
28+
style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; font-size: 14px"
29+
>
30+
<option value="editor" [selected]="selectedApp() === 'editor'">
31+
Generative Editor App
32+
</option>
33+
<option value="basic" [selected]="selectedApp() === 'basic'">Basic Counter App</option>
34+
</select>
35+
</div>
2136
<button (click)="connectAndLoadApp()" [disabled]="isAppLoading()">
22-
{{ isAppLoading() ? 'Loading...' : 'Load MCP App' }}
37+
{{ isAppLoading() ? 'Loading...' : 'Load Selected App' }}
2338
</button>
2439
</div>
2540

samples/mcp/a2ui-in-mcpapps/client/src/app/app.ts

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,17 @@ export class App implements AfterViewInit {
3232
private messageListenerAdded = false;
3333
protected readonly mcpAppHtmlUrl = signal<string | null>(null);
3434
protected readonly isAppLoading = signal<boolean>(false);
35+
protected readonly selectedApp = signal<'editor' | 'basic'>('editor');
3536

3637
private mcpClient: Client | null = null;
3738

39+
private readonly allowedTools = new Set([
40+
'fetch_counter_a2ui',
41+
'increase_counter',
42+
'smart_editor_get_controls',
43+
'smart_editor_apply',
44+
]);
45+
3846
ngAfterViewInit() {
3947
if (this.messageListenerAdded) return;
4048
this.messageListenerAdded = true;
@@ -75,40 +83,50 @@ export class App implements AfterViewInit {
7583
window.location.origin,
7684
);
7785
}
78-
} else if (data?.method === 'ui/fetch_counter_a2ui') {
79-
if (data.id && target && this.mcpClient) {
80-
this.mcpClient
81-
.callTool({
82-
name: 'fetch_counter_a2ui',
83-
arguments: {},
84-
})
85-
.then(result => {
86-
target.postMessage(
87-
{
88-
jsonrpc: '2.0',
89-
id: data.id,
90-
result: result.content,
91-
},
92-
window.location.origin,
93-
);
94-
})
95-
.catch(error => {
96-
target.postMessage(
97-
{
98-
jsonrpc: '2.0',
99-
id: data.id,
100-
error: {message: error.message},
86+
} else if (data?.method === 'ui/initialize') {
87+
if (data.id && target) {
88+
target.postMessage(
89+
{
90+
jsonrpc: '2.0',
91+
id: data.id,
92+
result: {
93+
hostCapabilities: {
94+
displayModes: ['inline'],
10195
},
102-
window.location.origin,
103-
);
104-
});
96+
},
97+
},
98+
window.location.origin,
99+
);
100+
}
101+
} else if (data?.method === 'ui/resize') {
102+
const height = data.params?.height;
103+
if (typeof height === 'number') {
104+
iframe.style.height = `${height}px`;
105+
}
106+
} else if (data?.method?.startsWith('ui/')) {
107+
// Generic tool relay for unknown verbs
108+
const toolName = data.method.replace('ui/', '');
109+
110+
if (!this.allowedTools.has(toolName)) {
111+
console.warn(`[Host] Blocked unauthorized tool call: ${toolName}`);
112+
if (data.id && target) {
113+
target.postMessage(
114+
{
115+
jsonrpc: '2.0',
116+
id: data.id,
117+
error: {message: `Tool '${toolName}' is not whitelisted.`},
118+
},
119+
window.location.origin,
120+
);
121+
}
122+
return;
105123
}
106-
} else if (data?.method === 'ui/increase_counter') {
124+
107125
if (data.id && target && this.mcpClient) {
108126
this.mcpClient
109127
.callTool({
110-
name: 'increase_counter',
111-
arguments: {},
128+
name: toolName,
129+
arguments: data.params || {},
112130
})
113131
.then(result => {
114132
target.postMessage(
@@ -131,30 +149,18 @@ export class App implements AfterViewInit {
131149
);
132150
});
133151
}
134-
} else if (data?.method === 'ui/initialize') {
135-
if (data.id && target) {
136-
target.postMessage(
137-
{
138-
jsonrpc: '2.0',
139-
id: data.id,
140-
result: {
141-
hostCapabilities: {
142-
displayModes: ['inline'],
143-
},
144-
},
145-
},
146-
window.location.origin,
147-
);
148-
}
149-
} else if (data?.method === 'ui/resize') {
150-
const height = data.params?.height;
151-
if (typeof height === 'number') {
152-
iframe.style.height = `${height}px`;
153-
}
154152
}
155153
});
156154
}
157155

156+
onAppChange(value: string) {
157+
if (value === 'editor' || value === 'basic') {
158+
this.selectedApp.set(value);
159+
} else {
160+
console.error(`[Host] Invalid app selected: ${value}`);
161+
}
162+
}
163+
158164
async connectAndLoadApp() {
159165
this.status.set('Connecting to MCP Server...');
160166
this.isAppLoading.set(true);
@@ -164,7 +170,7 @@ export class App implements AfterViewInit {
164170
const transport = new SSEClientTransport(new URL('http://127.0.0.1:8000/sse'));
165171
const client = new Client(
166172
{
167-
name: 'basic-host',
173+
name: 'editor-host',
168174
version: '1.0.0',
169175
},
170176
{
@@ -176,10 +182,12 @@ export class App implements AfterViewInit {
176182
await client.connect(transport);
177183
this.mcpClient = client;
178184

179-
this.status.set('Calling get_basic_app tool...');
185+
this.status.set('Calling MCP App tool...');
186+
const toolName = this.selectedApp() === 'editor' ? 'get_editor_app' : 'get_basic_app';
187+
180188
// 2. Call the tool to get the app
181189
const result = await client.callTool({
182-
name: 'get_basic_app',
190+
name: toolName,
183191
arguments: {},
184192
});
185193

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# A2UI-Powered Document Editor Micro-App
2+
3+
This directory contains the source code for the generative document editor micro-app based on Angular and Editor.js.
4+
5+
It is built as a standalone static bundle and served by the MCP server as an isolated resource to be rendered securely within the host container.
6+
7+
---
8+
9+
## Prerequisites
10+
11+
### 1. Node.js
12+
13+
Ensure you have Node.js installed (LTS v20 or v22 is recommended).
14+
15+
If Node.js is missing from your PATH, consider installing it via NVM (Node Version Manager):
16+
17+
```bash
18+
# Download and install NVM
19+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
20+
21+
# Refresh terminal
22+
source ~/.bashrc
23+
24+
# Install LTS Node.js
25+
nvm install --lts
26+
```
27+
28+
### 2. Build Core A2UI Libraries
29+
30+
This application has file-based dependencies on the core A2UI packages which reside elsewhere in this repository. You **must build these packages first** in specific order before this application's `npm install` will succeed.
31+
32+
Run the following commands from the **root of the repository**:
33+
34+
```bash
35+
# 1. Build Web Core
36+
cd renderers/web_core
37+
npm install
38+
npm run build
39+
40+
# 2. Build Markdown Utilities
41+
cd renderers/markdown/markdown-it
42+
npm install
43+
npm run build
44+
45+
# 3. Build Angular Renderer
46+
cd renderers/angular
47+
npm install
48+
npm run build
49+
50+
cd ../../
51+
```
52+
53+
---
54+
55+
## Local Execution Workflow
56+
57+
To execute and run this application locally, you need to build the application bundle, ensure the host environment assets are ready, and then boot the services.
58+
59+
### Step 1: Build the Editor App Bundle
60+
61+
From inside this directory (`samples/mcp/a2ui-in-mcpapps/server/apps/editor`):
62+
63+
```bash
64+
# Install local package dependencies
65+
npm install --legacy-peer-deps
66+
67+
# Build Angular project AND generate the single self-contained HTML file
68+
npm run build:all
69+
```
70+
71+
_This outputs the final static artifact into `../public/editor.html` which the Python server reads._
72+
73+
### Step 2: Build the Client Host Bridge (Required Once)
74+
75+
Navigate to the client host directory and build its security-sandbox bridge:
76+
77+
```bash
78+
cd ../../../client
79+
npm install
80+
npm run build:sandbox
81+
```
82+
83+
### Step 3: Run the Full Local Environment
84+
85+
Open two terminals to start the stack:
86+
87+
#### Terminal A: Run MCP Server (Python)
88+
89+
```bash
90+
cd samples/mcp/a2ui-in-mcpapps/server
91+
uv sync
92+
uv run python server.py --transport sse --port 8000
93+
```
94+
95+
#### Terminal B: Run Host Web App (Angular)
96+
97+
```bash
98+
cd samples/mcp/a2ui-in-mcpapps/client
99+
npm run start
100+
```
101+
102+
#### Access the Application
103+
104+
Visit **[http://localhost:4200](http://localhost:4200)** in your browser to load the host container, which will automatically load this Editor app via the MCP server connection.

0 commit comments

Comments
 (0)