若按功能清单在这四者之间做选择,你多半会选错,因为其中只有两个真正互为替代。它们的分界只在一个事实上——可执行代码是否跨越网络边界——而决定你的智能体所绘制的界面能否出现在浏览器之外的,正是这个事实,不是任何一项功能。先确定界面必须在哪些载体上渲染,标准会从这个答案里自己浮现出来;而四者中有三个,此后是可以叠在一起用的。
一览
四份都被描述成「让智能体画界面」的规范,放在真正区分它们的那条轴上对读。
| 项目 | 托管方 | 状态 | 跨越网络边界的是什么 |
|---|---|---|---|
| MCP Apps(SEP-1865) | Model Context Protocol | 2026-01-26 版规范已稳定,draft 仍在推进 | 一份 HTML5 文档 |
| OpenAI Apps SDK | OpenAI | 已在 ChatGPT 中交付 | 一份 HTML 文档,外加一套宿主 API |
| A2UI | v0.9.1 为稳定版,v1.0 为候选发布,自述为「早期公开预览」 | 声明式 JSON | |
| AG-UI | ag-ui-protocol,源自 CopilotKit 与 LangChain、CrewAI 的合作 | MIT 许可,约十六种标准事件类型 | 事件——根本不含界面 |
最后一列值得读两遍,整场比较都在里面。其中两个交付的是浏览器要去运行的东西,一个交付的是渲染器要去解释的东西,还有一个两者都不交付,因为它是一种传输协议,只是恰好出现在同一批讨论里。
把它们分开的那个问题
对任何一份智能体界面提案都该问一句:当智能体想要一个日期选择器时,它送出的是一个日期选择器,还是「日期选择器」这几个字?其余一切都由此推导。送出选择器,你得到像素级控制、任意库和动画,代价是对端必须有 JavaScript 运行时,且宿主得愿意跑你的代码。送出那几个字,你得到一个在手机、手表或车机上都能渲染的界面,代价是你只能表达对端已经知道怎么画的东西。
这不是新的取舍;邮件客户端、终端转义序列和原生移动 SDK 早就都做过同样的选择。新的地方在于,两种答案现在各有一份 2026 年的规范在背后支撑,于是这个选择变成了现实而非理论——而做选择的团队,读的往往是把两者并列在同一栏里的功能对照表。
真正能定夺的测试:把你的产品在未来十八个月里必须支持的载体全部写下来。如果这份清单只有「一个不属于我们的聊天客户端」,那么你要的是 MCP Apps。如果清单里出现了任何不含浏览器的东西,可执行那条通路对你就是关闭的,功能上再怎么追平也不会重新打开它。
MCP Apps 细读
机制
工具声明 _meta.ui.resourceUri,指向同一服务端托管的一份资源。该资源 MUST 使用 ui:// 方案与 text/html;profile=mcp-app 这一 MIME 类型,内容 MUST 是合法的 HTML5 文档。宿主用普通的 resources/read 读取它,并在沙箱 iframe 中渲染;若宿主本身是一个网页,规范要求中间再加一层沙箱代理,且宿主与沙箱 MUST 处于不同的源。通信是 postMessage 之上的 JSON-RPC,握手顺序为 ui/initialize 再到 ui/notifications/initialized,此后宿主发送 ui/notifications/tool-input,随后发送 ui/notifications/tool-result。
团队最常漏掉的一点
内容安全策略默认拒绝一切,而需要提出申请的正是你的应用本身。完全省略 ui.csp,宿主就会套用 default-src 'none' 外加 connect-src 'none'。connectDomains 留空,你得不到任何对外连接;frameDomains 留空,你得不到任何嵌套 iframe。宿主 MUST 依据你声明的内容构造策略,MAY 进一步收紧,MUST NOT 放行你未声明的域。每一个源都要算进去,包括你打包的 JavaScript 与 CSS 的来源——所以务实的做法是把产物内联进去,而不是在某个根本不会把策略拦截日志回传给你的宿主里才发现这件事。
值得打印出来贴墙上的那句规范
即使界面可用,工具 MUST 仍然返回有意义的 content 数组;并且对每一个启用了界面的工具,服务端 SHOULD 提供纯文本回退行为。这项扩展是可选的,在 io.modelcontextprotocol/ui 下协商;不支持它的宿主会把你的工具当作普通工具处理。所以文本通路不是你可以留到第二个迭代再补的优雅降级,它就是契约本身,界面是叠在它上面的增强。先做控件、后补文本结果的团队,交付出的工具在所有尚未采用该扩展的宿主里都是坏的——在可预见的时间里,那指的是大多数宿主。
触达范围
2026 年 1 月发布时,已支持的宿主为 Web 与桌面版 Claude、Goose、VS Code Insiders,以及当周正在逐步放量的 ChatGPT。MCP 官方博客把这项工作归功于与 OpenAI 及 MCP-UI 项目的合作,后者的模式被收进了官方扩展;MCP-UI 仍在持续维护,而若干宿主实际交付的正是它的客户端 SDK。仓库本身给出了一条诚实的提醒:除一个基础示例外,它不含任何生产级宿主实现,所以宿主的行为是逐宿主的事实,不是规范的事实。
A2UI 细读
机制
智能体以 JSON 输出一份按 ID 相互引用的扁平组件清单,客户端再用自己维护的目录里的控件逐个绘制。README 对意图的表述罕见地清楚:A2UI「是一种声明式数据格式,不是可执行代码」,智能体「只能从客户端维护的、预先批准的组件目录中请求组件」,设计目标是做到「像数据一样安全,又像代码一样有表达力」。采用扁平清单而非树,是对语言模型的刻意让步——模型增量生成一份清单,远比生成正确嵌套的标记可靠。
你买到了什么
不是浏览器的渲染器。Web 侧已有 Lit、Angular 和 React 的渲染器,Flutter 方向有已发布的 Dart 包,SwiftUI 渲染器在代码树里但尚未发布;Python 包已上 PyPI。因为没有任何可执行物跨越网络边界,安全评审就变成了一次 schema 评审——智能体最糟也只能请求一个并不存在的组件,而客户端会拒掉它。这与「我们要在 iframe 里跑第三方的 HTML」是性质不同的一场对话,在受监管的环境里,有时它是唯一能进行的那场对话。
你放弃了什么
目录画不出来的一切。README 的示例是 Card、Button 和 TextField。如果你的界面需要一张带可刷选时间轴的 K 线图,那你送不出去;你要么把它加进每一个客户端的目录,要么放弃它。这是那条安全性质的代价,而且它是永久性的,不是会随 v1.0 收口的缺口。请把这份目录当作一套设计系统来治理——现在你得在所交付的每一个渲染器上同时治理它。
状态
v0.9.1 是当前的生产版本,v1.0 是候选发布,项目自述为「早期公开预览」,规范与实现「可用但仍在演进」。若干包——TypeScript 智能体 SDK、Flutter 渲染器与 CLI、A2A 与 ADK 连接器——可从源码使用但尚未发布。基于它构建时,请预期自己要跟踪仓库,而不是锁定一个版本号。
AG-UI 细读
它为什么不在同一条赛道上
AG-UI 自称「一种开放、轻量、基于事件的协议,用于规范 AI 智能体如何连接面向用户的应用」,而它自己对这片版图的归纳,是目前公开表述里对边界最清楚的一次:「MCP 给智能体工具」,「A2A 让智能体之间通信」,「AG-UI 把智能体带进面向用户的应用」。它定义约十六种标准事件类型,可跑在任意事件传输之上——SSE、WebSocket、webhook。它不定义组件,不定义布局,不定义任何一个像素。前端仍然由你来写。
它换来什么
问题中不起眼的那一半,也正是多数团队低估的那一半:流式 token 增量、工具调用的开始与结束、状态补丁、运行生命周期和中断,全部统一在一种线上形态里,而不是每接一个后端就要重新设计一套事件信封。集成清单是支持它最有力的论据——LangChain、CrewAI、Microsoft Agent Framework、Google ADK、AWS Strands、Mastra、Pydantic AI、Agno、LlamaIndex、Amazon Bedrock AgentCore,社区集成还包括 Claude Agent SDK 与 Cloudflare Agents,并有 Kotlin、Go、Dart、Java、Rust、Ruby、C++ 和 .NET 的 SDK。MIT 许可。
真正重要的那种组合
A2UI 把 AG-UI 与 A2A 一并列为兼容的传输。所以「AG-UI 还是 A2UI」是一个范畴错误:常规的搭法是 AG-UI 承载 A2UI 载荷,绘制交给你自己的组件。如果你在构建自己的产品界面,而不是向别人的客户端里发布内容,那么这一组合——再加上把智能体嵌入既有应用里的那些做法——很可能就是全部答案。
OpenAI Apps SDK 细读
机制
同样建立在 MCP 之上,同样是 iframe 里的一份 HTML 文档,形态上是四者中与 MCP Apps 最接近的一个。差别在宿主 API。在一个 ChatGPT 控件内部,你的代码从 window.openai 读取 toolInput、toolOutput、displayMode、theme 和 widgetState,用 setWidgetState 写入,用 callTool 调用工具,并通过 requestDisplayMode、openExternal 和 sendFollowUpMessage 触达宿主。示例仓库把这套接口呈现为完整面貌,并把整件事定位为构建「面向 ChatGPT 的应用」,其安装步骤走的是 ChatGPT 的开发者模式与连接器设置。
该怎么理解这层重叠
MCP 官方博客与 OpenAI 自己的材料都把 Apps SDK 描述为基于 MCP,而 MCP Apps 的发布公告也把构建这份共享扩展的功劳归入 OpenAI 的参与。有用的心智模型是:组件模型收敛了,宿主 API 没有收敛——同样是 iframe 里的 HTML 这一形态,在 ChatGPT 里经由 window.openai 触达,在别处经由标准化的 ui/ JSON-RPC 方法触达。针对一套宿主 API 写的代码,不加适配层就跑不到另一套上,而一年之后你真正在维护的,也只有这层适配。
什么时候它仍然是对的选择
当在 ChatGPT 内的分发本身就是目的时。一个装机量极大的客户端,加上通用扩展并未定义的宿主能力,是接受宿主耦合的正当理由——人们多年只做 iOS 应用,道理相同。只是请把它当成一次移植预算,而不是一次构建预算,并让服务端的工具结果保持完全可用的纯文本形态,这样同一个服务端日后还能服务通用 MCP 宿主。而这一点,恰好也是 MCP Apps 规范对你的要求。
横向对比
可移植性
按一个能用的界面能走多远给四者排序,排出来的顺序与通常那种「谁更强」的排序正好相反。AG-UI 走得最远,因为它压根不携带会不兼容的界面。A2UI 次之,因为它的载荷是数据,任何目录对得上的渲染器都能把它画出来,包括完全不含浏览器的渲染器。MCP Apps 能跨宿主,但仅限于协商了该扩展、且具备 Web 能力的宿主。Apps SDK 走到 ChatGPT 为止。在这把梯子上每往上一级,你要付出表达力;每往下一级,你要付出触达范围。
安全姿态
两个可执行选项对「要运行不受信任的代码」这件事是诚实的,它们的规范篇幅也都花在约束上——不同的源、强制的沙箱属性、默认拒绝的策略、一个代理每一次工具调用的宿主,而且对可见性不含 "app" 的工具,宿主 MUST 拒绝来自应用的调用。声明式选项则从构造上回避了这个问题:没有代码,就不需要约束,攻击面收缩为 schema 校验,外加你自己的控件如何处置这些数据。两种姿态在实践中都不是严格更安全的一方,因为可执行通路上的约束是真实有效的,而声明式通路上的控件仍然可能被你写错——但它们的失效方式不同,所需的评审也由不同的人来做。
谁掌握模型的上下文
四者最终都要回答「用户在界面里做的事,模型学到了什么」,而只有 MCP Apps 把它规定得很紧:ui/update-model-context,每一次请求都覆盖该视图先前送出的上下文;若在下一条用户消息之前到达了多次更新,宿主 SHOULD 只发送最后一次。Apps SDK 通过控件状态与追问消息来做这件事。A2UI 让它流经一份智能体可回读的数据模型。AG-UI 把它留给你。如果你曾调试过一个自信地描述着用户早已改过的表单的智能体,那么这就是你该先读的那个字段。底下的失效模式正是上下文工程里讲过的那一种:模型对世界的图景,就是最后一次被写进去的那份。
何时选哪一个
| 情形 | 选择 | 理由 |
|---|---|---|
| 把一个工具服务端发布到不属于你的聊天客户端里 | MCP Apps | 唯一具备跨宿主协商、且由规范强制要求文本回退的选项 |
| 在 ChatGPT 内的分发就是商业理由 | Apps SDK,同一个服务端 | 用上通用扩展并未定义的宿主能力;同时保持工具结果的纯文本可用性 |
| 你的载体包含移动端、桌面端或嵌入式设备 | A2UI | 跨越网络边界的是数据,非浏览器渲染器才有可能存在 |
| 受监管环境,不会运行第三方标记 | A2UI | 把沙箱评审换成 schema 评审;目录是设计上的硬天花板 |
| 自己的应用、自己的组件、多种智能体后端 | AG-UI | 跨框架统一的事件形态;若要让智能体来挑组件,再叠上 A2UI |
| 你写不出一份屏幕清单 | 先读生成式 UI 模式 | 标准是容易的那个决定;是否该让智能体来组装,才是难的那个 |
如果你要把界面交付进别人运营的宿主里,选标准大约是一周的工作量,而运营层面的后果会持续数年——你无法掌控的缓存行为、默认就会拦住你遥测的策略,以及逐宿主各不相同的能力集合。这些我们单独写在把界面交付进你无法掌控的宿主里。
常见问题
MCP Apps 和 MCP-UI 是同一件事吗?
不是,但两者直接相关。MCP-UI 是 Ido Salomon 与 Liad Yosef 做的社区项目,它的模式被标准化为 MCP Apps,也就是 MCP 的第一个官方扩展。MCP-UI 仍在持续维护,若干宿主实现该官方扩展时用的正是它的客户端 SDK。
AG-UI 和 A2UI 能一起用吗?
能,而且这正是预期的搭法。A2UI 仓库把 AG-UI 与 A2A 列为兼容的传输。AG-UI 承载事件流,A2UI 描述流中的组件,你的渲染器负责绘制。
一个 MCP App 能访问网络吗?
默认不能。若省略 ui.csp,宿主会套用 default-src 'none' 与 connect-src 'none';connectDomains 为空或缺失即表示没有任何对外连接。服务端声明自己需要的源,宿主依据该声明构造策略,并且不得放行任何未声明的内容。
在不支持该界面扩展的宿主里会发生什么?
你的工具会被当作普通工具处理。该扩展在 io.modelcontextprotocol/ui 下协商,服务端 SHOULD 在注册启用界面的工具之前检查客户端能力,而每一个启用界面的工具 MUST 仍然返回有意义的 content 数组。换句话说,文本结果是契约,界面是增强。
A2UI 可以上生产吗?
它自己的 README 称其为「早期公开预览」,并提醒规范与实现仍在演进,其中 v0.9.1 为生产版本、v1.0 为候选发布。若干包尚未发布、需从源码使用。请把它当作一个需要跟踪的依赖,而不是一个锁定版本的依赖。
模型看到的是哪一部分?
在 MCP Apps 里,可见性是显式的:_meta.ui.visibility 默认为 ["model", "app"],当可见性不含 "model" 时宿主 MUST NOT 把该工具列给模型,当可见性不含 "app" 时 MUST 拒绝来自应用的调用。这就给了你模型无法调用的「仅应用」工具,而那正是安放「界面需要、但模型不该自行触发」之物的合适位置。
延伸阅读
本站相关:
- 生成式 UI 模式——在选「怎么做」之前,先决定是否该让智能体组装一屏界面。
- 把界面交付进你无法掌控的宿主——当渲染器属于别人时的版本管理、缓存与遥测。
- 能力发现——界面扩展正是这一协商模式的一个实例。
- 什么是 MCP——三个基于 MCP 的选项共同立足的协议。
项目来源:
- modelcontextprotocol/ext-apps——MCP Apps 规范与各 SDK
- google/A2UI——A2UI 规范、渲染器与智能体 SDK
- ag-ui-protocol/ag-ui——AG-UI 协议与各类集成
- openai/openai-apps-sdk-examples——工作示例中的
window.openai接口