客户端:渲染与双层沙箱
本章是 MCP-UI 的核心。宿主(如 Claude、VSCode)用
@mcp-ui/client把工具的 UI 渲染出来。难点不是「显示一段 HTML」——浏览器天生会——而是怎么在显示不可信 HTML 的同时,既隔离它、又让它能安全地回调工具。答案是一套双层 iframe 代理 + postMessage 桥。
1. 两个组件的分工
MCP-UI 客户端只有两个 React 组件,是上下层关系:
| 组件 | 你给它什么 | 它负责 |
|---|---|---|
AppRenderer(高层) | client + toolName | 取 HTML(读 _meta 拿 URI、resources/read)+ 建 AppBridge + 注册回调 |
AppFrame(低层) | 现成的 html + 现成的 appBridge | 建沙箱 iframe + 连桥 + 推 HTML/输入/结果 |
想自己控制取 HTML 的逻辑,可以绕过 AppRenderer 直接用 AppFrame(AppFrame.tsx:96-100 的注释明说了这个分层意图)。
2. AppRenderer:取 HTML + 接线
AppRenderer(sdks/typescript/client/src/components/AppRenderer.tsx:267)用一串 useEffect 把生命周期串起来。两件主要的事:
2.1 怎么拿到 HTML
Effect 2(AppRenderer.tsx:449-539)的取 HTML 逻辑分两步走,顺序和代码一致——注意「URI 解析」和「读 HTML」各有各的优先级,别混为一条链:
第 0 步:直接给了 html prop? → 用它,setHtml 后立刻返回,跳过一切抓取(449-452)
否则,先解析出 ui:// URI(474-489,toolResourceUri 优先):
toolResourceUri 有? → 直接用它当 URI
否则 client 有? → getToolUiResourceUri() 读 tool._meta 拿 ui:// URI
两者都无 → 报错
再用这个 URI 读 HTML(497-521,client 优先):
client 有? → readToolUiResourceHtml() 走 resources/read
否则 onReadResource 有? → 用回调去读(适合 client 在别的上下文)
两者都无 → 报错
换句话说,三种输入模式(html prop / client / toolResourceUri + onReadResource)的进入门槛在 449-466 校验,但真正取值时:URI 解析先看 toolResourceUri、读 HTML 先看 client,不是一条简单的「① html ② client ③ toolResourceUri」线性优先级。
取 URI 的 getToolUiResourceUri(sdks/typescript/client/src/utils/app-host-utils.ts:95-121)会翻页遍历 listTools 找到目标工具,再用 ext-apps 的 getToolUiResourceUri(tool) 从 _meta 里抽 URI,并强制校验它 ui:// 开头。
读 HTML 的 readToolUiResourceHtml(同文件 123-150)对 resources/read 的结果做严格校验:必须恰好 1 个 content、MIME 必须等于 RESOURCE_MIME_TYPE,text 直接用、blob 走 atob 解码。