还记得前段时间引起“SaaS末日”惊呼的 Claude Cowork 专家插件(Plugins)系统吗?其背后的逻辑是 — 当 AI 助手可以通过插件接入各类企业应用,自动执行复杂任务,并在聊天框中生成交互式界面时,传统 SaaS 厚重的界面形态便显得可有可无。而其中支撑“在对话框中运行交互式 UI 应用”的关键技术,已于上个月正式纳入 MCP 扩展规范,即 MCP Apps。这一由 OpenAI 与 Anthropic 等推动的开放标准,让传统对话式 AI 助手从“命令行”迈向“图形界面”时代。
- 认识 MCP Apps:价值、概念、场景、与 Web Apps 的区别
- 深入 MCP Apps:Server + Host + View 的协同运作机制
- 体验 MCP Apps:从零构建你的第一个 MCP App
-
MCP Apps 与 A2UI:设计思路、能力边界与适用选择
MCP 是为 LLM 对接外部数据和工具而设计的开放协议。MCP 扩展(Extensions)是在核心协议基础上增加的扩展功能规范,MCP Apps 就是 MCP 的官方扩展标准之一。尽管基础的 MCP 协议打通了外部的“数据管道”,但在很多场景下,它还需要更友好、灵活的呈现与交互方式。假设在你的 AI 助手客户端中新增一个功能:能够连接企业的 CRM 系统,统计上个季度的销售与订单数据。
开发一些 MCP Tool,调用 CRM API 查询数据,并将工具注册给 AI 助手。于是,LLM 可以借助这些工具获得数据,并按要求以合适的方式展示结果;用户通过反复的多轮对话来获得信息。
- 单纯的 Markdown 表格或冗长文本,表现力十分有限
- 展示实时变化的“监控面板”或非文本媒体数据较为麻烦
- 探索性交互(如排序、点击查看详情)需要来回反复对话
- 多轮对话会造成上下文膨胀、污染与 token 消耗
这个问题就像:我们给电脑插上了连接外部资源的”管道“(通过 MCP 这个 USB口),但却缺乏一个”可互动的高清显示器“来展示数据。MCP Apps 正是用 UI 交互来弥合这道鸿沟:MCP Apps 允许 MCP Server 不再仅仅返回文本、代码或结构化数据,而是直接返回交互式的 HTML 前端界面。
比如:当用户要求“分析本季度销售数据”时,AI 不再回复一堆数字,而是在对话框中直接渲染出一个带有筛选器、折线图和钻取功能的交互式仪表盘(嵌入在iframe 中)。用户可以直接浏览、操作、点击;数据也可以根据需要动态更新;甚至可以直接查看PDF等非文本内容。这种自然顺畅的体验,使 AI 真正具备了替代传统 UI 软件的潜力。
- MCP Server 中的工具可以声明关联的交互式 UI 资源
- 返回的交互式 UI 可内嵌渲染在客户端应用(如AI 对话)中
或者说,用户仿佛在聊天里直接收到了一个可以操作的“小程序”。哪些场景适合用 MCP Apps 来增强 AI 助手的体验呢?
- 数据探索与可视化:如上文所述 — 销售分析工具返回一个交互式大屏。用户可以直接在 AI 生成的界面中按地区过滤、向下钻取客户详情,并导出报告,全程无需离开对话窗口。
- 配置向导与多步表单:很多业务场景需要分步骤填写信息,比如创建项目、发起审批等。在纯文本对话中,用户往往需要反复交互;而 MCP Apps 可以呈现具备交互逻辑的表单,简化流程。
- 富文本/文档的审查:例如 AI 合同分析工具可以在聊天框内直接展示高亮争议条款的 PDF 原文件。你可以点击界面上的“批准”或“标记”按钮,AI 模型会实时感知这些操作。
- 实时状态监控:在需要持续关注变化的场景中(如任务进度、数据变化、流程状态等),MCP Apps 可以让界面在后台持续更新数据,实时刷新,而无需用户频繁对话刷新。
可以预见,未来会有越来越多的新型“MCP 应用”出现,让 AI 对话框逐渐演变为承载各种功能的小型操作系统。MCP Apps 与 普通 Web 应用 的本质区别既然都是基于 HTML / JS / CSS 的 UI 应用,那么 MCP Apps 与传统 Web 应用到底有什么不同?是什么逻辑让传统 SaaS 界面显得“不再重要”?区别不在于实现技术,而是从“应用优先“到”任务优先“的模式变革。传统企业信息系统(CRM / MES / WMS / SRM 等)遵循典型的 “应用优先” 模式。业务人员可能需要在多个系统来回切换以完成一件事:
系统割裂了流程,上下文也被拆散在各个界面里。即使在这些传统应用中引入 AI,AI 也只是被调用者,不掌握完整的任务上下文。而 AI + MCP Apps 则更接近一种 “任务优先”的工作模式。这种模式下,对话界面成为统一的工作入口,用户不再需要记住“这件事该去哪个系统处理”,而是直接围绕任务与 AI 对话:AI 会根据任务需要,自动规划工作步骤,调用工具,进而唤起 MCP Apps 的 UI 组件:订单详情、生产进度看板、库存明细等。此时用户看到的是围绕任务的一组 UI,而不是彼此割裂的多个系统入口。因此,从传统多个 Web Apps 到嵌入 AI 的 MCP Apps,是一种从“人找系统 / UI”到“系统围绕任务主动呈现合适 UI”的转变。此外,严格的沙盒隔离(iframe)、明确的生命周期管理等机制,也是 MCP Apps 区别于传统 Web 应用的重要特征。Server + Host + View 的协同运作机制现在我们去繁从简,尽量简洁的说明 MCP Apps 的运作原理。- 也就是MCP Server,除了注册工具,现在还需要注册 UI 资源。这些 UI 资源就是打包好的 HTML 页面,描述了工具的“UI”。
- 也就是 AI 客户端应用。它负责连接 MCP Server、发现工具、在 iframe 中渲染 UI,并充当 UI View 和 Server 之间的"中间人"。
- 运行在 Host 的 iframe 沙箱中的可交互 UI “小应用”。它接收工具数据,展示界面,响应用户操作。
当 Host 连上一个 MCP Server 时,它会问:"你有哪些工具?" Server 回复工具列表,但此时有的工具身上多了一个 ui:// 开头的资源地址的标签。这个标签就是元数据中的 _meta.ui.resourceUri,它告诉 Host:"我不仅返回数据,还有一套专属 UI,可以去这个地址拿 HTML。"由于 UI 模板是在工具注册时就声明好的,Host 甚至可以提前缓存 — 还没调用工具,HTML 就已经准备好了。第二幕:初始化 — iframe 中的握手(初始化)当 Host 调用了一个带 UI 的 MCP 工具,会做三件事:
- 把拿到的 HTML 渲染注入一个 iframe,这就是View
- 创建一个 AppBridge 对象 — View 和 Server 之间的通信桥梁
iframe 是一个安全隔离的沙箱环境,所以 View 不能随意访问 Host 页面的 DOM,也不能直接发起 Server 请求。所有通信必须通过 Host 进行。通过握手,View 可以获得 Host 的主题色、容器尺寸、支持哪些能力等。这样 View 就能适配不同的 Host 环境 — 比如一个天气 UI,在浅色主题和深色主题的不同的 Host 应用里,都能完美显示。握手完成后,Host 就可以向 View “投喂” 数据 — 通常是工具的返回数据,View 在收到数据后,就可以“填充”到界面上。
- content会进入 AI 模型上下文的精简内容,比如"北京,25°C,晴"。
- structuredContent用来给 UI 用的富数据,比如用来提供查询切换的城市列表等。
这是因为 AI 的上下文窗口是宝贵的。没必要把所有的 View 需要的数据都塞进上下文,但 UI 又确实需要这些数据来渲染,做更细节的展示或互动。到这里,用户已经看到了一个嵌入在 iframe 内的 UI 界面。但 MCP Apps 的真正魅力在于:View 不是静态的,它可以反过来和 Server对话。比如,用户点击 UI 上的一个“刷新”按钮,会发生什么?这个过程中 AppBridge 扮演的角色至关重要(所以这里与 Host 分离展示)。它就像一个安全中转站:View 想调用工具与读取数据必须经过我。为什么不让 View 直接和 Server 通信?
由于 iframe 里运行的是 MCP Server 的第三方代码。如果让它随意访问网络,安全隐患极大,而通过 AppBridge 代理可以控制 View 的“活动范围”。当然,除了调用工具,View 还可以做更多事。比如:
- 向对话发送消息 — 比如用户在 UI 上做了个选择,View 可以把用户的选择发回聊天界面,AI 就知道了用户的意图。
- 更新模型上下文 — View 可以告诉 AI 当前 UI 的状态,比如"用户正在查看上海天气",AI 在后续回答中就可以参考这个上下文。
所以,这不是单向的"数据展示",而是 UI 和 AI 的双向对话。当用户关闭 UI 或者切换其他工具时,Host 会先发一个"退场通知":核心通信机制:postMessage + JSON-RPC最后来说说贯穿始终的跨 iframe 的通信机制,即 AppBridge 的底层原理 — 它依赖于浏览器提供的一个原生 API — postMessage。
- 绝对安全隔离:浏览器提供的跨 iframe 通信标准,iframe 里的代码只能通过 postMessage 对外传话。
- 基于事件监听:一方通过 window.postMessage() 喊话,另一方通过监听 message 事件来收听。
MCP Apps 在此之上做了一个有趣的设计:消息格式直接采用 JSON-RPC 2.0,也就是 MCP 基础协议本身的消息格式。这种方式下,View 本质上就类似一个 MCP 客户端,只不过它不通过 HTTP,而是通过 postMessage。即 AppBridge 在中间做了一次“协议转换” — 从 postMessage 翻译成 HTTP,再发给真正的 MCP Server:这个设计的好处在于:在 MCP App 中调用 MCP 工具的方式几乎与一个命令行的 MCP 客户端完全一致 — 无额外的学习成本。可以看到,MCP Apps 并不只是简单的“在聊天里嵌一个 HTML 页面”。它是一套完整的运行时框架 — 定义了发现、初始化、数据传递、双向交互、安全隔离、优雅退出的全生命周期。让 MCP 工具从“返回单调的文本”进化为按需“呈现一个可交互的应用”,并同时保持了 MCP 协议的开放和标准化。拆解了 MCP Apps 的原理后,让我们来动手做一个 MCP App,切身体验下它的强大之处 — 一个可以查询天气,并可以切换城市的天气预报MCP App。官方为主流 AI 编程 Agent 准备了几个技能(Skill):
- 将已有的 OpenAI Apps SDK 项目迁移到 MCP Apps SDK。
- 用来给已有的 MCP Server 增加交互式的 UI。
- 将普通 Web App 转化成 MCP App,让其可以在 MCP Host 应用中呈现。
npx skills add modelcontextprotocol/ext-apps
跟随出现的引导界面,选择技能、Code Agent等,即可完成安装:![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
现在,你只需要用自然语言描述你想要创建的 MCP App。比如:"创建一个MCP App,用来展示模拟的天气预报,并支持城市切换”AI 会自动识别 create-mcp-app 技能,加载其中的架构指导和最佳实践,然后帮你生成完整的初始项目。接下来我们把 AI 帮你做的事情,一步步拆开来看详细步骤。
- TypeScript 5+(MCP Apps SDK 全程 TypeScript)
- 对 MCP 协议与开发有基本了解(可翻阅我们的文章)
官方在@modelcontextprotocol/ext-apps提供了一组分工明确的 SDK,不同子模块分别对应 View、Host 和 Server 不同角色的开发:@modelcontextprotocol/ext-apps
基础 SDK,供 View 端开发使用(运行在 iframe 内)。
@modelcontextprotocol/ext-apps/app-bridge
MCP Host 端 SDK,用于开发 Host 应用与 View 通信。
@modelcontextprotocol/ext-apps/server
MCP Server 端 SDK,用于注册带 UI 的工具。
@modelcontextprotocol/ext-apps/react(可选)
基础 SDK 的 React Hooks 封装;如果选择React 开发 view,可以用它。
对于MCP Server 开发来说,重点关注 View 端与 Server 端。参考官方 basic-server-vanillajs(或 react/vue/svelte 等变体)脚手架,初始化项目。核心结构如下:关键依赖包括:@modelcontextprotocol/ext-apps、@modelcontextprotocol/sdk、vite + vite-plugin-singlefile(用来把UI 部分打包成一个自包含的 HTML 文件)。后端的核心工作是两件事:注册一个工具 + 注册一个对应的 UI 资源。SDK 提供了 registerAppTool 和 registerAppResource 两个辅助函数,用来注册 MCP 工具与 UI 资源,工具通过_meta.ui.resourceUri来声明“这是一个带有 UI 的工具”:这里工具的实现就是标准的 MCP 工具逻辑 — 接收 city 参数,返回模拟天气数据(JSON 字符串)与可切换城市列表:registerAppTool(
server,
"get-weather",
{
title: ...
description: ...
inputSchema: ...
_meta: { ui: { resourceUri: weatherResourceUri } },
},
async ({ city }) => {.........模拟返回天气数据.......
},
);
这里的weatherResourceUri为自己定义的 UI 资源(html)。比如:const weatherResourceUri = "ui://get-weather/weather-app.html"registerAppResource(
server,
...
async () => {
const html = await fs.readFile(path.join(DIST_DIR, "weather-app.html"), "utf-8");
return {
contents: [
{ uri: weatherResourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
},
);
MCP Server部分的开发就这么多,只是在原来MCP Tool开发的基础上,多声明与实现一个 UI 资源(html),并链接到 Tool 上。现在需要完成上一步中注册的 weather-app.html + weather-app.ts,这部分最终会运行在 Host 应用的 iframe 内,也就是 View 的部分。其中HTML负责“外观”,TS 负责“逻辑” 。核心的实现逻辑(weather-app.ts)包括:import { App } from"@modelcontextprotocol/ext-apps";
...
const app = new App({ name: "Weather App", version: "1.0.0" });
// 注册工具结果达到:重新渲染 UI
app.ontoolresult = (result) => {
const data = parseResult(result);
if (data) {
currentCity = data.city;
renderWeather(data);
}
};
// 点击UI上的刷新按钮,更新天气数据
refreshBtn.addEventListener("click", () => {
fetchWeather(currentCity);
});
// 与Host连接、初始化、握手等
app.connect();
app.ontoolresult 回调必须在 app.connect() 之前设置,否则 Host 推送的初始工具结果会在你注册监听器之前到达,导致首屏数据丢失。代码中fetchWeather函数中会调用 app.callServerTool({ name: "get-weather", arguments: { city } }) — 这个请求会经由 AppBridge 代理,透传给后端 MCP Server(回顾上一节的 iframe 通信原理)。注意 weather-app.html 与 weather-app.ts 会打包成自包含的html文件,这就是通过 MCP 工具调用时返回给 Host 的 resource 内容。如果一切正常,服务端会默认监听 localhost:3001/mcp。AI 客户端使用官方提供的一个现成 Host 程序 — basic-host 来测试:
SERVERS='["http://localhost:3001/mcp"]' npm run dev
成功后打开 http://localhost:8080,就可以看到这个界面:选择调用get-weather这个工具,将会在页面上方出现一个iframe区域:这就是MCP Apps的呈现效果!我们不仅通过 get-weather 这个 tool 获得了数据,还同时获得了一个可以呈现与互动的UI界面。点击上方的城市切换到“Shanghai”,来测试互动效果:可以看到,这个 View 成功再次调用了 MCP 工具更新了数据(再次强调,是通过 Host 来代理调用)。当然,在实际开发中,你可能需要开发自己的 Host 应用(AI 对话客户端),标准做法有两种:
- 一种是使用上面提到的 SDK中的 AppBridge 模块,用来在沙盒iframe渲染UI、消息传递、工具调用等。具体参考官方的例子 basic-host。
- 另一种是使用官方独立的 mcp-ui React组件库,具体参考:
https://github.com/MCP-UI-Org/mcp-ui。
以上我们演示了一个完整的 MCP App 的开发过程与细节。借助于 SDK,很多细节都被屏蔽在幕后(比如 iframe 生命周期、postMessage 通信、View 的握手等),这可以让开发者更加专注于业务本身。在官方的SDK中,有很多现成的examples完整代码,可以参考学习。谷歌不久前也发布了一个类似的标准 — A2UI(Agent-to-User Interface)。它的目标是让 AI Agent 可以直接生成可跨端渲染的结构化用户界面。简单来说,A2UI 让 AI 用 JSON 这种声明式格式来“描述”UI 的结构,由客户端根据这份描述去绘制界面。例如,AI 输出一段 JSON 表示“创建一个柱状图组件,数据如下……”,客户端便使用原生 UI 框架(Flutter、React 等)将图表渲染出来(前提是具备相应的渲染器)。那么,相比 A2UI,MCP Apps 有哪些不同?可以从几个角度来看:A2UI 的 UI 由 AI 动态生成 JSON 来描述,属于“即时构建 UI”;而 MCP Apps 的 UI 则由开发者预先实现为完整网页。
换句话说,在 A2UI 中,AI 更像“设计师”,实时构建界面;而在 MCP Apps 中,开发者提供现成组件,AI 按需调用。
-
MCP Apps 通过 iframe 几乎可以嵌入任意复杂的前端应用,支持高度自由的交互与渲染;A2UI 生成的 UI 则通常受限于预定义的白名单组件。
不过,正因为组件受限,A2UI 的安全边界相对更清晰;而 MCP Apps 虽然有沙盒 iframe 机制,但仍需对第三方前端代码进行一定程度的信任。
-
A2UI 的最终渲染由各端原生框架完成;MCP Apps 则更偏向在浏览器环境中工作,通过 iframe 在 Web 界面中渲染。
如果你的 AI 应用需要运行在多种终端类型上,并希望 UI 风格与系统原生体验保持一致,A2UI 可能更合适。MCP Apps 目前主要应用于 Web 场景,未来也可能扩展到更多终端。
-
MCP Apps 得到 Anthropic 与 OpenAI 的支持,并已在 Claude、ChatGPT 等主流 AI 客户端中落地。开发者社区也贡献了大量 SDK 与示例,整体门槛相对较低。
A2UI 当前主要应用于 Google 自身产品及部分开源项目中,后续生态扩展情况仍有待观察。
-
MCP Apps 更适合在对话式 Agent 应用中嵌入垂直应用开发者提供的交互能力,例如 SaaS 产品推出 MCP Apps 插件版。
而 A2UI 更像是在培养 AI 自身的前端生成能力:用户只需用自然语言描述需求,AI 即可动态生成一个可用的界面。
当然,两者并非水火不容。未来的 AI 助手可能既能调用预制的 MCP Apps 组件,也能在缺少特定应用时通过 A2UI 动态生成界面。总体而言,它们的共同目标都是让 AI 交互突破纯文本的限制,在安全可控的前提下引入图形界面能力。总的来说,业内对给“Agent 装上 GUI”的前景普遍看好。随着 OpenAI 与 Anthropic 的推动,这类交互标准有望逐步成为行业共识。可以预见,在不远的将来,“会发小程序”的 AI 将越来越常见,人机协作也会更加直观高效。而对开发者而言,一方面可以考虑将自家产品能力通过 MCP Apps 向 AI 开放;另一方面,也可以基于 MCP Apps 构建新的 AI 驱动应用,打造更自然的交互体验。