MCP Apps:交互式UI在AI对话中直接运行
MCP Apps如何改变AI智能体UX——从沙箱iframe与JSON-RPC双向通信架构到实战实现代码,从Engineering Manager视角全面解析。
2026年1月26日,Model Context Protocol团队悄然发布了一项正在改变AI智能体UX范式的功能:MCP Apps。AI不再只能返回文字回答,而是可以在AI对话窗口内直接运行交互式仪表盘、表单和数据可视化组件。
从Engineering Manager的角度用一句话说明其重要性:过去AI智能体”展示数据”时,用户需要阅读文字再手动操作工具。MCP Apps消除了这一鸿沟。
什么是MCP Apps
MCP Apps是MCP(Model Context Protocol)的第一个官方扩展(extension)——一个让工具调用(tool call)响应中可以返回交互式HTML UI的协议。
传统MCP工具返回文本、图像和结构化数据。使用MCP Apps,同样的工具调用可以返回:
- 可点击的地区销售地图
- 实时更新的系统监控仪表盘
- 一目了然展示所有选项的部署配置表单
- PDF查看器、3D模型查看器或乐谱渲染器
而且这个UI在对话窗口内部、在对话上下文中运行。
为什么不只是发送网页链接
你可能会想,“发个链接不就行了?” MCP Apps与独立Web应用根本不同的原因有四点。
1. 上下文保持
UI存在于对话之中。用户不需要切换标签页,也不需要回想哪个聊天线程里有那个仪表盘。UI自然地融入对话流程中。
2. 双向数据流
MCP App可以调用MCP服务器上的任何工具,宿主(host)也可以将新结果推送到应用。独立Web应用需要自己的API、认证和状态管理,而MCP Apps直接利用现有的MCP模式。
3. 宿主能力集成
应用可以将操作委托给宿主。当应用向宿主发送”将此会议添加到日程”时,宿主通过用户已连接的日历集成来处理。应用不需要自己实现所有外部集成。
4. 安全保障
MCP Apps在沙箱iframe中运行。它们无法访问父页面、窃取cookie或逃出容器。宿主可以安全渲染第三方应用,而无需完全信任服务器开发者。
工作原理:架构详解
MCP Apps结合了两个MCP原语:声明UI资源的工具(tool),以及将数据渲染为交互式HTML界面的UI资源。
sequenceDiagram
participant 用户
participant 智能体/宿主
participant App as MCP App iframe
participant Server as MCP服务器
用户->>智能体/宿主: "显示销售分析"
Note over 用户,App: 对话中渲染交互式应用
智能体/宿主->>Server: tools/call (show_analytics)
Server-->>智能体/宿主: 工具结果 + UI资源引用
智能体/宿主-->>App: 将工具结果推送到应用
用户->>App: 点击地区,调整过滤器
App->>智能体/宿主: tools/call请求 (通过postMessage)
智能体/宿主->>Server: 转发工具调用
Server-->>智能体/宿主: 最新数据
智能体/宿主-->>App: 数据更新
Note over 用户,App: 应用以新数据更新
App-->>智能体/宿主: 上下文更新
逐步工作流程
第1步:UI预加载
工具描述(tool description)中包含指向 ui:// 资源的 _meta.ui.resourceUri 字段。宿主可以在工具被调用前预加载此资源,从而实现流式工具输入等功能。
第2步:资源获取
宿主从服务器获取UI资源。该资源是一个HTML页面,通常捆绑了JavaScript和CSS。
第3步:沙箱渲染
宿主在对话中以沙箱iframe渲染HTML。沙箱限制应用访问父页面。
第4步:双向通信
应用与宿主通过带有 ui/ 方法名前缀的JSON-RPC协议通信。应用可以发起工具调用请求、发送消息、更新模型上下文以及接收宿主数据。
实战实现:构建MCP App服务器
让我们实际构建一个MCP App服务器。以下是一个简单的销售分析仪表盘示例。
1. 安装依赖
npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps express
2. 带UI声明的MCP服务器
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "analytics-dashboard",
version: "1.0.0",
});
// 声明UI资源的工具定义
server.tool(
"show_sales_dashboard",
"以交互式仪表盘展示各地区销售数据",
{
region: {
type: "string",
description: "要分析的地区 (all, kr, jp, us, cn)",
default: "all",
},
period: {
type: "string",
description: "分析周期 (7d, 30d, 90d)",
default: "30d",
},
},
// _meta.ui: MCP Apps的核心 — 声明UI资源引用
{
_meta: {
ui: {
resourceUri: "ui://analytics-dashboard/sales",
},
},
},
async ({ region, period }) => {
// 获取实际数据
const salesData = await fetchSalesData(region, period);
return {
content: [
{
type: "text",
text: `已加载${region}地区近${period}销售数据。`,
},
{
type: "resource",
resource: {
uri: "ui://analytics-dashboard/sales",
mimeType: "text/html",
},
},
],
// 向UI应用传递初始数据
_meta: {
ui: {
resourceUri: "ui://analytics-dashboard/sales",
initialData: salesData,
},
},
};
}
);
// UI资源处理器
server.resource("ui://analytics-dashboard/sales", async () => {
const htmlContent = generateDashboardHTML();
return {
contents: [
{
uri: "ui://analytics-dashboard/sales",
mimeType: "text/html",
text: htmlContent,
},
],
};
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
3. MCP App UI实现(React示例)
// dashboard-app/src/App.tsx
import { useEffect, useState } from "react";
import { App as McpApp, useToolCall, useHostData } from "@modelcontextprotocol/ext-apps";
interface SalesData {
regions: { name: string; revenue: number; growth: number }[];
total: number;
period: string;
}
function SalesDashboard() {
const [data, setData] = useState<SalesData | null>(null);
const [selectedRegion, setSelectedRegion] = useState<string>("all");
// 从宿主接收初始数据
const hostData = useHostData<SalesData>();
// 工具调用hook — 用户交互时向服务器请求新数据
const { call: fetchRegionData, loading } = useToolCall("show_sales_dashboard");
useEffect(() => {
if (hostData) {
setData(hostData);
}
}, [hostData]);
const handleRegionClick = async (region: string) => {
setSelectedRegion(region);
// 直接从UI调用MCP工具 — 无需额外LLM轮次!
const result = await fetchRegionData({ region, period: "30d" });
if (result?.data) {
setData(result.data as SalesData);
}
};
if (!data) return <div className="loading">数据加载中...</div>;
return (
<div className="dashboard">
<h2>销售现状仪表盘</h2>
<div className="region-filters">
{["all", "kr", "jp", "us", "cn"].map((region) => (
<button
key={region}
className={selectedRegion === region ? "active" : ""}
onClick={() => handleRegionClick(region)}
disabled={loading}
>
{region.toUpperCase()}
</button>
))}
</div>
<div className="chart-area">
{data.regions.map((r) => (
<div key={r.name} className="region-bar">
<span className="label">{r.name}</span>
<div
className="bar"
style={{ width: `${(r.revenue / data.total) * 100}%` }}
/>
<span className="value">
¥{r.revenue.toLocaleString()}
<span className={r.growth > 0 ? "up" : "down"}>
{r.growth > 0 ? "▲" : "▼"}{Math.abs(r.growth)}%
</span>
</span>
</div>
))}
</div>
<div className="summary">
合计:¥{data.total.toLocaleString()} | 周期:{data.period}
</div>
</div>
);
}
// 用McpApp包装以启用与宿主的通信
export default function App() {
return (
<McpApp>
<SalesDashboard />
</McpApp>
);
}
4. 安全配置(CSP与权限)
// 在工具声明中明确安全策略
{
_meta: {
ui: {
resourceUri: "ui://analytics-dashboard/sales",
permissions: [], // 无额外权限(仅基本沙箱)
csp: {
// 明确列出允许的外部资源域名
"script-src": ["'self'", "https://cdn.jsdelivr.net"],
"connect-src": ["'self'", "https://api.yourcompany.com"],
"style-src": ["'self'", "'unsafe-inline'"],
},
},
},
}
当前客户端支持情况
截至2026年3月,支持MCP Apps的客户端如下:
| 客户端 | 支持状态 | 备注 |
|---|---|---|
| Claude (claude.ai) | ✅ 已支持 | Web + Desktop |
| Claude Desktop | ✅ 已支持 | v3.5+ |
| VS Code Copilot | ✅ 已支持 | Insiders → Stable |
| Goose (Block) | ✅ 已支持 | |
| Postman | ✅ 已支持 | 可用于API测试 |
| MCPJam | ✅ 已支持 | |
| ChatGPT | ⏳ 未知 | 无官方公告 |
| Cursor | ⏳ 未知 | 路线图讨论中 |
在VS Code中,可通过 /mcp 聊天命令启用/禁用服务器并管理OAuth认证。
实务应用:Engineering Manager视角
以下是EM在引入MCP Apps时需要判断的关键点。
MCP Apps适合的场景
频繁的复杂数据探索。如果团队成员问AI”汇总本月故障情况”后,还要阅读文字再打开仪表盘确认——用MCP Apps将仪表盘内嵌到对话中即可。
多步骤配置/审批工作流也很适合。基础设施部署配置、成本审批、代码审查分类等工作,一次展示所有选项的表单远比逐一询问的对话方式高效得多。
实时监控也是优势所在。在聊天中提问,实时指标仪表盘随即出现的体验,与传统方式有着根本性的不同。
引入时的注意事项
Bundle大小管理:UI资源在对话中加载,初始加载性能至关重要。建议使用Preact或vanilla JS而非完整的React bundle。
CSP(内容安全策略)配置:必须明确声明外部脚本和API端点。需与安全团队协商维护允许域名列表。
Fallback设计:始终为不支持MCP Apps的客户端设计有用的文字响应作为回退方案。
用户同意流程:当UI发起工具调用时,宿主会请求用户同意。需将此UX设计得自然流畅。
客户端实现(构建自定义宿主时)
正在构建自己AI客户端的团队有两种选择:
# 选项1: @mcp-ui/client 包(提供React组件)
npm install @mcp-ui/client
# 选项2: 直接实现App Bridge
# 利用SDK的App Bridge模块:
# - 沙箱iframe渲染
# - 消息传递
# - 工具调用代理
# - 安全策略执行
用例展示
查看官方仓库中的示例,可以直观感受其可能性范围:
- map-server:CesiumJS地球仪 — “展示亚洲物流状况” → 3D地球在对话中出现
- cohort-heatmap-server:队列热力图 — 用户留存率分析仪表盘
- pdf-server:PDF查看器 — 在对话中直接审阅合同
- system-monitor-server:实时系统指标监控
- scenario-modeler-server:业务场景建模工具
- budget-allocator-server:预算分配模拟器
所有示例均提供React、Vue、Svelte、Preact、Solid和vanilla JavaScript版本。
结论
MCP Apps解决了AI智能体界面的根本性局限。原本只能通过文字交流的AI,现在可以在对话中直接运行实时UI。
从Engineering Manager的视角来看,这项技术的价值非常明确:团队成员向AI提问,在对话中直接获得交互工具并完成工作——无需切换到单独的仪表盘标签页或其他工具。
现在不必为所有MCP服务器都添加UI。但不妨从团队最常用的一个工具入手,试用MCP Apps。这一经验将改变你未来设计AI工作流的思路。
参考资料
阅读其他语言版本
- 🇰🇷 한국어
- 🇯🇵 日本語
- 🇺🇸 English
- 🇨🇳 中文(当前页面)
这篇文章有帮助吗?
您的支持能帮助我创作更好的内容。请我喝杯咖啡吧!☕