承接: 协议升级深度实践:MCP无状态化迁移清单(网关、任务与鉴权) · 协议升级后的验收深度实践: MCP迁移实战:用官方 Conformance Suite 给 Client/Server 加回归门禁
调研日期:2026-08-03
本文目标:将 MCP Apps 作为一个可协商、可降级、可审查的交互层接入 MCP Server,而不是让聊天里的 iframe 绕开工具权限、网络限制或人工确认。

        MCP 工具返回文本与结构化数据已经能覆盖大量 Agent 场景,但它不擅长审批表单、实时状态、图表和复杂选择。MCP Apps 为这类场景定义了标准路径:Server 声明 ui:// 资源,Host 在沙箱 iframe 中渲染 View,并经由 Host 把 UI 的交互继续纳入 MCP 的通信与审计路径。MCP Apps 的 2026-01-26 规范已标为稳定;而 2026 年 MCP 的扩展化方向又使它成为值得优先评估的能力。官方规范项目文档 都强调:这是一项可选扩展,Host 支持度不同,不能假设“接了 MCP 就一定能渲染 UI”。

        因此,最可靠的实现目标不是“让每个工具都有一个漂亮界面”,而是:即使 UI 不存在、加载失败或被 Host 拒绝,工具仍以安全且有意义的文本契约工作;而当 UI 可用时,它也只拥有完成该交互所需的最小能力。

适用前提:你维护一个可在隔离环境启动的 MCP Server,并能同时测试至少一个目标 Host 和不支持 MCP Apps 的纯文本路径。本文不假设任意生产 Host 都支持该扩展;上线前应按目标客户端、SDK 与规范版本逐一核验。


一、先看清三方边界:Server、Host 与 View 各做什么

        MCP Apps 的关键不在于“把网页塞进聊天框”,而在于保持下面三条通信边界:

Agent / 模型
      │ 选择工具
      ▼
Host(聊天客户端、能力协商、iframe 沙箱、审计代理)
      │ MCP tools/call、resources/read
      ▼
MCP Server(工具、ui:// 资源、业务授权)

Host  ⇄  postMessage / JSON-RPC  ⇄  View(iframe 中的交互界面)

组件

主要责任

不应承担的责任

MCP Server

声明工具与 UI 资源、执行业务授权、返回文本与结构化数据

假定浏览器存在,或把授权判断下放给前端

Host

协商扩展能力、获取资源、用沙箱 iframe 渲染并代理通信

把任何 UI 按钮默认视为已获用户授权

View

展示数据、收集受限输入、通过 Host 请求允许的工具

直接连接其他 Server、持有高权限凭据或取代 Server 鉴权

        规范将 UI 资源与普通资源区分为 ui:// URI,并要求初始 HTML 资源使用 text/html;profile=mcp-app。View 与 Host 的双向通信使用 JSON-RPC 基础协议;这意味着“刷新”“分页”“提交表单”等界面交互不必暴露成模型上下文里的新工具,但也不意味着它们脱离审计与权限边界。


二、先做能力协商与文本降级,再谈 UI 体验

        MCP Apps 是渐进增强(progressive enhancement),不是前置依赖。Server 应先判断 Host 是否声明了相应 UI 能力:支持时注册带 UI 元数据的工具;不支持时仍提供同一业务结果的文本版本。无论哪条路径,工具都要返回有意义的 content,不能只把关键数据藏在 iframe 里。

Host 状态

Server 行为

用户可见结果

失败信号

已协商 MCP Apps

关联 ui:// 模板,并返回文本摘要和结构化数据

可交互的仪表盘/表单,同时保留可读摘要

UI 存在但文本结果为空,无法审计或降级

不支持或未协商

不注册 UI 元数据,走普通工具结果

可读文本、JSON 或链接说明

工具因缺少 iframe 直接失败

UI 资源读取失败

回退到文本结果并记录受控诊断

用户仍能完成只读判断或请求人工处理

业务结论只能从前端看见

UI 需发起动作

仅暴露经过定义的 app-only 工具,经 Host 转发

明确的动作、理由与确认路径

View 直接调用未声明或跨 Server 的能力

        这条降级策略还能避免一个常见兼容性陷阱:官方项目明确提示 Host 支持会变化。把“目标 Host 已测试的 UI 版本”和“纯文本回退仍通过”都放入发布验收,才不会因一个客户端升级而让核心工具失效。


三、用两类工具划出模型和界面的最小契约

        规范允许在工具的 _meta.ui 中关联 UI 资源,并通过 visibility 区分模型和 App 能否调用。下面是协议级契约示意,用于说明边界;实际注册 API 应以已固定版本的 @modelcontextprotocol/ext-apps SDK 为准。

{
  "name": "show_change_risk",
  "description": "返回一次变更风险评估的文本摘要和交互式详情",
  "inputSchema": { "type": "object", "properties": { "changeId": { "type": "string" } } },
  "_meta": {
    "ui": {
      "resourceUri": "ui://risk-console/change-risk",
      "visibility": ["model", "app"]
    }
  }
}

        上面的业务工具可被模型选择,也能让 UI 获得相同 Server 连接内的结果。对刷新、页码切换或临时表单状态这类不该污染模型上下文的动作,使用 app-only 工具:

{
  "name": "refresh_change_risk",
  "description": "仅供风险面板刷新其已展示的数据",
  "inputSchema": { "type": "object" },
  "_meta": {
    "ui": {
      "resourceUri": "ui://risk-console/change-risk",
      "visibility": ["app"]
    }
  }
}

        visibility: ["app"] 不会让模型看见或调用这个工具,且 app-only 工具只能由同一 Server 连接中的 App 使用。它适合低风险、界面专属的读取和刷新;它不是隐私标签,也不是把危险操作藏起来的方式。只要动作会改变外部系统、权限或数据,就仍需要 Server 侧授权、明确的输入校验,以及符合团队政策的用户确认或人工审批。

        新实现应使用嵌套的 _meta.ui.resourceUri 结构,不要从已弃用的扁平 ui/resourceUri 别名开始。模板和数据也要拆开:模板是可审查、可缓存的静态资源,工具结果才承载每次调用的动态数据。


四、把 iframe 当作隔离边界,而不是信任边界

        MCP Apps 的规范要求 Host 以沙箱 iframe 渲染 HTML,并允许资源通过元数据声明 CSP。最小网络策略应从“没有外连”开始:规范中省略或留空 connectDomains 的含义是没有外部连接;只有确实需要的 API 或 WebSocket 域名才逐项加入。

{
  "uri": "ui://risk-console/change-risk",
  "mimeType": "text/html;profile=mcp-app",
  "_meta": {
    "ui": {
      "csp": {
        "connectDomains": ["https://risk-api.example.internal"],
        "resourceDomains": ["https://static.example.internal"]
      },
      "prefersBorder": true
    }
  }
}

        示例中的域名只是占位形态,生产配置必须替换为团队实际受控的精确来源;不要使用 *、临时 CDN 通配或未经审核的第三方埋点。还应明确以下四条原则:

  1. 资源先审查,再渲染。 UI 模板应进入代码审查、版本固定和安全扫描,Host 可以在工具运行前预取或审查资源并不代表团队不必审查其供应链。
  2. 前端不持久化高权限凭据。 View 需要的数据应由 Server 依据当前请求和身份提供,不能把服务 Token、OAuth refresh token 或客户数据打进 HTML。
  3. 按钮不是审批。 “确认部署”“提交修改”按钮只能发起一个受控工具调用;真正允许或拒绝应发生在 Server/Host 的授权与审批链上。
  4. 内容与指令分离。 从工具结果呈现的外部文本、图表标签或链接都可能是不可信内容,不能反向改变 UI 能调用的工具白名单。

五、用一个只读原型验证端到端,再增加写操作

        官方 ext-apps 仓库提供了 basic-host 和多个示例。要快速检查本地渲染链路,可在隔离环境运行其参考实现:

git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
npm start

        然后先接一个只读工具,例如变更风险摘要、构建状态或资产清单。首个版本不需要“提交”按钮;它只需证明资源发现、Host 渲染、文本回退和错误处理都按预期工作。下面这张验收矩阵比“界面是否好看”更重要:

场景

期望结果

失败信号

支持 UI 的目标 Host

ui:// 资源在沙箱中渲染,工具仍返回文本摘要

UI 成为唯一数据载体

不支持 UI 的 Host

不带 UI 元数据的工具照常返回可读结果

因无 iframe 而报错或空结果

CSP 未列出的外部请求

被阻止并产生受控诊断

模板可随意连接任意域名

app-only 刷新工具

不进入模型的工具列表,只能在同一 App 会话使用

模型能调用隐藏工具,或跨 Server 调用成功

恶意/异常工具数据

文本和 UI 都把它当数据,不扩展权限

页面内容可诱导自动执行高风险动作

UI 资源版本升级

通过固定 SDK/Host 组合验证并可回滚

只更新前端模板,未重测协议与降级路径

        确定只读原型稳定后,再把一个可逆的动作做成 app-only 工具,并让 Server 对每次调用验证身份、对象范围、参数和审计字段。涉及提交、删除、转账、修改生产配置等不可逆操作时,UI 只能改善信息呈现,不能替代现有的人审、权限检查与变更记录。


六、把 MCP Apps 纳入同一套协议与供应链门禁

上一节的 UI 规范并不替代核心 MCP 的升级与 conformance。建议将发布检查拆成三层:

层次

最小检查

不能证明什么

MCP 协议层

当前 SDK/传输组合通过项目的协议测试与回归门禁

UI 模板的 CSP、交互和业务授权正确

App 资源层

ui:// URI、MIME type、资源内容、能力协商与文本回退都被测试

业务动作是否按组织流程获批

业务动作层

参数校验、最小权限、审计关联、人工确认与回滚演练

其他 Host 是否同样支持该 UI

        每次升级 @modelcontextprotocol/ext-apps、核心 SDK、Host 或 UI 打包链时,都应重新运行这三层检查。不要只因某个演示 Host 成功显示面板,就宣布生产客户端、网关和认证路径已经兼容。


七、六个常见误区

1)把 MCP Apps 当成所有 Host 的默认能力

        它是可选扩展,Host 兼容度需要实际验证。文本回退不是“旧客户端兼容补丁”,而是协议设计的一部分。

2)只返回 iframe,不返回文本结果

        这样会让无 UI Host、审计系统和辅助工具失去核心信息,也会让故障时无法判断工具是否已成功。

3)将 app-only 误解为保密或高权限通道

它只控制工具对模型/App 的可见性;Server 仍要逐次鉴权、校验参数并记录动作。

4)放宽 CSP 来解决加载失败

        宽泛的 CDN 或未固定的远程脚本会把调试便利变成供应链与数据外传风险。应先找出真正需要的精确来源。

5)让 UI 按钮直接代表用户同意

        界面点击只是一个输入事件。高影响操作仍需要服务端确认当前身份、目标、范围和审批状态。

6)将 UI 测试与协议升级测试割裂

        传输、能力协商和资源发现的变化都可能破坏界面。MCP Apps 应和核心 MCP 回归一起被验证。


结语

        MCP Apps 最有价值的地方,是把图表、表单和复杂状态从“每个聊天客户端各做一套”变成可协商的协议扩展;但只有把 UI 当成渐进增强的受限视图,它才不会变成新的权限旁路。

        从一个只读面板开始:先让支持 UI 的 Host 能渲染,确认不支持时仍返回完整文本,再测试 CSP、app-only 工具与审计关联。等这条最小闭环被证明稳定后,才逐一增加可逆交互与需要人工确认的业务动作。这样,丰富界面提升的是 Agent 的可用性,而不是它的越权能力。


来源与延伸阅读

更多推荐