MCP Apps 完全指南:MCP 交互式 UI 如何工作(2026)
MCP Apps 让 MCP 服务器以 ui:// 资源的形式交付交互式 UI,并在 Claude、ChatGPT、VS Code 和 Goose 的沙箱 iframe 中渲染。本文讲清规范现状、SDK 和一个可运行的最小示例。
MCP Apps 是 Model Context Protocol 的官方扩展,让 MCP 服务器可以返回交互式 UI(图表、表单、地图、仪表盘),而不只是纯文本。工具通过元数据指向一个装着 HTML 页面的 ui:// 资源,宿主(Claude、ChatGPT、VS Code、Goose 等)把这个页面放进沙箱 iframe 里渲染,页面再通过 JSON-RPC 和宿主通信。同一份代码可以在所有支持该扩展的宿主中运行。
本文介绍规范目前的状态、哪些客户端能渲染 MCP Apps、取自官方 SDK 的最小可运行示例,以及 MCP Apps 和 OpenAI Apps SDK、MCP-UI 之间的关系。
现状速览(2026 年 10 月)
| 项目 | 当前状态 |
|---|---|
| 规范 | SEP-1865「MCP Apps: Interactive User Interfaces for MCP」,Extensions 轨道 |
| 状态 | Stable(稳定版),版本 2026-01-26;另有 draft 版本在开发中 |
| 发布 | 2026 年 1 月 26 日,第一个官方 MCP 扩展 |
| 仓库 | modelcontextprotocol/ext-apps |
| SDK | @modelcontextprotocol/ext-apps(子路径 /server、/react、/app-bridge),v2.x |
| MIME 类型 | text/html;profile=mcp-app |
| 关联字段 | 工具上的 _meta.ui.resourceUri |
这份 SEP 最早发布于 2025 年 11 月 21 日,作者包括 MCP-UI 的创建者以及来自 Anthropic 和 OpenAI 的成员。
MCP Apps 如何工作
ext-apps README 把流程概括为四步:
- 定义工具:工具声明一个装着 HTML 界面的
ui://资源。 - 调用工具:模型照常调用你服务器上的工具。
- 宿主渲染:宿主拉取该资源,并在沙箱 iframe 中显示。
- 双向通信:宿主通过通知把工具的输入和结果传给 UI,UI 也可以经由宿主调用其他工具。
也就是说,UI 只是又一个 MCP 资源,所以 MCP Apps 能自然融入现有协议。想复习工具和资源的区别,可以看 MCP 资源与工具对比。
协议的组成部分
- UI 资源:URI 为
ui://、MIME 类型为text/html;profile=mcp-app的资源。它的_meta.ui可以声明csp(允许的来源)、permissions、domain和prefersBorder。 - 工具关联:工具的
_meta.ui.resourceUri指定要渲染的资源。_meta.ui.visibility可以是["model", "app"](默认),也可以是["app"],用于只给 UI 调用、模型看不到的辅助工具。 - 握手:View 发送
ui/initialize,收到hostContext(主题、显示模式、语言等),然后发送ui/notifications/initialized。 - 数据输入:
ui/notifications/tool-input和ui/notifications/tool-result传递本次调用的参数和结果。 - 对外动作:View 可以发送
tools/call、ui/message(往对话里发消息)、ui/open-link和ui/update-model-context。 - 显示模式:
inline(默认)、fullscreen和pip(画中画)。宿主不必三种都支持。
安全模型
所有内容都运行在沙箱 iframe 里,CSP 默认拒绝一切。如果资源没有声明 ui.csp,规范要求宿主使用 connect-src 'none',也就是说 UI 无法调用任何外部 API。要放行外部来源,需要分别列在 connectDomains(fetch/WebSocket)、resourceDomains(脚本、图片、字体、样式)、frameDomains(嵌套 iframe)和 baseUriDomains 中。宿主可以把规则收得更紧,但绝不能放行你没有声明的域名。发布公告还补充说,UI 与宿主之间的所有消息都是可审计的 JSON-RPC,宿主也可以要求用户批准由 UI 发起的工具调用。更完整的威胁模型请看 MCP 安全最佳实践。
哪些客户端支持 MCP Apps
下表只列出我们能对照官方文档核实的宿主:
| 宿主 | 支持情况 | 说明 |
|---|---|---|
| Claude(网页版和桌面版) | 支持,发布当天起 | Claude 文档:显示 App 前会先征求许可(Allow / Always allow)。设置了 ui.domain 的远程 App 必须使用 Claude 规定的哈希格式 *.claudemcpcontent.com |
| ChatGPT | 支持 | OpenAI 更新日志称,自 2026-02-22 起 ChatGPT「fully compatible with the MCP Apps spec」。见 Add UI to your MCP server |
| VS Code(GitHub Copilot agent) | 支持 | 2026-01-26 宣布,先上 Insiders,随后进入 Stable |
| Goose | 支持 | 官方公告中的首批宿主之一 |
| Postman、MCPJam、mcp-use Inspector、Alpic Playground | 已列出 | ext-apps README 中列为支持的宿主 |
各宿主对具体功能的支持仍有差异,尤其是显示模式和宿主专属的扩展能力。工具本身仍应返回有用的文本,这样不支持 MCP Apps 的客户端也能完成任务。完整的支持矩阵见 modelcontextprotocol.io/clients,我们的 /clients 目录也收录了各类 MCP 客户端。
动手做一个最小 MCP App
下面的代码直接来自官方 quickstart 示例(ext-apps 2.x,Node.js 20+):一个 get-time 工具,它的 UI 显示服务器时间,并带一个刷新按钮。如果你从没写过服务器,先看 如何构建 MCP 服务器。
1. 安装依赖
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@^2.0.0 \
@modelcontextprotocol/server@^2.0.0 \
@modelcontextprotocol/node@^2.0.0 \
@modelcontextprotocol/express@^2.0.0 \
zod@^4.2.0
npm install -D typescript vite vite-plugin-singlefile tsx
宿主把 UI 当作单个 HTML 资源来加载,所以 quickstart 用 Vite 配合 vite-plugin-singlefile 把 JavaScript 内联打包进去。
2. 注册工具和 UI 资源(server.ts)
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
registerAppResource,
registerAppTool,
RESOURCE_MIME_TYPE,
} from '@modelcontextprotocol/ext-apps/server';
import { McpServer } from '@modelcontextprotocol/server';
import { z } from 'zod';
const DIST_DIR = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'dist'
);
export function createServer(): McpServer {
const server = new McpServer({
name: 'Quickstart MCP App Server',
version: '1.0.0',
});
// Tool + resource, tied together by the resource URI.
const resourceUri = 'ui://get-time/mcp-app.html';
registerAppTool(
server,
'get-time',
{
title: 'Get Time',
description: 'Returns the current server time.',
inputSchema: z.object({}),
_meta: { ui: { resourceUri } }, // links this tool to its UI
},
async () => {
const time = new Date().toISOString();
return { content: [{ type: 'text', text: time }] };
}
);
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(
path.join(DIST_DIR, 'mcp-app.html'),
'utf-8'
);
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
}
);
return server;
}
RESOURCE_MIME_TYPE 就是 text/html;profile=mcp-app 这个常量,用它代替手写字符串。registerAppTool 和 registerAppResource 这两个辅助函数还会替你生成各宿主需要的元数据,这也是 Claude 文档 推荐使用它们的原因。
3. 编写 View(mcp-app.html + src/mcp-app.ts)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Get Time App</title>
</head>
<body>
<p>
<strong>Server Time:</strong> <code id="server-time">Loading...</code>
</p>
<button id="get-time-btn">Get Server Time</button>
<script type="module" src="/src/mcp-app.ts"></script>
</body>
</html>
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' });
// Register handlers BEFORE connect() so you don't miss the first result.
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === 'text')?.text;
serverTimeEl.textContent = time ?? '[ERROR]';
};
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();
main.ts(在 3001 端口的 /mcp 上跑 Streamable HTTP,或用 --stdio 走 stdio)、vite.config.ts 和 package.json 里的脚本,从 quickstart 目录 复制即可。React 用户可以从 @modelcontextprotocol/ext-apps/react 拿到 useApp 和 useHostStyles。
4. 测试
- basic-host:克隆
ext-apps,运行npm install && npm start,打开http://localhost:8080。这个参考宿主默认连接http://localhost:3001/mcp。 - Claude Desktop:在 Settings → Developer → Edit Config 中添加 stdio 配置。想先试试官方发布的示例,可以这样写:
{
"mcpServers": {
"map": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-map", "--stdio"]
}
}
}
- ChatGPT:只能连接远程 HTTPS 服务器。先部署服务器或开隧道,再把它添加为自定义 MCP 服务器。详见 ChatGPT MCP 配置指南 和 部署 MCP 服务器。
- 调试:在排查渲染问题之前,先用 MCP Inspector 确认工具的
_meta和资源都能正确返回。
在用 AI 编程助手?仓库自带 Agent Skills(create-mcp-app、add-app-to-server、migrate-oai-app、convert-web-app)。在 Claude Code 中先运行 /plugin marketplace add modelcontextprotocol/ext-apps,再运行 /plugin install mcp-apps@mcp-apps 即可安装。
MCP Apps、OpenAI Apps SDK 与 MCP-UI 对比
三者出自同一条技术脉络。发布公告明确写道,MCP Apps 建立在 MCP-UI 和 OpenAI Apps SDK 的基础之上。
| MCP-UI | OpenAI Apps SDK(ChatGPT) | MCP Apps | |
|---|---|---|---|
| 是什么 | 率先在 MCP 上实现 UI 的社区项目 | OpenAI 为 ChatGPT 构建应用的方式,现归入 Plugins 文档 | 官方、厂商中立的 MCP 扩展 |
| 当前状态 | @mcp-ui/server 和 @mcp-ui/client 现已实现 MCP Apps 标准 | ChatGPT 实现了 MCP Apps,window.openai 额外提供 ChatGPT 专属能力 | 稳定版规范 2026-01-26 |
| 工具与 UI 的关联 | 内嵌 UI 资源 | _meta["openai/outputTemplate"](作为别名保留) | _meta.ui.resourceUri |
| 运行环境 | MCP-UI 宿主,以及 MCP Apps 宿主 | ChatGPT | Claude、ChatGPT、VS Code、Goose 等 |
落到实际开发上:
- 新项目:直接用 MCP Apps(
ui.resourceUri、text/html;profile=mcp-app、App类)。OpenAI 的 文档 要求开发者使用通用名称,只有规范没覆盖的能力才用window.openai,包括requestCheckout、文件上传与选择、requestModal和widgetState。请用特性检测判断它们是否可用,不要按宿主名称写分支。 - 已有的 ChatGPT 应用:旧的
window.openai.toolInput、toolOutput、callTool和sendFollowUpMessage仍作为别名可用。migrate-oai-app技能和 迁移指南 会把它们映射到ontoolinput、ontoolresult、callServerTool和ui/message。 - 自己做宿主:MCP-UI 的客户端 SDK(
AppRenderer)是一个完整的 MCP Apps 渲染器;也可以用@modelcontextprotocol/ext-apps/app-bridge自行嵌入 View。
常见问题排查
- 只显示文本、没有 UI:检查工具是否带有
_meta.ui.resourceUri,资源注册时 MIME 是否为text/html;profile=mcp-app,以及宿主是否支持 MCP Apps。 - iframe 空白或请求失败:几乎都是 CSP 的问题。打包产物加载或调用的每个来源都必须写进
resourceDomains或connectDomains,开发环境的 localhost 也不例外。漏掉的来源会静默失败,浏览器控制台里能看到违规记录。 - 第一次的结果没显示:要在
app.connect()之前 设置ontoolresult和ontoolinput。 - 部署后 UI 还是旧的:OpenAI 文档建议把资源 URI 当作缓存键。做了破坏性改动时,发布新的 URI,并更新引用它的工具。
- Claude 拒绝
ui.domain:Claude 要求的值是服务器 URL 的 SHA-256 前 32 位十六进制字符,再加上.claudemcpcontent.com,具体见 Claude 文档。
下一步
MCP Apps 让工具结果变成用户可以直接点击操作的界面,但背后的工具本身仍然得好用。浏览 MCP 服务器目录,看看现有服务器是怎么设计工具的;也可以从 最佳 MCP 服务器 中找灵感。等你的 App 上线后,把它提交到 mcp.so,让更多人发现它。
常见问题
什么是 MCP Apps?
- MCP Apps 是 Model Context Protocol 的官方扩展(SEP-1865),让 MCP 服务器可以交付交互式 UI。工具指向一个 ui:// HTML 资源,宿主在沙箱 iframe 中渲染它,页面通过 JSON-RPC 与宿主通信。
MCP Apps 规范已经稳定了吗?
- 是的。MCP Apps 规范以 2026-01-26 版本进入稳定状态,并于 2026 年 1 月 26 日作为第一个官方 MCP 扩展发布。draft 版本仍在 modelcontextprotocol/ext-apps 仓库中持续演进。
哪些客户端支持 MCP Apps?
- Claude(网页版和桌面版)、ChatGPT、带 GitHub Copilot 的 VS Code 以及 Goose 都支持 MCP Apps,Postman、MCPJam 等工具也能渲染。全屏、画中画等具体功能的支持程度仍因宿主而异。
MCP Apps 和 OpenAI Apps SDK 有什么区别?
- OpenAI Apps SDK 是 ChatGPT 自有的应用 UI 方案,MCP Apps 则是部分基于它制定的开放标准。如今 ChatGPT 已实现 MCP Apps,保留 window.openai 名称作为别名,并额外提供结账、文件上传等 ChatGPT 专属能力。