MCP Apps 解读:让 MCP Server 在对话里渲染交互式 UI(SEP-1865)
MCP Apps(SEP-1865,Final)拆解:工具声明 ui:// 资源,宿主在沙箱 iframe 里渲染交互式 HTML——数据可视化、表单、仪表盘直接长在对话里。机制、安全模型、官方 SDK 代码与八家宿主支持面一文讲清。
操作步骤
安装依赖
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk;构建侧用 vite 与 vite-plugin-singlefile 把 UI 打包为单文件 HTML。
服务端注册
用 registerAppTool 注册带 _meta.ui.resourceUri 的工具,用 registerAppResource 提供 ui:// 资源,MIME 类型用包内导出的 RESOURCE_MIME_TYPE。
UI 侧接入
new App() 后调用 connect() 建立与宿主的通信,用 ontoolresult 接收宿主推送的工具结果,用 callServerTool 在用户交互时主动调用服务器工具。
本地验证
npm run build 与 npm run serve 后,用官方 basic-host 测试宿主(SERVERS 环境变量指向本机 /mcp 端点)或经 cloudflared 隧道把本地服务器接入 Claude 的自定义连接器。
MCP Apps 是 Model Context Protocol 的官方扩展(SEP-1865,扩展轨,状态 Final):MCP Server 在工具描述里声明一个 ui:// 资源,宿主取回后在对话内以沙箱 iframe 渲染这份交互式 HTML 界面——数据可视化、表单、仪表盘直接长在聊天里,应用还能反向调用服务器工具。本篇基于 2026-09-14 抓取的 MCP 官网总览、SEP-1865 原文与官方构建指南逐节解读(代码示例为指南原文,属文档核对版、未做真机验证),只转述当日可核实的内容。
1. MCP Apps 要解决什么问题
官方总览开篇点题:文本回复的能力是有上限的——有时用户需要的不是「读数据」而是「操作数据」。传统 MCP 工具返回文本、图像或结构化数据,由宿主显示在对话中;MCP Apps 把这一模式扩展为「工具在描述中声明一个交互式 UI 的引用,宿主就地渲染」。
一个自然的疑问是:为什么不直接做个 web 应用发链接?官方给了四个理由:
| 维度 | 独立 web 应用 | MCP App |
|---|---|---|
| 上下文 | 用户切换标签页、丢失所在位置、记不清仪表盘在哪个会话里 | 界面就在产生它的对话里,与讨论同处一屏 |
| 数据流 | 需要自建 API、鉴权与状态管理 | 复用既有 MCP 模式:应用可调用 MCP 服务器上的任意工具,宿主也能把新结果推进来 |
| 能力集成 | 每个应用自建并维护与邮件等外部服务的直连 | 应用可委托宿主路由到用户已连接的能力(受用户同意约束),例如请求「安排这场会议」而由宿主执行 |
| 安全 | 取决于应用自身实现 | 运行在宿主控制的沙箱 iframe 里,不能访问父页面、偷取 cookie 或逃出容器 |
官方也明确了适用边界:如果你的场景吃不到这些性质,普通 web 应用可能更简单;需要与 LLM 对话深度集成的场景,MCP Apps 才是更合适的工具。
2. 工作机制:ui:// 资源、工具元数据与沙箱渲染
官方总览把 MCP Apps 的核心模式概括为两个 MCP 原语的组合:一个在描述中声明 UI 资源的工具,加一个把数据渲染成交互式 HTML 界面的 UI 资源。当模型决定调用一个支持 MCP Apps 的工具时,流程分四步:
- UI 预加载:工具描述包含
_meta.ui.resourceUri字段,指向一个ui://资源。宿主可以在工具被调用之前就预加载该资源——这让「流式工具输入直送应用」等特性成为可能。 - 资源取回:宿主从服务器取回 UI 资源。资源内容是一个 HTML 页面,为简化常与 JavaScript、CSS 打包在一起;应用也可以从
_meta.ui.csp指定的外部来源加载脚本与资源。 - 沙箱渲染:Web 宿主通常把 HTML 渲染在对话内的沙箱 iframe 中。资源的
_meta.ui对象可含permissions(申请麦克风、摄像头等额外能力)与csp(控制可加载外部资源的来源)。 - 双向通信:应用与宿主之间通过一个 JSON-RPC 协议通信——它构成 MCP 的一种方言:部分请求与通知与核心 MCP 协议共享(如
tools/call),部分相似(如ui/initialize),多数则是带ui/前缀的新方法。应用可以请求工具调用、发送消息、更新模型上下文,并接收来自宿主的数据。
一次典型交互的时序(官方图示的文字版):用户说「给我看分析数据」→ 模型调工具 → 服务器返回工具结果 → 宿主把结果推给应用 → 用户在应用里点选、下钻 → 应用发起 tools/call 请求 → 宿主转发给服务器 → 新数据回到应用,界面就地更新 → 应用还能把上下文更新发回给模型。
3. SEP-1865 的三个关键设计决策
SEP-1865(2025-11-21 创建,状态 Final,9 位作者,扩展轨)记录了标准化时的取舍,三条最值得读:
- 预声明资源,而不是内嵌或资源链接:UI 建模为预声明的
ui://资源、经元数据与工具关联,宿主可在工具执行前预取模板(提速)、把展示层与数据层分开(利于缓存)、并在渲染前对 UI 资源做安全审查。SEP 列出的被否替代方案:内嵌资源(MCP-UI 当时的做法,便于开发但性能优化与审查流程有缺口)与资源链接(同样有性能缺口)。 - 复用 MCP 的 JSON-RPC,而不是自定义协议:直接复用既有类型定义与 SDK 基础设施,天然获得超时、错误等能力。被否的替代方案:自定义消息协议(MCP-UI 的 tool/intent/prompt 消息类型可翻译为所提 JSON-RPC 消息的子集)与全局 API 对象(需要宿主注入、无法用于外部 iframe 来源)。
- HTML-only MVP:HTML 通用、安全模型简单(标准 iframe 沙箱)、可生成截图预览、已覆盖观察到的大多数用例。外部 URL 内容类型因模型可见性、截图能力与审查流程的顾虑被推迟——未来可能经
externalIframes能力有效支持。
SEP 的动机部分还交代了标准的来源:社区项目 MCP-UI 先验证了 UI 资源与双向通信模式的可行性与价值,采用者包括 Postman、HuggingFace、Shopify、Goose、ElevenLabs;OpenAI 的 Apps SDK 于 2025 年 11 月上线、以 MCP 为骨架,进一步验证了对话式界面内富 UI 的需求。MCP Apps 把这两条路线统一进单一开放标准,解决「Server 无法可靠预期宿主支持 UI、各宿主行为不一、安全与审计模式不一致、开发者要维护多套实现」的碎片化问题。作为扩展它是可选的、向后兼容,需经扩展能力机制显式协商。
4. 动手写一个 MCP App(官方指南代码)
官方构建指南的环境要求是 Node.js 18 或更高,建议先熟悉 MCP 的工具与资源两个原语。依赖安装:
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
ext-apps 包同时提供服务端辅助(注册工具与资源)和客户端的 App 类(UI 与宿主通信);vite 加 vite-plugin-singlefile 在这里把 UI 与资源打包成单文件 HTML——这是为了省事,可选,配好 CSP 与 CORS 后用任意打包器或不打包都行。
服务端要做两件事:注册一个带 _meta.ui.resourceUri 的工具,注册一个供应 HTML 的资源处理器。指南给出的完整服务端文件(server.ts):
// server.ts
console.log("Starting MCP App server...");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import cors from "cors";
import express from "express";
import fs from "node:fs/promises";
import path from "node:path";
const server = new McpServer({
name: "My MCP App Server",
version: "1.0.0",
});
// The ui:// scheme tells hosts this is an MCP App resource.
// The path structure is arbitrary; organize it however makes sense for your app.
const resourceUri = "ui://get-time/mcp-app.html";
// Register the tool that returns the current time
registerAppTool(
server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time.",
inputSchema: {},
_meta: { ui: { resourceUri } },
},
async () => {
const time = new Date().toISOString();
return {
content: [{ type: "text", text: time }],
};
},
);
// Register the resource that serves the bundled HTML
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(
path.join(import.meta.dirname, "dist", "mcp-app.html"),
"utf-8",
);
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
},
);
// Expose the MCP server over HTTP
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());
expressApp.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
expressApp.listen(3001, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log("Server listening on http://localhost:3001/mcp");
});
按指南的拆解:resourceUri 用 ui:// scheme 告知宿主这是 MCP App 资源,路径结构任意;registerAppTool 注册带 _meta.ui.resourceUri 的工具——宿主调用该工具时取回并渲染 UI,工具结果到达后推送给它;registerAppResource 在宿主请求 UI 资源时供应打包好的 HTML;Express 服务器把 MCP 服务暴露在 3001 端口的 /mcp 路径。
UI 侧是一个 HTML 页面加一个用 App 类与宿主通信的 TypeScript 模块。指南给出的 UI 逻辑(src/mcp-app.ts):
// src/mcp-app.ts
import { App } from "@modelcontextprotocol/ext-apps";
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
const app = new App({ name: "Get Time App", version: "1.0.0" });
// Establish communication with the host
app.connect();
// Handle the initial tool result pushed by the host
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
};
// Proactively call tools when users interact with the UI
getTimeBtn.addEventListener("click", async () => {
const result = await app.callServerTool({
name: "get-time",
arguments: {},
});
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
});
三个关键点(指南原文口径):app.connect() 建立与宿主的通信,应用初始化时调用一次;app.ontoolresult 在宿主把工具结果推给应用时触发(例如工具首次被调用、UI 刚渲染时);app.callServerTool() 让应用主动调用服务器工具——每次调用都涉及一次到服务器的往返,UI 要按这个延迟设计。App 类还提供日志、打开 URL、用应用内的结构化数据更新模型上下文等方法,完整 API 见官方文档。框架不绑定:协议只是标准 web 原语,官方示例库提供 React、Vue、Svelte、Preact、Solid 与原生 JavaScript 六种起步模板。
5. 安全模型:沙箱、CSP 与可审计通信
MCP Apps 的安全模型值得单独一节,因为它回答了「宿主为什么敢渲染第三方界面」:
- iframe 沙箱隔离:应用无法访问父窗口 DOM、读取宿主 cookie 或 localStorage、导航父页面、或在父上下文执行脚本;
- 通信只走 postMessage:宿主控制应用可用的能力——例如限制应用能调用哪些工具、禁用
sendOpenLink能力; - 默认拒绝的 CSP:构建指南明确 UI 资源将渲染在「带默认拒绝 CSP 配置的安全 iframe」中,外部资源需显式配置;
- SEP 威胁模型的四条缓解:iframe 沙箱限制权限、预声明模板可在渲染前审查、全部 UI 到宿主通信走可记录的 JSON-RPC(可审计)、宿主可要求 UI 发起的工具调用获得用户明确同意。
对安全面更广的背景(Confused Deputy、Token Passthrough、SSRF 等 8 类攻击面),可以与本站的 MCP 安全指南对照阅读——MCP Apps 的沙箱模型解决的是其中「不可信内容进入宿主渲染」这一层,与服务器侧的攻击面缓解是互补关系。
6. 测试与宿主支持
指南给两条测试路径。其一是官方测试宿主 basic-host:克隆 ext-apps 仓库,在 examples/basic-host 下安装依赖,用 SERVERS 环境变量指向自己的服务器后 npm start,浏览器打开本机 8080 端口即可选工具、调用并看到沙箱 iframe 里的应用。其二是接入真实宿主 Claude:本地服务器经 npx cloudflared tunnel --url http://localhost:3001 暴露到公网,再把生成的 URL 作为自定义连接器添加进 Claude(官方注明自定义连接器在付费档可用)。
npx cloudflared tunnel --url http://localhost:3001
截至 2026-09-14,MCP 官网列出的 MCP Apps 支持宿主共 8 家:
| 宿主 | 说明 |
|---|---|
| Claude(网页版)/ Claude Desktop | 指南演示的接入路径:cloudflared 隧道 + 自定义连接器 |
| VS Code GitHub Copilot | 编辑器内渲染 |
| Microsoft 365 Copilot | 办件套件场景 |
| Goose | SEP 动机部分列出的 MCP-UI 采用者之一 |
| Postman | 同为 MCP-UI 采用者 |
| MCPJam | MCP 官网首页列出的 MCP 客户端之一 |
| Archestra.AI | 官网支持列表收录 |
要给自建宿主加 MCP Apps 支持,指南给出两条路:用社区的 @mcp-ui/client React 组件渲染与交互;或基于官方 SDK 的 AppBridge 模块——它负责在沙箱 iframe 中渲染应用、消息传递、工具调用代理与安全策略执行,配套的 basic-host 示例展示了集成方式。
工程活跃度的两个锚点(2026-09-14 核对):npm 上 @modelcontextprotocol/ext-apps 最新版 2.0.0 发布于 2026-09-08;ext-apps 仓库最近一次推送在 2026-09-09,构建指南也注明该扩展仍在活跃开发中。
7. 生态位:MCP 扩展体系的一环
MCP Apps 不是孤立的提案,而是 MCP 扩展体系(SEP-2133 定义扩展轨)的一员。官网扩展目录当前还列出:Tasks(SEP-2663,面向长时运行 MCP 操作的异步任务执行)、Skills(SEP-2640,从 MCP 服务器发现与读取 Agent Skills)、OAuth Client Credentials(机器对机器认证)与 Enterprise-Managed Authorization(经企业身份提供商的集中访问控制)——每个扩展在客户端的实现情况由官方 client-matrix 矩阵页统一列出。对扩展机制本身的取舍(哪些进核心规范、哪些留在扩展轨)感兴趣的读者,可对照本站对客户端特性现状的解读。
官方还把「用 AI 编码代理写 MCP App」做成了一等公民:create-mcp-app 技能包含架构指引、最佳实践与可运行示例,Claude Code 可从插件市场安装,其他代理可用 Vercel Skills CLI 安装或手动复制——技能目录表覆盖 VS Code / GitHub Copilot、Gemini CLI、Cline、Goose、Cursor,以及 Codex(~/.codex/skills/)。对 GPTMap 读者,这是 Codex 用户与 MCP 生态的一个直接交点。
官方示例库按五类给出 15 个可运行示例:三维与可视化(CesiumJS 地图、Three.js、着色器)、数据探索(队列热力图、客户分群、Wiki 浏览器)、业务应用(场景建模、预算分配)、媒体(PDF、视频、乐谱、语音合成)与工具(二维码、系统监控、语音转文字)。
8. 对本站读者意味着什么
把断言限定在当日可核实的范围:MCP 官网首页(2026-09-14)把 ChatGPT 列为支持 MCP 的 AI 助手之一——这是协议连接层面的支持;而同日的 MCP Apps 宿主支持列表(上表 8 家)不含 ChatGPT,两个断言不能混用。OpenAI 侧与 MCP Apps 最直接的关联是 SEP-1865 原文写明的事实:Apps SDK 以 MCP 为骨架,其架构显著影响了本规范的设计。此外,Codex 支持 skills 目录、可安装官方 create-mcp-app 技能,是 OpenAI 工具链与 MCP Apps 工作流的另一个交点。本站世界线已收录 MCP Apps 关键事实(2026-09-14 更新)。
9. 下一步
- MCP 协议本体与工具、资源原语:《Model Context Protocol 完全指南:MCP 工作机制与实战》
- 从零搭一个 MCP Server(本文服务端模式的基座):《自己搭一个 MCP Server:从零到发布的完整指南》
- 扩展轨的另一面,核心规范里特性的生灭:《MCP 客户端特性现状:Elicitation 当立,Roots 与 Sampling 已弃用(SEP-2577)》
关键要点
- 定位与状态:MCP Apps 是 MCP 扩展轨(Extensions Track)的交互式 UI 标准,SEP-1865 状态 Final、2025-11-21 创建(2026-09-14 官网核对);规范文本独立维护在 modelcontextprotocol/ext-apps 仓库(含 2026-01-26 版与持续更新的 draft 版)
- 工作机制四步:工具描述带 _meta.ui.resourceUri(宿主可在调用前预加载)→ 宿主取回 ui:// 资源(HTML 常与 JS/CSS 打包为单文件)→ 在沙箱 iframe 内渲染 → 应用与宿主经 postMessage 以 JSON-RPC 方言双向通信(tools/call 与核心协议共享、ui/initialize 相似、多数方法带 ui/ 前缀)
- 对比独立 web 应用的四个官方优势:上下文保留在对话内、双向数据流复用既有 MCP 模式(无需自建 API 与鉴权)、可委托宿主路由用户已连接的能力(需用户同意)、宿主控制的沙箱 iframe 安全隔离
- 安全模型:iframe 沙箱隔离父页面 DOM 与 cookie、localStorage;默认拒绝(deny-by-default)的 CSP;_meta.ui.permissions 可申请麦克风/摄像头等能力、_meta.ui.csp 控制可加载的外部来源;通信为全程可审计的 JSON-RPC;UI 发起的工具调用可要求用户确认
- 官方 SDK @modelcontextprotocol/ext-apps(npm 最新版 2.0.0,2026-09-08 发布):服务端 registerAppTool / registerAppResource / RESOURCE_MIME_TYPE,UI 侧 App 类(connect / ontoolresult / callServerTool);宿主实现可选 AppBridge 模块或社区 @mcp-ui/client React 组件
- 宿主支持(2026-09-14 官网列出 8 家):Claude、Claude Desktop、VS Code GitHub Copilot、Microsoft 365 Copilot、Goose、Postman、MCPJam、Archestra.AI;标准统一了社区 MCP-UI 与 OpenAI Apps SDK 两条路线
常见问题
官方参考
相关文章
Codex CLI 接入 MCP 服务器:config.toml 配置全字段手册
Codex CLI 通过 config.toml 里的 [mcp_servers] 表接 MCP 服务器:stdio 用 command/args/env,远程用 url/bearer_token/http_headers,还有 startup_timeout_sec、enabled_tools、required 等控制面。本文按 codex 源码的配置结构逐项讲清。
阅读全文Model Hardware Standard 解读:MHS 如何让 AI 智能体安全操控物理设备
Anthropic 8-27 公告的 Model Hardware Standard(MHS)研究预览版拆解:标准化驱动 + read/write 原语 + MCP/CLI/代码文件三种控制机制,六家机构实测数据与八家硬件厂商跟进——开源在即的物理设备操控标准。
阅读全文MCP 客户端特性现状:Elicitation 当立,Roots 与 Sampling 已弃用(SEP-2577)
2026-07-28 规范版重排了 MCP 客户端特性:Roots 与 Sampling 被弃用(SEP-2577,保留期至少 12 个月),Elicitation 保留并新增 URL 模式。逐条拆解三特性的现状、弃用原因与迁移方向。
阅读全文订阅 GPTMap Weekly
每周一封邮件,精选 OpenAI 重要更新、深度解读与最佳实践。无广告,可随时退订。
提交后将在新标签页打开 Buttondown 完成订阅确认。