news 2026/9/15 21:57:16

A2UI Angular 客户端实战:通过 MCP Apps、Iframe URL 与 Srcdoc 三种方式嵌入外部 Web 应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
A2UI Angular 客户端实战:通过 MCP Apps、Iframe URL 与 Srcdoc 三种方式嵌入外部 Web 应用

A2UI Angular 客户端实战:通过 MCP Apps、Iframe URL 与 Srcdoc 三种方式嵌入外部 Web 应用

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

本文基于samples/community/client/angular/projects/mcp_calculator示例,讲解如何在 A2UI Angular 客户端中加载并展示一个由 Agent 驱动的交互式 Pong 游戏界面。该示例通过同一套 A2UI 协议演示了三种截然不同的 Web 应用嵌入路径——MCP Apps(沙箱内嵌)、Iframe by URL(远程地址直载)与 Iframe by Srcdoc(内联 HTML 注入),读者读完可以掌握完整的运行配置、三种嵌入方式的原理与差异、以及沙箱隔离与消息安全的核心实现要点,并能够直接复刻到自己的 A2UI 客户端项目中。

示例概览:一个客户端、三种召唤方式

mcp_calculator是一个 Angular 单页应用,它通过 A2A 协议连接后端的 MCP Apps Proxy Agent,由 Agent 根据用户指令返回对应的 A2UI 布局 payload,客户端据此渲染出 Pong 游戏界面与计分板。示例的独特之处在于,它把"如何把一个 Web 游戏嵌入到客户端"这一问题的三种解法放在同一个界面里对比:

召唤方式对应 A2UI 组件内容来源关键特征
MCP AppsMcpAppAgent 将 HTML 与 JS 逻辑直接打包进组件 payload沙箱 iframe + JSON-RPC over window messaging
Iframe by URLWebAppFrameUrl远程 Pong Web 服务器的 URL(http://localhost:8081/pong_app_web_frame.html浏览器直接从 HTTP 端点加载游戏帧
Iframe by SrcdocWebAppFrameSrcdocAgent 在后端抓取远程 HTML 后内联传输使用 iframe 的srcdoc属性渲染 HTML 字符串

这三种方式共享同一个游戏逻辑(Pong),但内容投递与隔离策略完全不同,非常适合作为理解 A2UI 嵌入式内容渲染机制的对照实验。

环境准备与依赖

运行本示例需要满足以下前置条件:

  1. Node.js 18 或更高版本,以及Yarn包管理器;
  2. Python 3.9 或更高版本,并安装uv
  3. Pong Web 服务器:托管游戏端点,运行说明见 samples/community/web/pong/README.md;
  4. MCP Apps Proxy Agent 后端:运行说明见 samples/community/agent/adk/mcp_app_proxy/README.md。

其中 Agent 后端基于 Agent Development Kit(ADK)与 A2A 协议构建,本身是一个 A2A 服务器,需要配置 LLM 的 API Key(复制.env.example.env后填写)。

完整运行步骤

按照以下顺序依次启动即可跑通整个示例:

第 1 步:构建仓库根目录的共享工作区依赖

yarn build:all

该命令会构建 A2UI 的共享 workspace 依赖(如@a2ui/angular@a2ui/web_core等),供后续的示例项目引用。

第 2 步:安装samples/community目录下的本地依赖

cd samples/community yarn install

第 3 步:启动 Pong Web 服务器

cd samples/community/web/pong uv run .

服务器会监听8081端口,控制台输出Serving at port 8081。它实际是一个 Python HTTP 静态服务器:请求/pong_app_web_frame.html时,动态读取共享的pong_base.htmlpong_engine.js(位于samples/community/agent/adk/mcp_app_proxy/目录),注入本地桥接脚本pong_web_frame_bridge.js后组装成完整页面,并附带相应的 CORS 头。它还额外提供了/pong_app_web_frame_srcdoc.html端点,供WebAppFrameSrcdoc场景由 Agent 在后端远程抓取。

第 4 步:在另一个终端启动 Agent 后端

cd samples/community/agent/adk/mcp_app_proxy uv run .

第 5 步:启动 Angular 应用

cd samples/community/client/angular yarn start mcp_calculator

第 6 步:打开浏览器访问

http://localhost:4200/?disable_security_self_test=true

URL 中的disable_security_self_test查询参数用于关闭沙箱 iframe 的原生安全自测(origin toggle 相关检查),便于在本地开发环境中直接运行。

界面交互:三个建议按钮触发三种渲染路径

应用加载后,主界面(基于A2aChatCanvas构建,Agent 名称显示为 "MCP Calculator",见 app.ts)会展示三个建议按钮:

  • Open Pong as MCP App
  • Open Pong from remote web server
  • Open Pong with WebApp Srcdoc

点击任意按钮,客户端即向后端 Agent 发送一条消息(通过ChatService.sendMessage),Agent 返回对应的 A2UI 布局 payload,界面随即更新为游戏区域 + 计分板 + 解说组件。计分板是 A2UI 原生组件(PongScoreBoard),通过playerScorecpuScorecommentary三个动态属性实时展示比分与解说;而游戏区域则由三种不同的嵌入组件分别承载。整个布局由 pong-layout.ts 中的PongLayout组件组合而成。

方式一:MCP Apps 沙箱内嵌

这是三种方式中能力最完整的一种。Agent 将游戏的全部 HTML 与 JavaScript 逻辑直接写入McpApp组件的htmlContent属性,客户端在沙箱 iframe 中渲染,宿主与内嵌应用之间通过 window messaging 上的 JSON-RPC 协议通信。

组件属性契约

从 catalog.ts 中McpAppSchema的定义可以看到McpApp支持的属性:

属性类型说明
htmlContent动态字符串游戏 HTML 内容,支持url_encoded:前缀的 URL 编码形式
allowedTools字符串数组允许内嵌应用调用的工具白名单
allowedFunctions字符串数组允许内嵌应用调用的本地函数白名单
data动态值数据绑定路径映射,实现与宿主 Data Model 的双向同步
title动态字符串iframe 的标题

渲染链路与桥接机制

McpApp 组件的渲染流程可以概括为:

  1. 内容解析:读取htmlContent,若以url_encoded:开头则先decodeURIComponent解码;
  2. 加载沙箱宿主:iframe 首先加载同源的mcp_apps_inner_iframe/sandbox.html(不直接加载游戏内容),从而获得宿主页面的window.location.origin
  3. 桥接握手:沙箱页面加载完成后通过SANDBOX_PROXY_READY_METHOD消息通知宿主,宿主收到后通过AppBridge+PostMessageTransport建立 JSON-RPC 连接,并把解析出的 HTML 通过sendSandboxResourceReady注入沙箱,sandbox属性为allow-scripts
  4. 双向数据绑定
    • 宿主侧通过surface.dataModel.subscribe(dataPath, ...)订阅 Data Model 变化,把增量变化以ui/notifications/data-model-update通知推送给应用;对对象类型还会逐键 diff,避免子路径相互覆盖;
    • 应用侧通过ui/notifications/data-model-change通知写回宿主,写入前会经过validateMessageSecurity安全校验,并用isProcessingAppWrite标志抑制回显,防止反馈循环;
  5. 函数与工具调用
    • 应用发起ui/requests/function-call时,先校验参数安全性,再检查函数名是否在allowedFunctions白名单内,最终通过surface.catalog.invoker在客户端本地执行;
    • 应用发起工具调用oncalltool时,先校验参数与allowedTools白名单,再通过surface.dispatchAction以 Action 形式派发给宿主处理;
  6. 动态尺寸同步:应用可通过onsizechange回调请求调整尺寸,宿主侧实现了一整套节流策略——宽度钳制在 200–3000px、高度钳制在 100–2000px,变化超过 5px 阈值才生效,且用 100ms 定时器节流,避免频繁重排;同时用ResizeObserver把宿主容器尺寸反向同步给应用(setHostContext)。

组件销毁时还会依次清理数据订阅、消息监听、ResizeObserver并关闭AppBridge,避免内存泄漏与僵尸事件监听。

方式二:Iframe by URL 远程直载

WebAppFrameUrl组件的载荷只有一个 URL,指向远程 Pong 服务器(http://localhost:8081/pong_app_web_frame.html),由浏览器直接加载该页面。它的核心价值在于:游戏逻辑完全托管在远端,客户端只负责提供一个安全的外壳。

从 web-app-frame-url.ts 的实现可以看到几个关键的安全设计:

  • 协议白名单targetUrl计算时先通过new URL()解析,仅允许http:https:协议,从根源上阻止javascript:data:file:等协议的注入;
  • origin 传递:URL 上会附加origin查询参数(当前客户端窗口的window.location.origin),供远端页面识别宿主来源;
  • 期望源校验expectedOrigin从 URL 推导出目标页面的 origin,沙箱握手时据此校验消息来源;
  • 宿主外壳复用:iframe 首先加载同源的sandbox-url.html沙箱宿主,待SandboxResourceReady握手后,再把带 origin 参数的目标 URL 通过 postMessage 交给沙箱去加载,从而保证远程内容在受控的沙箱中渲染。

方式三:Iframe by Srcdoc 内联注入

WebAppFrameSrcdoc与方式二的差别在于内容投递路径:Agent 在后端主动抓取远程 Web 服务器的游戏 HTML(pong_app_web_frame_srcdoc.html),把原始 HTML 字符串内联到htmlContent属性中随 A2UI payload 一起传输,客户端再把该字符串塞进 iframe 的srcdoc属性渲染。

由于 HTML 内容完全来自(可能不可信的)Agent,web-app-frame-srcdoc.ts 在注入前做了两道强化防护:

  1. 强制注入 Content-Security-Policy(CSP):任何作者自带的 CSP meta 标签都会被剥离,并注入如下受限默认策略:
default-src 'self' 'unsafe-inline' 'unsafe-eval' data:; connect-src 'none'; form-action 'none'; base-uri 'none'; object-src 'none'; frame-src 'none';

该策略的效果是:connect-src 'none'阻断fetchXMLHttpRequestWebSocketEventSource等一切外发网络连接;form-action 'none'封堵表单提交外泄路径;base-uri 'none'阻止基址劫持;object-srcframe-src禁止插件对象与嵌套子帧。

  1. 注入链接点击拦截器:页面内所有<a>链接点击都会被拦截,除锚点与javascript:外一律preventDefault,并把目标 URL 以a2ui_action/open_url消息转发给宿主,由宿主决定如何处理,而不是让沙箱内页面直接跳转。

同时sandbox属性被设定为allow-scripts allow-forms allow-modals——刻意省略allow-same-origin(保持源隔离)、allow-top-navigation(防框架逃逸)与allow-popups(防一键链接外泄),且消息发送目标固定为window.location.origin,保证消息只在同源宿主间传递。

三种方式的取舍与选型建议

综合源码实现,三种方式形成了清晰的取舍光谱:

  • MCP Apps(McpApp:能力最完整——支持双向数据绑定、本地函数调用、工具调用白名单与动态尺寸协商,适合需要与宿主深度交互、共享 A2UI Data Model 的嵌入式应用;代价是需要在客户端实现完整的AppBridge桥接逻辑,且所有交互消息都要经过安全校验与白名单过滤(validateMessageSecurity在数据写回、函数参数、工具参数三个入口都会执行,见 web-frame-messages.ts)。
  • Iframe by URL(WebAppFrameUrl:实现最轻、隔离性最好,内容托管在远端可独立演进;适合内容更新频繁、不需要与宿主数据模型深交互的场景,代价是依赖远端服务器可用性与 CORS 配置。
  • Iframe by Srcdoc(WebAppFrameSrcdoc:把"从 URL 加载"变成"内容随载荷走",一次握手即可注入,且通过强制 CSP 与链接拦截提供了最严格的静态内容防护;适合内容由 Agent 聚合、但无需与宿主双向通信的场景。

扩展实验:自定义目录组件与本地函数

mcp_calculator还展示了如何为客户端定义一套完整的自定义目录。catalog.ts 中的DEMO_CATALOG注册了六个组件(McpAppPongScoreBoardPongLayoutWebAppFrameUrlWebAppFrameSrcdocColumn),其 Catalog ID 与 Agent 侧的mcp_app_catalog.json保持一致(https://a2ui.org/samples/community/agent/adk/mcp_app_proxy/catalogs/0.9/mcp_app_catalog.json),保证 Agent 返回的 payload 能被客户端正确解析。

此外,示例还通过createFunctionImplementation注册了showWinnerModal本地函数:比赛结束时内嵌应用调用该函数,客户端弹出胜者对话框,"Play Again" 按钮通过context.surface.dataModel.set('/pong_state/player_score', 0)等调用直接重置 A2UI Data Model 中的比分——这正是"本地函数执行"与"数据模型回写"两个机制的直观演示。

安全提醒

A2UI 的嵌入式内容机制(iframes、web views)会引入不可信内容,参照 mcp_app_proxy README 的免责声明,Agent 返回的任何 UI 定义与数据流都应视为不可信输入:恶意 Agent 可能伪造界面诱导用户(钓鱼)、通过属性值注入恶意脚本(XSS)或构造过重的布局拖垮客户端(DoS)。本文示例中的协议白名单、期望源校验、CSP 注入、链接拦截与allowedTools/allowedFunctions白名单机制,正是生产环境必备的加固手段;在实际项目中,还应对 AgentCard、消息与任务状态等全部外部数据做同样严格的清洗与隔离。

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 21:57:14

Ozlo睡眠监测平台:医疗级精度与消费级体验的融合

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:04:45

基于Vue的uni-app小程序开发:农家书屋项目全解析

简介&#xff1a;基于Vue框架的农家书屋小程序设计源码&#xff0c;是一套面向小程序开发者和前端学习者的完整工程&#xff0c;适合用Vue技术栈构建乡村数字化阅读服务场景。资源共含941个文件&#xff0c;压缩包约25.94MB&#xff0c;主要涵盖248个JavaScript脚本、229个Vue组…

作者头像 李华
网站建设 2026/9/14 20:04:10

DeepSeek 跑 128K 长文档总结:Key 用 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:03:52

QGIS中CRS选择与坐标转换实践指南

1. QGIS中的CRS基础概念与选择逻辑在地理信息系统&#xff08;GIS&#xff09;工作中&#xff0c;坐标参考系统&#xff08;Coordinate Reference System&#xff0c;简称CRS&#xff09;的选择直接影响空间数据的定位精度和后续分析结果。QGIS作为开源GIS软件的代表&#xff0…

作者头像 李华
网站建设 2026/9/14 20:01:17

Unity正式包防调试代码混入:条件编译与日志治理实战

先问一个很现实的问题&#xff1a;你上一次在 Unity 的正式包里发现 Debug.Log 刷屏、调试图标乱入、甚至按住屏幕某个角落就能呼出作弊菜单&#xff0c;是什么时候&#xff1f;如果你心想"啊&#xff0c;还好没人发现"&#xff0c;那这篇就是给你写的。调试代码混进…

作者头像 李华