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。
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-appMIME、iframe 沙箱的 postMessage 协议,以及「让服务端渲染 HTML」随之而来的安全面。
阅读完本节,你应当能够:
ui:// 资源,并设对 MIME 与元数据。_meta.ui.resourceUri、_meta.ui.csp、_meta.ui.permissions 声明工具关联的 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>..." }] }
host 把 HTML 渲染进一个沙箱 <iframe>:
sandbox="allow-scripts allow-same-origin"(或按服务端声明更严)connectSrc。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。新攻击面:
connectSrc 外泄:若 CSP 允 connect-src: *,UI 能把数据发到任何地方。默认应严格。第 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。
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 注释里勾勒(标准库驱动不了浏览器)。
直接看 HTML:运行 code/main.py,检查发出的 HTML,直接在浏览器打开,验证 SVG 渲染。再勾勒 UI 调 host.callTool("notes_update", ...) 会用的 postMessage 契约。
收紧 CSP:移除 'unsafe-inline',用 nonce 脚本策略。HTML 生成代码要改什么?
加编辑器 UI:加第二个 UI 资源 ui://notes/editor,带就地编辑笔记的表单。用户提交时 iframe 调 host.callTool("notes_update", ...)。
审计攻击面:审计 UI 的攻击面。恶意服务端能在哪儿注入内容?iframe 沙箱挡什么、不挡什么?
读规范补能力:读 SEP-1724 规范,找出本玩具实现未用的一个 MCP Apps SDK 能力。(提示:组件级状态同步。)
ui:// 资源,MIME text/html;profile=mcp-app,host 沙箱 iframe 渲染。_meta.ui 三件套:resourceUri、CSP、permissions。allow-scripts allow-same-origin、服务端 CSP、无 cookies/localStorage、网络限 connectSrc。targetOrigin 钉精确 origin、接收侧校验 event.origin、绝不用 "*"(消息体携带工具调用)。host.callTool、host.readResource、host.getPrompt、host.close,均走 MCP 协议继承权限。network:*,渲染前用户确认。connectSrc 外泄、点击劫持、抢焦点——默认严格 CSP + 视觉区分 + z-index/opacity 规则。ui/initialize 握手:iframe 加载后发,host 返能力集 + session token。下一节,我们进入 MCP 安全的核心战场——工具投毒:恶意描述、混洗攻击、敏感数据读取,以及如何防。