新闻详情

新闻详情

首页 / 资讯中心 / 详情

MCP工具UI方案:直接返回HTML还是采用A2UI结构化描述协议?

发布时间:2026/9/8 23:23:40来源:尧图网络
MCP工具UI方案:直接返回HTML还是采用A2UI结构化描述协议?
上个月我接了一个 MCP 工具想着“这回用 AI 自动生成表单总算能省掉自己写 UI 的功夫了”。结果工具返回了一段完整的 HTML从!doctype html到/html一应俱全。我把它贴到浏览器里渲染效果确实漂亮可一放进自己客户端的界面麻烦事接二连三——样式被 iframe 拦了一道暗黑模式下页面白得刺眼按钮点击掉了响应连用户输入的校验状态都拿不回来。这个问题最近在 MCP 生态的开发者群里被反复问MCP Apps 直接返回 HTML 不就好了吗客户端拿个 WebView 一裹什么界面都能渲染为什么还要 A2UI 这种结构化 UI 描述协议今天我就从实际接入的视角把这件事拆开讲清楚包括两种方案各自背后的取舍、安全模型差异、什么时候该用哪种以及我落地 A2UI 时踩过的坑。1. 直接返回 HTML看着很美走起来很挤1.1 最小可用链路工具吐出一段 HTML客户端用 iframe 一装了之MCPModel Context Protocol的生态里模型通过客户端调用外部工具或服务这些可调用单元在社区里常被统称为 MCP Apps 或 MCP Servers。一位开发者要走捷径的话最快的方式就是让 MCP 工具直接返回一段 HTML 字符串然后客户端用 iframe 的srcDoc属性或者 WebView 把它渲染出来。伪代码长这样mcp.tool() def generate_feedback_form() - str: return !doctype html html langzh-cn head style body { font-family: system-ui, sans-serif; } form { max-width: 420px; margin: 24px auto; } /style /head body form label forname姓名/label input idname namename typetext required / label forscore评分/label select idscore namescore option value55 分/option option value44 分/option /select button typesubmit提交/button /form /body /html 客户端那边更简单拿 iframe 的srcdoc属性一塞一个表单页面就出来了。这个方案的吸引力在于“零 UI 开发”浏览器天生就是渲染 HTML 的机器MCP 客户端只要自带一个浏览器内核理论上什么界面都能展示。尤其是在工具本身就跟网页内容打交道的场景——网页转 Markdown、抓取页面截图、PDF 预览——返回 HTML 确实自然。1.2 demo 能跑产品接不住我最初也被这个思路吸引了但它能撑起 demo撑不起产品。问题不是“HTML 能不能渲染”而是“渲染出来之后你的客户端还是你的客户端吗”。第一关是样式隔离。iframe 里的 HTML 自带一套 CSS字体、间距、按钮样式全是工具作者定的和宿主应用的视觉风格天然冲突。哪怕宿主应用用的是 Element Plus 或 Naive UI 这种成熟的 Vue 组件库工具返回的 HTML 也会我行我素。用户在一个设计语言统一的桌面应用里突然看到一段像十年前门户网站一样的表单体验立刻崩掉。第二关是脚本执行策略。如果客户端允许 iframe 里的script标签跑等于让任意第三方 MCP 工具在你应用的渲染进程里执行任何 JavaScript 代码如果禁止脚本很多交互又做不了。表单校验、动态展开、分步提交、按需加载这些基础交互全部依赖脚本一禁全完。第三关是通信成本。宿主应用和 iframe 是两个隔离的上下文想拿到用户填写的表单数据得靠postMessage在两边来回传递消息。表单变复杂之后这基本等于自己造一套协议——字段变更要传、校验失败要传、按钮点击要传、提交结果要传。更麻烦的是渲染方在沙箱里调不了宿主能力想调一个系统文件选择器、弹一个原生通知还得宿主从外面迁就它。1.3 更深一层HTML 把“信息”和“呈现”揉成了一个整包说到底MCP 工具的核心职责是返回信息。一个表单生成器最有价值的结果是“这个表单包含哪些字段、哪些校验、哪些提交动作”。但 MCP 工具选择返回 HTML 时它把这些信息写进了标签、CSS 和脚本的缝隙里——在信息外面裹了一层厚厚的样式和一段可执行代码。这种打包行为有一个隐蔽代价客户端失去了对 UI 的解释权。用一个生活类比来说HTML 方式是你去餐厅点了一道菜后厨直接把一整套盘碗连同摆盘设计一起送到你面前你连换餐具的余地都没有而 A2UI 方式是后厨给你一份用料清单和烹饪步骤用什么盘子摆盘、按什么口味上菜由你自己决定。MCP 客户端和浏览器不一样。浏览器是“专门用来解释 HTML 的机器”它天然愿意执行网页里的任何指令。但 MCP 客户端是产品是一个有自己的设计规范、交互模式和安全边界的应用。把 HTML 整包塞给它等于让外部工具来决定产品内部长什么样、能执行什么代码。2. A2UI 实际上在做什么把 UI 变成“可协商的数据”2.1 A2UI 的 Schema 长什么样A2UIAgent-to-UI这类协议的核心思路并不玄妙不再让 MCP 工具返回一份写死的网页文档而是返回一份结构化的界面描述客户端拿到描述之后用自己的组件库去渲染。还是刚才那个反馈表单的例子A2UI 视角下的 Schema 大致长这样{ type: form, name: feedback, title: 反馈表单, fields: [ { name: name, label: 姓名, kind: text, required: true, placeholder: 请输入你的名字 }, { name: score, label: 评分, kind: select, options: [ { value: 5, label: 5 分 }, { value: 4, label: 4 分 } ] } ], actions: [ { type: submit, label: 提交, target: submit_feedback } ] }这份 JSON 既不规定按钮长什么颜色也不指定页面用多大字号它只描述一件事这里有一个表单表单里有哪些字段每个字段的数据类型是什么提交动作指向哪个 MCP 工具。JSON 之所以成为这类协议的主流载体是因为它够普通任何语言都能解析任意两个系统之间都能交换可以放进 MCP 的返回结果里也可以流式分块传输。它不像 HTML 那样自带执行语义渲染端需要专门写一个解释器——而这恰恰是 A2UI 想要的安全底座。2.2 描述与渲染分离带来的四件事把“界面长什么样”和“界面怎么渲染”拆开之后至少四个能力会被重新解冻。第一是安全可控。A2UI Schema 里不会有script标签不会有内联事件处理器渲染器只会根据kind字段去映射一个预置组件。工具作者再也不能通过一段 HTML 在你的客户端里偷偷执行代码。第二是主题跟随。同样是kind: button在浅色模式下渲染成浅色按钮在暗黑模式下自动切换成暗色变体由宿主应用统一控制。用户感受是“这个界面就是我这款应用该有的样子”而不是“AI 工具给我塞进来一个不认识的页面”。第三是状态可追踪。用户填了哪个字段、改了哪个值、点了哪个按钮这些交互状态流转在渲染器里有明确的逻辑分支。不像 iframe 里的 HTML所有状态埋在网页内部宿主想知道用户干了什么都得靠猜或者靠消息协议。第四是多端复用。一份 Schema 可以同时驱动桌面客户端、Web 端和移动端。桌面端用原生组件渲染移动端用移动组件渲染只要同一套 Schema三端功能保持一致视觉又不打架。2.3 和 MCP 不是替代关系而是上下层关系这里必须说清楚A2UI 不是要替代 MCP。MCP 解决的是“模型怎么调用工具、工具怎么返回结果”的通信问题A2UI 解决的是“返回结果怎么变成用户能操作的界面”的呈现问题。它们是上下层关系不是竞争者。在 A2UI 模式下MCP 工具照样干活只是返回的内容从 HTML 字符串变成 JSON Schemamcp.tool() def generate_feedback_form() - dict: return { type: form, name: feedback, fields: [ {name: name, label: 姓名, kind: text, required: True}, {name: score, label: 评分, kind: select, options: [{value: 5, label: 5 分}, {value: 4, label: 4 分}]} ], actions: [{type: submit, label: 提交, target: submit_feedback}] }整个数据链路变成模型 → MCP 客户端 → MCP 工具 → 返回 A2UI Schema → 客户端渲染器 → 原生 UI → 用户交互 → 触发新的工具调用。MCP 管前后的通信A2UI 管中间的呈现各管一段互不越界。3. 两种方案的硬碰硬安全、一致、交互深度的横向对比3.1 一张表先看清全貌对比维度MCP 直接返回 HTMLA2UI Schema内容本质完整文档结构 样式 脚本结构化界面描述纯数据客户端渲染成本需要 WebView / iframe 容器原生组件映射渲染安全边界脚本执行风险高白名单组件机制视觉一致性由工具作者定义难以统一由宿主主题决定交互状态回传自建 postMessage 消息协议渲染器内置事件模型流式更新整包替换或注入脚本Schema 增量 patch多端适配每端都要维护一个网页容器一份 Schema 多端渲染可访问性取决于工具生成的 HTML渲染器可统一兜底接入成本低跑起来快中需要写渲染器表格能覆盖大部分结论但更重要的是理解背后为什么是这些结论。3.2 安全模型HTML 是无条件信任A2UI 是白名单渲染HTML 的问题不在于 HTML 本身而在于它默认带着执行能力。一个script标签、一个onclick属性、一行javascript:伪协议都能让第三方工具代码获得与宿主应用同等权限。你可以用 CSP、沙箱、禁脚本等手段把这些能力关掉可关掉的同时HTML 的交互层也基本废了。而且现实中的威胁不是“恶意工具”而是“不可预料的工具”。一个 MCP 工具作者今天写的代码是安全的但引用了某个第三方统计脚本明天那个脚本被攻破灾难就传导到你的客户端里。就安全设计而言黑名单永远防不住新变种白名单才能从根上控制风险。A2UI 走的是白名单路线渲染器只认识text、select、table、tabs这类预置节点类型其余一概不认。工具能影响的是数据结构不是执行逻辑。这个模型简单粗暴但可靠得多。3.3 视觉一致性浏览器内核渲染不出“你的应用”很多产品团队在 A/B 测试这个方案时都会低估违和感的杀伤力。用户的深层认知是这个软件的中文字体、按钮圆角、开关状态、空状态插画共同构成了“这款应用的样子”。MCP 工具返回 HTML 后页面里所有视觉元素都来自工具作者哪怕尽力模仿也永远差一层。原生组件方案天然解决这个问题。由于 A2UI Schema 在宿主端通过组件库渲染字体、间距、颜色、暗黑模式全部由客户端的全局主题接管。用户看到表单的第一反应是“这就是 App 内的一部分”不是“弹进来一个网页”。3.4 交互深度能看、能点和能协作是两码事HTML 返回方式也能做出复杂交互但付出的代价是非线性的。表单校验、分步填写、表格行内编辑、动态添加字段、级联下拉——这些功能如果要在 iframe 里实现工具作者就得自己维护一整套前端应用逻辑客户端还必须开放脚本权限。一旦出了问题你连调试堆栈都拿不全。A2UI 方式下复杂的交互能力分散在客户端的组件里。比如table类型自带排序、筛选、分页form类型自带校验规则绑定客户端每次升级可以同时提升所有工具界面。对 MCP 工具作者来说他只描述“要一个表格”不用关心表格怎么排序、怎么翻页。4. 什么场景无脑 HTML什么场景必须 A2UI4.1 用 HTML 完全合理的场景A2UI 不是万能药有些场景下 HTML 就该被直接用。第一类是工具本身处理的就是网页内容。比如网页转 Markdown、网页快照、页面文本提取、SEO 预览这样的 MCP 工具它们的输出天然是 HTML 或者基于 HTML 的派生结果。这时候让工具输出结构化 JSON 反而绕路直接给你看渲染后的网页才是本职。第二类是只读预览场景。用户只是“看一眼”的内容——一份 HTML 格式的周报、一个导出的静态页面、一张带交互图表的报告——用沙箱 iframe 只读渲染不让脚本执行不用回传状态完全够用。第三类是一次性原型验证。你自己在本地写脚本测试 MCP 工具能力时把结果直接丢进浏览器看远比配一个 A2UI 渲染器快。这个阶段的目的是“确认工具输出对不对”不是“把输出接进生产产品”。4.2 必须上 A2UI 的场景强交互界面是 A2UI 的主场。表单填写、配置向导、数据表格编辑、登录注册流程、分步骤操作这些场景用户需要在界面里持续输入、反复校验、提交后再根据结果调整。HTML 的整包渲染模式在这种场景下处处别扭。多端产品是另一个明显信号。如果你的 MCP 客户端同时有桌面版、Web 版和移动版一份 HTML 应对三种端意味着要维护三种 WebView 的兼容性。A2UI 一份 Schema 三端复用每端自己处理本地化渲染。团队协作边界也用得上 A2UI。MCP 工具由不同团队或社区作者提供客户端 UI 由你的团队控制A2UI 让两个团队只在 Schema 层面打交道不需要一方把前端代码交给另一方。4.3 折中方案如果一定要 HTML把约束做足如果你已经完全被 HTML 方案的接入成本吸引又不想牺牲安全性还有一个折中做法HTML 沙箱只读容器加结构化事件回传。具体来说iframe 只设置srcdoc不加allow-scripts不放开同源权限工具返回的 HTML 只能展示不能执行脚本页面里的用户操作比如填写、点击通过一个自定义协议从沙箱里把数据以 JSON 形式发出来宿主应用自己接管后续动作。这个方案能覆盖一部分“需要看网页也需要收集一点状态”的场景但同时也是最尴尬的中间态——你既没有 HTML 方案的零成本也没有 A2UI 方案的统一性。所以我建议把它当作过渡方案不要当长期架构。5. 在 MCP 客户端里落地 A2UI 的完整姿势5.1 数据流设计从拿到 Schema 到渲染出来落地 A2UI 的第一步不是写渲染器而是定数据流MCP 工具返回什么、客户端怎么识别、渲染器怎么消费。我现在的做法是把 A2UI Schema 当作 MCP 工具的固定返回类型。所有工具约定如果返回值是一个描述界面的 Schema必须在结果里带一个_ui标记字段表示“这段数据是给渲染器用的不是给模型直接读的”。客户端拿到结果后先检查这个标记是 Schema 就走渲染管线是普通文本就走原有对话展示逻辑。这一步很关键它让 A2UI 和 MCP 的普通文本返回并存不用为了 UI 功能推翻原有的协议设计。5.2 从社区实践看Vue 适合用来写这类渲染器我见了几个 A2UI 的开源项目目前的社区实践里 Vue 是出镜率最高的参考实现框架。原因不复杂Vue 的渲染函数和动态组件机制特别适合“拿一份 JSON 映射出组件树”这种活。React 当然也能做但 Vue 的模板表达更直接组件注册方式也更适合做“字符串到组件的映射表”。一个最小可用的 Vue 渲染器大致长这样template form submit.preventhandleSubmit component v-forfield in schema.fields :keyfield.name :iscomponentMap[field.kind] v-modelmodel[field.name] v-bindfield / button typesubmit {{ schema.actions schema.actions[0].label }} /button /form /template script setup import { ref, markRaw } from vue import TextField from ./fields/TextField.vue import SelectField from ./fields/SelectField.vue const props defineProps({ schema: { type: Object, required: true } }) const emit defineEmits([submit]) const model ref({}) const componentMap markRaw({ text: TextField, select: SelectField }) function handleSubmit() { const action props.schema.actions props.schema.actions[0] if (action) { emit(submit, { target: action.target, args: model.value }) } } /script实际项目里componentMap肯定不止text和select还会有table、checkbox、radio、date、tabs等一票组件类型。核心逻辑不变Schema 里kind字段就是组件注册的 keyv-bindfield把其余参数透传给真实组件。5.3 流式输出与局部更新Schema 也能增量走MCP 是支持流式输出的这给 A2UI 带来一个优势界面可以边生成边渲染不用等完整 HTML 全部到位。实践上我会让工具分几段返回 A2UI Shell 和字段片段。第一段只返回一个{type: form, name: feedback, fields: []}的空壳后续流式输出按{patch: {path: /fields/-, value: {...}}}的形式追加字段。渲染器每收到一段增量就按路径把字段插到 Schema 的fields数组里Vue 的响应式系统会自动渲染新节点。这套机制和 HTML 方式的区别在于HTML 要更新局部内容得整页替换或者靠脚本操作 DOM而 Schema Patch 是数据层面的操作天然适合响应式框架。工具端生成 UI 的延时就从“等完整网页”变成“首个字段就能渲染”。5.4 回传用户操作让表单提交变成一次 MCP 工具调用A2UI 和普通表单最大的不同是提交动作背后一般都对应一个 MCP 工具调用。用户在 Schema 渲染出来的表单里点了“提交”客户端不应该只把数据收到本地就完了而应该把它转成一次新的 MCP 工具调用让模型继续处理。比如表单的actions[0].target是submit_feedback点击提交后客户端调用 MCP 工具submit_feedback参数就是当前表单状态。工具返回“提交成功”后客户端再把成功结果渲染成一段普通对话消息或者渲染出一个新的 A2UI Schema比如“提交完成”的展示卡片。这样整个交互闭环就是模型发起表单生成 → 工具返回 Schema → 用户填写 → 客户端调用另一个工具 → 工具返回结果 → 模型继续对话。A2UI 始终只负责中间那一段“用户界面”的呈现业务逻辑仍然全部握在工具和模型手里。6. 我踩过的几个坑和最后想说的选型心法6.1 踩坑记录iframe 的隔离远比想象中彻底我最早在没有 A2UI 的情况下用 iframe 接 HTML踩过三个印象深刻的坑。第一个坑是 Cookie 和登录态完全隔离。iframe 里的页面即使和主应用同域在第三方工具生成的页面里也拿不到我主应用的登录态MCP 工具想调用户信息接口直接 401。当时我只能通过 postMessage 把 token 手动塞进去但这个操作本身就是个安全隐患。第二个坑是暗黑模式适配永远慢半拍。工具返回的 HTML 可能用prefers-color-scheme写了暗黑样式但我的客户端用的是一套自定义主题切换机制切换时机不完全同步经常出现深色壳子里的浅色网页亮得刺眼。第三个坑是 Schema 自由度失控。我自己设计 A2UI Schema 时一开始没限制kind枚举结果不同工具作者造出了各自的节点类型渲染器天天往下游兼容。后来我学乖了在协议层规范几个核心节点类型text、select、table、tabs、markdown超出这些类型的一律走自定义组件注册通道。控制好 Schema 的枚举范围渲染器才不会烂尾。6.2 最后的选型心法做了这么多对比和实验之后我现在的判断标准其实就三条能表达成数据的就别表达成文档能用白名单控制的就别用黑名单防御能把渲染控制权留在客户端的就别交给一个不可控的网页容器。MCP 工具能直接返回 HTML 确实很诱人因为它是阻力最小的一条路。但“阻力最小”往往只意味着“开头最快”一旦你要做的是产品级客户端HTML 的整包渲染就会变成成本沉淀的大坑。A2UI 多了一层 Schema 设计和渲染器开发换来的却是安全边界、视觉一致性和多端复用能力。这层投入在工具数量超过十几个之后会变成绝对的效率优势。我现在接到新 MCP 工具的第一反应不是“它的输出怎么放进网页里”而是“它的输出能不能用一段 Schema 描述出来”。能就接 A2UI不能再去看 HTML。这个顺序反过来往往会走回 iframe 里调样式、onmessage 满天飞的老路。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

湖南村级界线矢量数据清洗与应用实战指南 2026/9/9 0:08:45

湖南村级界线矢量数据清洗与应用实战指南

简介:湖南村级行政界线矢量数据以shapefile格式提供全省村级行政区域边界,面向GIS制图、空间分析和行政区划研究场景,适合需要村级基础底图的规划、国土、农业、科研等从业者及学生使用。压缩包共6个文件,由.shp几何数据、.dbf属性…

阅读更多 →
PX4 SITL+Gazebo+QGC仿真环境深度搭建与故障排查指南 2026/9/9 0:08:45

PX4 SITL+Gazebo+QGC仿真环境深度搭建与故障排查指南

1. 这不是“装个软件就完事”的仿真环境,而是PX4飞控开发的底层工作台你搜过“ubuntu搭建px4无人机仿真环境”,点开前十个结果,大概率会看到一串命令复制粘贴——sudo apt install gazebo11、git clone PX4-Autopilot、make px4_sitl_default…

阅读更多 →
40年城市年鉴面板数据整理:清洗、编码与校验实战 2026/9/9 0:08:45

40年城市年鉴面板数据整理:清洗、编码与校验实战

做城市经济、区域发展或者历史地理研究的人,几乎人手一册《中国城市统计年鉴》。但真要把1985—2025这40年的年鉴整理成一份能直接跑回归的面板数据,工作量比大多数论文“数据来源”段落里写的那两行字要残酷得多。不同版本的表格结构变了、指标名称换了…

阅读更多 →
Matlab蚁群算法路径规划实操指南:可调参、可部署、可落地 2026/9/9 0:08:45

Matlab蚁群算法路径规划实操指南:可调参、可部署、可落地

简介:本资源是一份面向算法学习者与MATLAB初学者的蚁群算法(ACO)路径规划实践代码包,聚焦旅行商问题(TSP)等组合优化场景,适用于机器人导航、物流调度等实际路径规划任务的原理验证与算法调试。…

阅读更多 →
智能体是什么?概念拆解、架构原理与落地入门指南 2026/9/9 0:08:45

智能体是什么?概念拆解、架构原理与落地入门指南

给新人讲“什么是智能体”,我从来不会先念定义。我通常只问一个问题:你手头有没有一件事,每周至少重复三遍以上,而且每次都希望有个AI替你跑完?多数人会愣一下,然后反问我:智能体是不是就是那个…

阅读更多 →
用原生JavaScript打造2048小游戏:从零实现核心算法与交互 2026/9/9 0:05:45

用原生JavaScript打造2048小游戏:从零实现核心算法与交互

简介:这是一款基于纯JavaScript开发的网页版2048小游戏完整源码包,面向初级Web工程师及前端初学者,旨在通过一个无需外部框架的实战项目,帮助理解JavaScript核心机制、DOM操作、事件驱动编程和游戏状态管理等知识点。压缩包共3个文…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞