MCP Apps:经 `ui://` 的交互式 UI 资源


文档摘要

MCP Apps:经 的交互式 UI 资源 本节摘要:纯文本的工具输出,限制了 Agent 能展示什么。MCP Apps(SEP-1724,2026 年 1 月 26 日官方发布)让一个工具能返回沙箱化的交互式 HTML,内联渲染在 Claude Desktop、ChatGPT、Cursor、Goose、VS Code 里。仪表盘、表单、地图、3D 场景,全通过一个扩展完成。本节走查 资源 scheme、 MIME、iframe 沙箱的 postMessage 协议,以及「让服务端渲染 HTML」随之而来的安全面。 学习目标 阅读完本节,你应当能够: 从工具调用返回一个 资源,并设对 MIME 与元数据。 用 、 、 声明工具关联的 UI。

MCP Apps:经 ui:// 的交互式 UI 资源

本节摘要:纯文本的工具输出,限制了 Agent 能展示什么。MCP Apps(SEP-1724,2026 年 1 月 26 日官方发布)让一个工具能返回沙箱化的交互式 HTML,内联渲染在 Claude Desktop、ChatGPT、Cursor、Goose、VS Code 里。仪表盘、表单、地图、3D 场景,全通过一个扩展完成。本节走查 ui:// 资源 scheme、text/html;profile=mcp-app MIME、iframe 沙箱的 postMessage 协议,以及「让服务端渲染 HTML」随之而来的安全面。

学习目标

阅读完本节,你应当能够:

  1. 从工具调用返回一个 ui:// 资源,并设对 MIME 与元数据。
  2. _meta.ui.resourceUri_meta.ui.csp_meta.ui.permissions 声明工具关联的 UI。
  3. 实现 iframe 沙箱的 postMessage JSON-RPC,用于 UI 到 host 的通信。
  4. 应用 CSP 与 permissions-policy 默认值,防御源自 UI 的攻击。

一、问题与直觉

一个 2025 年的 visualize_timeline 工具能返回「这是按时间排的 14 条笔记:……」——那是一段文字。用户其实想要交互式时间线。MCP Apps 之前,选项是:客户端专属的 widget API(Claude artifacts、OpenAI Custom GPT HTML),或干脆没 UI。

MCP Apps(SEP-1724,2026-01-26 发布)把契约标准化。工具结果含一个 resource,URI 是 ui://...,MIME 是 text/html;profile=mcp-app。host 在沙箱 iframe 里渲染它,配受限 CSP,除非显式授予否则无网络。iframe 内的 UI 经一种微型 postMessage JSON-RPC 方言向 host 发消息。

每个兼容客户端(Claude Desktop、ChatGPT、Goose、VS Code)都以相同方式渲染同一个 ui:// 资源。一个服务端、一个 HTML bundle、通用 UI。

二、从零实现

ui:// 资源 scheme

一个工具返回:

{ "content": [ {"type": "text", "text": "这是你的笔记时间线:"}, {"type": "ui_resource", "uri": "ui://notes/timeline"} ], "_meta": { "ui": { "resourceUri": "ui://notes/timeline", "csp": { "defaultSrc": "'self'", "scriptSrc": "'self' 'unsafe-inline'", "connectSrc": "'self'" }, "permissions": [] } } }

host 随后对该 ui://notes/timeline URI 调 resources/read,拿回:

{ "contents": [{ "uri": "ui://notes/timeline", "mimeType": "text/html;profile=mcp-app", "text": "<!doctype html>..." }] }

iframe 沙箱

host 把 HTML 渲染进一个沙箱 <iframe>:

  • sandbox="allow-scripts allow-same-origin"(或按服务端声明更严)
  • 服务端声明的 CSP 经响应头应用。
  • 无 cookies、无来自 host origin 的 localStorage。
  • 网络访问限制在 CSP 的 connectSrc

postMessage 协议

iframe 经 window.postMessage 与 host 通信。一种微型 JSON-RPC 2.0 方言:

始终targetOrigin 钉在对端的精确 origin,接收侧在处理任何 payload 前,把 event.origin 对照白名单校验。绝不在任一侧用 "*"——消息体携带工具调用与资源读取。

// iframe → host(钉 host origin) window.parent.postMessage({ jsonrpc: "2.0", id: 1, method: "host.callTool", params: { name: "notes_update", arguments: { id: "note-14", title: "..." } } }, "https://host.example.com"); // host → iframe(钉 iframe origin) iframe.contentWindow.postMessage({ jsonrpc: "2.0", id: 1, result: { content: [...] } }, "https://iframe.example.com"); // 双方接收侧 window.addEventListener("message", (event) => { if (event.origin !== "https://expected-peer.example.com") return; // 此处处理 event.data 安全 });

UI 可调的 host 侧方法:

  • host.callTool(name, arguments)——调用服务端工具。
  • host.readResource(uri)——读 MCP 资源。
  • host.getPrompt(name, arguments)——取提示模板。
  • host.close()——关闭 UI。

每个调用仍走 MCP 协议,继承服务端的权限。

权限

_meta.ui.permissions 列表请求额外能力:

  • camera——访问用户摄像头(扫文档 UI 用)。
  • microphone——语音输入。
  • geolocation——位置。
  • network:*——比 connectSrc 单独允许的更宽网络。

每个权限在 UI 渲染前都让用户看到一次提示。

安全风险

iframe 里的 HTML 仍是 HTML。新攻击面:

  • 经 UI 的提示注入:恶意服务端 UI 能显示看起来像系统消息的文字骗用户。host 渲染应视觉上区分服务端 UI 与 host UI。
  • connectSrc 外泄:若 CSP 允 connect-src: *,UI 能把数据发到任何地方。默认应严格。
  • 点击劫持:UI 覆盖 host 外壳。host 必须防 z-index 操纵,强制 opacity 规则。
  • 抢焦点:UI 抢键盘焦点并捕获下一条消息。host 必须拦截。

第 15 节把这些作为 MCP 安全的一部分深入;本节先引入。

ui/initialize 握手

iframe 加载后,经 postMessage 发 ui/initialize:

{"jsonrpc": "2.0", "id": 0, "method": "ui/initialize", "params": {"theme": "dark", "locale": "en-US", "sessionId": "..."}}

host 响应能力集与一个 session token。UI 在后续每个 host 调用上用该 token。

AppRenderer / AppFrame SDK 原语

ext-apps SDK 暴露两个便利原语:

  • AppRenderer(服务端侧)——包住一个 React/Vue/Solid 组件,发出带正确 MIME 与元数据的 ui:// 资源。
  • AppFrame(客户端侧)——接收资源、挂载 iframe、中介 postMessage。

你可以用它们,或手撸 HTML 与 JSON-RPC。

生态状态

MCP Apps 于 2026-01-26 发布。2026 年 4 月客户端支持情况:Claude Desktop(2026-01 起全支持)、ChatGPT(经 Apps SDK 全支持)、Cursor(beta,设置开启)、VS Code(仅 Insider)、Goose(全支持)、Zed/Windsurf(路线图上)。生产服务端:仪表盘、地图可视化、数据表、图表构建器、沙箱 IDE 预览。

三、框架对比

维度 纯文本工具结果 客户端专属 widget MCP Apps(ui://)
跨 host 通用 每家不同 通用
富交互
安全面 中(iframe 沙箱 + CSP)
生态(2026-04) 各家碎片 主流 host 支持

💡 心法:MCP Apps 把「富 UI」从客户端碎片化统一成 ui:// + 沙箱 iframe + postMessage。默认严格 CSP、最小权限、视觉区分服务端/host UI——这是把 HTML 放进 Agent 的安全底线。

四、可复用产物

本节产出 outputs/skill-mcp-apps-spec.md——给定一个会受益于交互式 UI 的工具,它产出完整 MCP Apps 契约:ui:// URI、CSP、权限、postMessage 入口、安全清单。

code/main.py 把笔记服务端扩展为一个 visualize_timeline 工具,返回 ui://notes/timeline 资源,加一个 resources/read 处理函数返回一小份但完整的 HTML bundle(含 SVG 时间线)。HTML 用标准库模板生成,无构建系统;postMessage 在 JS 注释里勾勒(标准库驱动不了浏览器)。

五、练习

  1. 直接看 HTML:运行 code/main.py,检查发出的 HTML,直接在浏览器打开,验证 SVG 渲染。再勾勒 UI 调 host.callTool("notes_update", ...) 会用的 postMessage 契约。

  2. 收紧 CSP:移除 'unsafe-inline',用 nonce 脚本策略。HTML 生成代码要改什么?

  3. 加编辑器 UI:加第二个 UI 资源 ui://notes/editor,带就地编辑笔记的表单。用户提交时 iframe 调 host.callTool("notes_update", ...)

  4. 审计攻击面:审计 UI 的攻击面。恶意服务端能在哪儿注入内容?iframe 沙箱挡什么、不挡什么?

  5. 读规范补能力:读 SEP-1724 规范,找出本玩具实现未用的一个 MCP Apps SDK 能力。(提示:组件级状态同步。)

本节要点回顾

  1. MCP Apps(SEP-1724, 2026-01-26):工具返回 ui:// 资源,MIME text/html;profile=mcp-app,host 沙箱 iframe 渲染。
  2. _meta.ui 三件套:resourceUri、CSP、permissions。
  3. iframe 沙箱:allow-scripts allow-same-origin、服务端 CSP、无 cookies/localStorage、网络限 connectSrc
  4. postMessage JSON-RPC:targetOrigin 钉精确 origin、接收侧校验 event.origin、绝不用 "*"(消息体携带工具调用)。
  5. host 侧四方法:host.callToolhost.readResourcehost.getPrompthost.close,均走 MCP 协议继承权限。
  6. 权限:camera/microphone/geolocation/network:*,渲染前用户确认。
  7. 四大安全风险:经 UI 提示注入、connectSrc 外泄、点击劫持、抢焦点——默认严格 CSP + 视觉区分 + z-index/opacity 规则。
  8. ui/initialize 握手:iframe 加载后发,host 返能力集 + session token。
  9. SDK 原语:AppRenderer(服务端,组件→资源)、AppFrame(客户端,挂 iframe 中介 postMessage)。
  10. 生态(2026-04):Claude/ChatGPT/Goose 全支持,Cursor beta,VS Code Insider。

下一节,我们进入 MCP 安全的核心战场——工具投毒:恶意描述、混洗攻击、敏感数据读取,以及如何防。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U