GPTMap

MCP Apps 解读:让 MCP Server 在对话里渲染交互式 UI(SEP-1865)

MCP Apps(SEP-1865,Final)拆解:工具声明 ui:// 资源,宿主在沙箱 iframe 里渲染交互式 HTML——数据可视化、表单、仪表盘直接长在对话里。机制、安全模型、官方 SDK 代码与八家宿主支持面一文讲清。

TL;DR
MCP Apps 是 MCP 的官方扩展(SEP-1865,状态 Final,2025-11-21 创建):Server 在工具描述里用 _meta.ui.resourceUri 声明一个 ui:// 资源,支持该扩展的宿主(Claude、VS Code GitHub Copilot 等)把这份 HTML 渲染进对话内的沙箱 iframe,应用经 postMessage 以 JSON-RPC 方言与宿主双向通信、可直接回调服务器工具。官方 SDK 为 @modelcontextprotocol/ext-apps(2026-09-08 发布 v2.0.0)。该标准统一了社区 MCP-UI 与 OpenAI Apps SDK(2025-11 上线、以 MCP 为骨架)两条路线。
MCP Apps 是 Model Context Protocol 的官方扩展(SEP-1865,扩展轨,状态 Final):MCP Server 通过工具描述中的 _meta.ui.resourceUri 指向一个 ui:// scheme 的 UI 资源(HTML,MIME 类型 text/html;profile=mcp-app),支持该扩展的宿主在对话内以沙箱 iframe 渲染这个交互式界面,应用与宿主之间经 postMessage 通道用 JSON-RPC(ui/ 前缀方言)双向通信。

操作步骤

  1. 安装依赖

    npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk;构建侧用 vite 与 vite-plugin-singlefile 把 UI 打包为单文件 HTML。

  2. 服务端注册

    用 registerAppTool 注册带 _meta.ui.resourceUri 的工具,用 registerAppResource 提供 ui:// 资源,MIME 类型用包内导出的 RESOURCE_MIME_TYPE。

  3. UI 侧接入

    new App() 后调用 connect() 建立与宿主的通信,用 ontoolresult 接收宿主推送的工具结果,用 callServerTool 在用户交互时主动调用服务器工具。

  4. 本地验证

    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 的工具时,流程分四步:

  1. UI 预加载:工具描述包含 _meta.ui.resourceUri 字段,指向一个 ui:// 资源。宿主可以在工具被调用之前就预加载该资源——这让「流式工具输入直送应用」等特性成为可能。
  2. 资源取回:宿主从服务器取回 UI 资源。资源内容是一个 HTML 页面,为简化常与 JavaScript、CSS 打包在一起;应用也可以从 _meta.ui.csp 指定的外部来源加载脚本与资源。
  3. 沙箱渲染:Web 宿主通常把 HTML 渲染在对话内的沙箱 iframe 中。资源的 _meta.ui 对象可含 permissions(申请麦克风、摄像头等额外能力)与 csp(控制可加载外部资源的来源)。
  4. 双向通信:应用与宿主之间通过一个 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办件套件场景
GooseSEP 动机部分列出的 MCP-UI 采用者之一
Postman同为 MCP-UI 采用者
MCPJamMCP 官网首页列出的 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 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 两条路线

常见问题

MCP Apps 是 MCP 的官方扩展(SEP-1865,状态 Final)。普通 MCP 工具返回文本、图像或结构化数据,由宿主显示在对话里;MCP Apps 让工具在描述中通过 _meta.ui.resourceUri 声明一个 ui:// 资源,宿主取回后在对话内以沙箱 iframe 渲染交互式 HTML 界面(数据可视化、表单、仪表盘等),应用还能反过来调用 MCP 服务器上的工具、把数据更新推回界面。

官方参考

相关文章

订阅 GPTMap Weekly

每周一封邮件,精选 OpenAI 重要更新、深度解读与最佳实践。无广告,可随时退订。

提交后将在新标签页打开 Buttondown 完成订阅确认。

GPTMap Editorial发布于 2026-09-14 19 分钟阅读
测试环境(EEAT)
最后测试时间:2026-09-14
使用模型:gpt-5.6