跳到主要内容

巧妙之处、边界与代码地图

把前两章的「怎么做」提炼成「为什么妙、它不做什么、和谁怎么比」,最后给一张可 grep 的跳转表。

1. 巧妙之处(可借鉴的技术)

1.1 双层 iframe + document.write 绕开 CSP base-uri

妙在把「中继逻辑」和「不可信内容」拆到两层 iframe:外层代理页是宿主控制的固定脚本,负责转发 postMessage;内层用 document.write 注入工具 HTML。改用 document.write 而非 srcdoc 是为了规避 srcdoc 与 CSP base-uri 的冲突(scripts/proxy/index.html:53-65,ProxyScript.test.ts:63-72)。

1.2 能力默认关闭(capability gating)

UI 想调工具/开链接,宿主不传对应回调就直接 MethodNotFound(AppRenderer.tsx:369-384)。不可信 UI 拿到的是一把把单独配发的钥匙,而非一串万能钥匙。配合 hostCapabilities(AppRenderer.tsx:354-358)向 UI 声明「我支持什么」,让 UI 能优雅降级。

1.3 SSRF 的多层纵深防御

externalUrl 抓取把「协议白名单 + 主机名黑名单 + 私网/链路本地 IP 段 + 超时 + 响应体大小上限」叠在一起(sdks/typescript/server/src/utils.ts:48-142)。尤其 169.254/16 那条专挡云元数据端点,是真实威胁建模的产物。

1.4 StrictMode / 异步竞态的防护

两处值得学:

  • cancelRef 同步赋值,让 effect 清理能取消尚未 resolve 的沙箱握手 Promise(app-host-utils.ts:49-58,修了 ce5893e 的 StrictMode 未处理拒绝)。
  • 每个发送 effect 都校验 currentAppBridgeRef.current === appBridge,防止快速切换 app 时把消息发错桥(AppFrame.tsx:253/275/284)。

1.5 大 HTML 的 base64 分块

utf8ToBase64 按 8192 字节分块喂 String.fromCharCode,避免一次性展开大数组爆栈(utils.ts:222-227)。小细节,但处理几 MB 的 UI 时是必需的。

2. 边界与局限(诚实)

  • 它本身不实现握手协议。 AppBridgePostMessageTransport、JSON-RPC 握手、sendSandboxResourceReady 等都在外部依赖 @modelcontextprotocol/ext-apps(client 用 ^1.2.0,server 用 ^0.3.1,见各 package.json)。本仓库是「符合标准的薄封装 + 渲染层 + 资源打包」。要读握手细节得去看 ext-apps。
  • 客户端只有 React。 @mcp-ui/client 全是 React 组件(AppRenderer/AppFrame),没有框架无关的渲染入口;Web Component 在本 commit 已不导出(见第 02 章 §7)。非 React 宿主需自行基于 ext-apps 实现。
  • externalUrl 预抓取只在 TS server。 Ruby/Python 的 create_ui_resource 对 externalUrl 不抓取、不注 <base>、无 SSRF 防护(Python core.py:131、Ruby process_external_url_content)。
  • SSRF 不挡 DNS rebinding。 isPrivateIPv4 只校验字面量 IP 字符串,域名解析到内网不在防护内 (inferred,validateExternalUrl 仅检查 parsed.hostname 字符串)。
  • 沙箱用了 allow-same-origin 内层/代理 iframe 的 sandbox 含 allow-same-origin(app-host-utils.ts:31index.html:111)——这是 document.write 跨文档操作所必需,但意味着隔离强度依赖 CSP 与 origin 隔离而非纯 sandbox 属性。所以文档反复强调用「读 ?csp= 设 HTTP 头」的代理服务器才是 tamper-proof(AppFrame.tsx:50-60)。
  • 文档漂移。 README 仍描述已删除的 legacy API,容易误导。

3. 横向对比(同 shelf 兄弟)

MCP-UI 属于 ai-protocol-reference协议 + Web 互操作这一类。它和兄弟项目的取舍差异:

维度MCP-UI一般 MCP server SDK
工具返回可交互 UI(HTML/外链)文本 / 结构化 JSON
信任模型UI 不可信,沙箱 + 能力门控工具代码可信
传输postMessage/JSON-RPC over iframestdio / HTTP-SSE
这层做什么资源打包 + 渲染隔离工具注册 + 协议握手

它和 MCP 本体的关系:MCP 定义工具/资源的传输,MCP-UI(=MCP Apps 标准)在其上加一层「UI 资源 + 渲染契约」。README 自述它「pioneered UI over MCP」,其模式直接影响了 MCP Apps 规范的成型。

接入点(写作时未逐一核对兄弟 doc 路径,按 shelf 约定链接):本 shelf 的 MCP 协议总览 doc、以及任何 MCP server/client SDK 的子库 doc,都是它的近邻。

4. 代码地图(导航索引)

用符号名 grep 比行号抗漂移。

主题文件符号
打包 UI 资源(TS)sdks/typescript/server/src/index.tscreateUIResource
UI → 宿主 实验性请求sdks/typescript/server/src/index.tssendExperimentalRequest
externalUrl 抓取sdks/typescript/server/src/utils.tsfetchExternalUrl
SSRF 校验sdks/typescript/server/src/utils.tsvalidateExternalUrlisPrivateIPv4BLOCKED_HOSTNAMES
<base> 注入sdks/typescript/server/src/utils.tsinjectBaseTag
元数据加前缀sdks/typescript/server/src/utils.tsgetAdditionalResourcePropsUI_METADATA_PREFIX
base64 分块编码sdks/typescript/server/src/utils.tsutf8ToBase64
资源/内容类型sdks/typescript/server/src/types.tsCreateUIResourceOptionsResourceContentPayloadRESOURCE_MIME_TYPE
高层渲染组件sdks/typescript/client/src/components/AppRenderer.tsxAppRendererAppRendererProps
低层 iframe 组件sdks/typescript/client/src/components/AppFrame.tsxAppFramebuildSandboxUrlSandboxConfig
沙箱握手sdks/typescript/client/src/utils/app-host-utils.tssetupSandboxProxyIframeSandboxCancelRef
取/读 UI 资源sdks/typescript/client/src/utils/app-host-utils.tsgetToolUiResourceUrireadToolUiResourceHtml
双层 iframe 代理页sdks/typescript/client/scripts/proxy/index.htmlrenderHtmlInIframeui-html-contentui-proxy-iframe-ready
提取 UI 元数据sdks/typescript/client/src/utils/metadataUtils.tsgetUIResourceMetadata
判断是否 UI 资源sdks/typescript/client/src/utils/isUIResource.tsisUIResource
客户端能力声明sdks/typescript/client/src/capabilities.tsUI_EXTENSION_CAPABILITIESUI_EXTENSION_NAME
客户端导出面sdks/typescript/client/src/index.ts(看这里确认哪些 API 仍存在)
Ruby 打包sdks/ruby/lib/mcp_ui_server.rbMcpUiServer.create_ui_resource
Python 打包 + action 辅助sdks/python/server/src/mcp_ui_server/core.pycreate_ui_resourceui_action_result_tool_call
Python 类型sdks/python/server/src/mcp_ui_server/types.pyCreateUIResourceOptionsUIActionResult*