Skip to content

Commit ecc5226

Browse files
committed
feat: Add MCP Calculator app & McpApp rendering component
Squashed commits: - feat: Add MCP Calculator app & McpApp rendering component - chore: Update the package.json in the Angular client samples
1 parent 15a73b7 commit ecc5226

7 files changed

Lines changed: 382 additions & 2 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ site/
2121
a2a_agents/python/a2ui_agent/src/a2ui/assets/**/*.json
2222
## new agent SDK path
2323
agent_sdks/python/src/a2ui/assets/**/*.json
24+
## Generated JS file from the strictly-typed `sandbox.ts`.
25+
samples/client/angular/projects/orchestrator/public/sandbox_iframe/sandbox.js

samples/client/angular/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"serve:ssr:contact": "node dist/contact/server/server.mjs",
1414
"build:renderer": "cd ../../../renderers && for dir in 'web_core' 'markdown/markdown-it'; do (cd \"$dir\" && npm install && npm run build); done",
1515
"serve:agent:restaurant": "cd ../../agent/adk/restaurant_finder && uv run .",
16-
"demo:restaurant": "npm run build:renderer && concurrently -k -n \"AGENT,WEB\" -c \"magenta,blue\" \"npm run serve:agent:restaurant\" \"npm start -- restaurant\""
16+
"demo:restaurant": "npm run build:renderer && concurrently -k -n \"AGENT,WEB\" -c \"magenta,blue\" \"npm run serve:agent:restaurant\" \"npm start -- restaurant\"",
17+
"build:sandbox": "esbuild projects/orchestrator/public/sandbox_iframe/sandbox.ts --bundle --outfile=projects/orchestrator/public/sandbox_iframe/sandbox.js --format=esm --platform=browser"
1718
},
1819
"prettier": {
1920
"printWidth": 100,
@@ -30,6 +31,7 @@
3031
"private": true,
3132
"dependencies": {
3233
"@a2a-js/sdk": "^0.3.4",
34+
"@modelcontextprotocol/ext-apps": "^1.2.0",
3335
"@a2ui/web_core": "file:../../../renderers/web_core",
3436
"@a2ui/markdown-it": "file:../../../renderers/markdown/markdown-it",
3537
"@angular/cdk": "^20.2.10",
@@ -83,6 +85,7 @@
8385
},
8486
"workspaces": [
8587
"projects/*",
86-
"../../../renderers/web_core"
88+
"../../../renderers/web_core",
89+
"../../../renderers/markdown/markdown-it"
8790
]
8891
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Sandbox Iframe
2+
3+
This directory contains the `sandbox.html` and its associated resources.
4+
5+
## Purpose
6+
7+
`sandbox.html` is designed to be loaded into an `<iframe>` to provide a secure, isolated environment for running MCP (Model Context Protocol) applications. It acts as a bridge between the host application (Orchestrator) and the untrusted or external MCP apps, managing communication via `postMessage`.
8+
9+
## Development
10+
11+
The logic for the sandbox is written in `sandbox.ts` and must be compiled to `sandbox.js` to be executable by the browser.
12+
13+
### Build Command
14+
15+
To generate `sandbox.js` from `sandbox.ts`, run the following command from the `samples/client/angular` directory:
16+
17+
```bash
18+
npm run build:sandbox
19+
```
20+
21+
This ensures that all dependencies (like `@modelcontextprotocol/ext-apps`) are correctly bundled.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!--
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
<!DOCTYPE html>
18+
<html lang="en">
19+
<head>
20+
<meta charset="UTF-8">
21+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
22+
<title>MCP App Sandbox</title>
23+
<style>
24+
body, html {
25+
margin: 0;
26+
padding: 0;
27+
width: 100%;
28+
height: 100%;
29+
overflow: hidden;
30+
}
31+
#content {
32+
width: 100%;
33+
height: 100%;
34+
border: none;
35+
}
36+
</style>
37+
</head>
38+
<body>
39+
<div id="content"></div>
40+
<script type="module" src="sandbox.js"></script>
41+
</body>
42+
</html>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import {
18+
AppBridge,
19+
PostMessageTransport,
20+
SANDBOX_PROXY_READY_METHOD,
21+
SANDBOX_RESOURCE_READY_METHOD
22+
} from '@modelcontextprotocol/ext-apps/app-bridge';
23+
24+
const contentDiv = document.getElementById('content');
25+
26+
// Initialize AppBridge
27+
const bridge = new AppBridge(
28+
null, // No client in sandbox
29+
{ name: 'MCP Sandbox', version: '1.0.0' },
30+
{
31+
serverTools: {},
32+
logging: {}
33+
}
34+
);
35+
36+
bridge.oncalltool = async (params: any) => {
37+
// Forward tool calls to parent if needed, or handle locally
38+
// For now, we just log
39+
console.log('[Sandbox] Tool call:', params);
40+
// We can also postMessage back to parent manually if bridge doesn't handle it
41+
// implementation of forwarding logic would go here
42+
// For now, throw to indicate not implemented in sandbox
43+
throw new Error('Tool execution not supported in sandbox directly');
44+
};
45+
46+
// Notify parent we are ready (standard)
47+
window.parent.postMessage({ method: SANDBOX_PROXY_READY_METHOD }, window.location.origin);
48+
49+
// Listen for resource ready message
50+
window.addEventListener('message', async (event) => {
51+
// Validate the origin of incoming messages
52+
if (event.origin !== window.location.origin) {
53+
return;
54+
}
55+
56+
const data = event.data;
57+
if (data && data.method === SANDBOX_RESOURCE_READY_METHOD) {
58+
const { html, sandbox } = data.params;
59+
60+
if (html && contentDiv) {
61+
// Create an inner iframe with srcdoc to enable Javascript execution if any.
62+
const innerFrame = document.createElement('iframe');
63+
innerFrame.srcdoc = html;
64+
innerFrame.style.width = '100%';
65+
innerFrame.style.height = '100%';
66+
innerFrame.style.border = 'none';
67+
if (sandbox) {
68+
innerFrame.sandbox = sandbox;
69+
}
70+
71+
// Clear any existing content and inject the new iframe
72+
contentDiv.innerHTML = '';
73+
contentDiv.appendChild(innerFrame);
74+
}
75+
}
76+
});
77+
78+
// Initialize transport with parent
79+
const transport = new PostMessageTransport(window.parent, window.parent);
80+
await bridge.connect(transport);

samples/client/angular/projects/orchestrator/src/a2ui-catalog/catalog.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,14 @@ export const DEMO_CATALOG = {
3838
inputBinding('title', () => ('title' in properties && properties['title']) || undefined),
3939
],
4040
},
41+
McpApp: {
42+
type: () => import('./mcp-app').then((r) => r.McpApp),
43+
bindings: ({ properties }) => [
44+
inputBinding(
45+
'content',
46+
() => ('content' in properties && properties['content']) || undefined,
47+
),
48+
inputBinding('title', () => ('title' in properties && properties['title']) || undefined),
49+
],
50+
},
4151
} as Catalog;

0 commit comments

Comments
 (0)