模型选择 UI 能力,不拥有渲染器
模型输出组件类型、受限 Props、数据引用与白名单 Action ID;前端 Renderer 根据本地 Registry 创建真实组件。模型既不能传脚本、事件处理器或原始 HTML,也不能决定浏览器权限。未知组件、字段或版本降级为安全文本、结构化数据或明确错误,Tool 的事实结果仍应可访问。
Why Not 直接生成代码:代码自由度会把 Prompt Injection、XSS、供应链、无障碍、性能和品牌一致性合并成一个无法穷举的执行面。受控意图牺牲了一部分布局自由,却换来静态审查、版本兼容、确定性测试和可撤销发布。
type UiNode =
| { id: string; type: 'metric'; props: { label: string; valueRef: string } }
| { id: string; type: 'table'; props: { rowsRef: string; columns: Column[] } }
| { id: string; type: 'action'; props: { actionId: AllowedAction; label: string } };
// 不接受 html、script、onClick 或任意 URL。
UI 协议要表达结构但限制自由度
用判别联合类型表达组件,Schema 记录协议版本、节点 ID、Props 类型、必填字段、长度/数量上限和允许的数据引用。布局只开放少量容器与断点,限制节点总数、嵌套深度和递归,避免模型构造不可读或拒绝服务式组件树。
事件字段只引用注册 Action,不携带 JavaScript、URL 模板或 Tool 参数拼接逻辑。Schema 合法只是第一层,Renderer 还要检查业务不变量,例如表格列是否允许展示敏感字段、按钮是否适合当前状态、外链是否来自允许协议和域名。
组件 Registry 是产品与治理边界
每项 Registry 记录用途、适用/禁用条件、Props Schema、可用数据分类、允许 Action、无障碍契约、版本、Owner、实验状态和废弃日期。模型描述告诉它何时选择组件,但 Host 使用本地可信元数据做权限和渲染决定,不能信任模型回传的风险标签。
| 字段 | 作用 | 失败处置 |
|---|---|---|
| 版本与兼容范围 | 确定协议/Renderer 能否解释 | 协商旧版或降级 |
| 数据分类 | 限制可绑定的数据和持久化 | 拒绝并记录策略原因 |
| 交互权限 | 列出 Action 与风险 | 服务端重新授权 |
| Owner 与弃用 | 维护、回归和迁移责任 | 冻结新增,提供迁移期 |
发布先在影子流量记录模型选择但不渲染,再小比例启用只读组件;观察误选、降级、任务完成和可访问性后扩量。下线时保留旧 Renderer 到存量 Artifact 迁移完成,不能只从模型 Tool 列表删除。
MCP Apps 把交互 UI 作为协议扩展交付
MCP Apps 官方文档把它定义为可选扩展;当前稳定规范(2026-01-26)让 Tool 通过元数据关联 ui:// Resource,Host 获取 HTML View 并在隔离环境承载。不支持扩展的 Host 仍应消费文本或结构化 Tool Result。它解决跨 Host 的交互 UI 交付,不等同于允许 Server 控制宿主页面。
Server、Host 与 View 分担不同责任
Server 声明 Tool、结果和 UI Resource;Host 决定是否信任、获取、缓存和显示,并代理受控通信;View 在 iframe 中渲染交互。Server 不能假设所有 Host 支持同一尺寸、主题或能力,View 也不能直接取得 Host DOM 和高权限凭据。
UI Resource 与 Tool Result 解耦
ui://资源是可预取、审查和缓存的模板,Tool Result 是本次调用数据。模板、数据 Schema 与 Bridge 协议分别版本化,避免数据变化迫使重新下载 UI,也避免模板把结果藏进不可审计脚本。资源失效时,Host 仍展示 Tool 的文本或结构化事实。
Host 拥有最终安全与体验边界
Host 使用 sandbox iframe、Content Security Policy(CSP)、外部 Origin 白名单和声明式 Permissions。摄像头、麦克风等能力必须显式申请;Bridge 校验消息来源、会话、方法、Schema 与权限,所有 Tool Call 继续经过 Host/Server 策略和审批,不能因为来自 View 就自动可信。
扩展能力必须协商并可降级
连接时协商扩展与版本。未知版本、旧字段、资源加载失败、主题不支持或 View 崩溃都应降级,而不丢失 Tool Result。Server 升级前用目标 Host 矩阵做契约、供应链和权限回归;“在一个桌面客户端可用”不是协议兼容证据。
数据与表现分离
大型表格、文件和媒体通过 Artifact/Resource ID 引用,浏览器按当前用户身份重新取数。引用包含版本、MIME、完整性与过期信息;Renderer 使用确定性代码做格式化、排序、聚合和本地化,不让模型在 Props 中提前计算金额或权限结论。
秘密、未经净化 HTML 和任意 data URL 不进入 UI Schema。即便模型已在 Context 中看到数据,前端也要按展示目的做字段级过滤;View 与 Host 的日志只记录必要摘要和引用,避免把敏感结果复制到多个遥测系统。
流式 UI 更新需要稳定 Patch 语义
首个事件创建带稳定 ID 的占位节点,后续 Patch 指定 base tree version、node ID、operation 与目标 version。客户端只对已存在且版本匹配的节点应用更新,重复 Patch 幂等,缺口或过期 Patch 触发 Snapshot 对账。完成事件到达后做整树 Schema、引用与业务校验。
不要把每个 Token 映射成组件结构变化。结构 Patch 稀疏且可审计,文本或数据内容可在节点内部独立流式更新;否则频繁重排会破坏焦点、读屏与用户正在进行的编辑。
用户交互回到 Agent 时保持语义
View 发出的事件包含 Action ID、component/node ID、tree version、用户输入、幂等键和会话绑定。Host 校验来源与 Schema,Server 再校验身份、权限、对象版本和当前业务状态。旧组件触发新状态、重复提交或越权 Action 均被拒绝,并返回用户可理解的恢复信息。
乐观更新仅用于可可靠回滚的本地状态;外部付款、发信或权限授予必须等待服务端事实。交互结果以事件返回并更新同一节点,不把自由文本“用户点了同意”注入 Prompt 代替结构化授权。
生成式 UI 的验收维度
质量同时检查任务效率、信息匹配、布局、响应式、键盘、读屏、本地化、Schema 合法率、组件误选、降级率和恢复率。安全测试注入脚本字段、危险 URL、超深嵌套、超大数据、过期节点、跨会话消息和越权 Action。
| 层 | 拒绝什么 | 证据 |
|---|---|---|
| Schema | 未知组件、字段、深度和长度越界 | 属性/模糊测试 |
| Registry | 数据分类、版本、状态不匹配 | 组件契约测试 |
| Host | Origin、CSP、Permission、Bridge 越权 | Sandbox 与消息测试 |
| 业务服务 | 身份、对象与动作权限不成立 | 负向授权与审计 |
采用决策应与固定 UI、结构化卡片和纯文本基线比较。若组件误选与维护成本超过任务收益,保留确定性 UI 或只对少量展示场景开放,不以“生成式”作为必须扩张的目标。
主动提取:Schema 已校验通过,为什么 Action 仍须服务端授权?
兼容判断:Host 不支持 MCP Apps 时,Tool 是否应该失败?
资料与事实边界
以下以官方文档、标准、论文和原始工程资料为主。厂商 API、价格、限额与模型能力会变化,落地时仍需按目标版本做能力探测和回归。
- MCP Apps 官方文档与 Stable Specification 2026-01-26 — 稳定规范入口、UI Resource、能力协商、Bridge 与安全模型
- MCP Apps Overview — Server、Host、View、ui://、sandbox 与渐进降级
- MCP Apps API Overview — 当前 SDK 角色、UI Resource 与兼容边界
- W3C Content Security Policy Level 3 — 嵌入内容与外部资源的浏览器策略基础