核心摘要
MCP Apps 是 Model Context Protocol 的官方可选 UI 扩展。2026-01-26 稳定规范规定:Tool 通过 _meta.ui.resourceUri 指向 ui:// Resource;支持该扩展的 Host 获取 HTML,在 Sandboxed View 中渲染,并代理 View 与 MCP Server 之间的 JSON-RPC 通道。它标准化的是交互界面,不是身份、授权、计费、分发或业务正确性。
协议边界
扩展标识符是 io.modelcontextprotocol/ui,主要增加四类契约:
- UI Resource:预声明的
ui://Resource,内容类型为text/html;profile=mcp-app。 - Tool 与 UI 关联:Tool Metadata 声明用于渲染 Result 的 Resource。
- View 与 Host 协议:Iframe 与 Host 通过
postMessage交换 JSON-RPC 消息。 - 安全 Metadata:Resource 声明 CSP Origin、请求的浏览器权限与显示偏好。
用户
|
v
Host / Agent ----- tools/call -----> MCP Server
| |
| resources/read | Tool Result
v v
Sandboxed View <---- UI 消息 -------- Host Bridge
MCP Apps 是 Extension,不是所有 Client 必须实现的核心能力。Server 与 Host 需要协商支持;Host 无法渲染 View 时,同一个 Tool 仍应提供有意义的 content 或 structuredContent。
正确声明 UI Resource
当前 Tool 声明把引用放在 _meta.ui 下:
{
"name": "orders.review",
"description": "Return a read-only review queue for orders visible to the caller.",
"inputSchema": {
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["pending", "flagged"]}
},
"required": ["status"],
"additionalProperties": false
},
"_meta": {
"ui": {
"resourceUri": "ui://orders/review-v3",
"visibility": ["model", "app"]
}
}
}
resources/read 返回的 Resource 必须使用相同 URI 和 MIME Type:
{
"contents": [{
"uri": "ui://orders/review-v3",
"mimeType": "text/html;profile=mcp-app",
"text": "<!doctype html><html>...</html>",
"_meta": {
"ui": {
"csp": {
"connectDomains": ["https://api.example.com"],
"resourceDomains": ["https://static.example.com"]
},
"permissions": {},
"prefersBorder": true
}
}
}]
}
稳定规范已弃用早期扁平的 _meta["ui/resourceUri"]。应固定 Extension Revision 和生成的 SDK Type,不要复制旧博客示例。
理解完整生命周期
生命周期包含不同阶段:
| 阶段 | 方向 | 必须处理的事项 |
|---|---|---|
| Discovery | Server 到 Host | Tool Metadata 与扩展支持 |
| Resource Fetch | Host 到 Server | 精确 URI、MIME、大小和 Cache Identity |
| Initialization | View 与 Host | ui/initialize、Capability、Origin 与 Session |
| Input / Result | Host 到 View | 有界、脱敏、版本化数据 |
| Interaction | View 经 Host 到 Server | Tool Allowlist、授权与确认 |
| Teardown | Host 与 View | 清理、取消和保留状态 |
View 不能假设某个 Tool Result、Permission、Open Link 或 Host Capability 一定存在。Feature Detection 和非 UI Fallback 都是兼容性的一部分。
分开 Model Context 与 View Data
MCP Tool Result 可以同时携带供模型使用的文本 content,以及供 UI 等消费者使用的 structuredContent。两者都要最小化:
- 模型只接收推理所需事实;
- View 只接收当前 Surface 所需字段;
- 不能把隐藏 UI 字段当作授权;
- 两条通道都不要携带凭据、内部对象 ID 或原始下游响应;
- 可能过期的数据需要 Source 与 Version Identity。
图表可能需要 50 个聚合点,而模型只需要两句摘要。把完整数据复制到两个通道,只会扩大隐私与 Context 风险。
Visibility 不是授权
扩展允许 Tool 声明对 model、app 或两者可见。App-only Tool 可以把刷新等交互能力从模型 Tool List 隐藏,并阻止该路径跨 Server 调用。这是有用的暴露控制,但不是业务授权。
每个 Server 调用仍需:
- 从可信 Transport Context 获取已认证 Subject 与 Tenant;
- 执行 Tool 和对象授权;
- 校验并规范化参数;
- 读取当前对象状态和策略;
- 把 Confirmation 绑定到确切副作用;
- 处理 Idempotency 与 Outcome Recovery;
- 限制速率、时间、结果大小和成本。
View 提交的是 Intent。它不能决定用户身份、订单价格或副作用是否允许。
可运行的服务端 Action Gate
下面的 Go 程序演示如何把确认凭证绑定到 Subject、Action、Object、Amount 与 Expiry。它不会信任 UI 自报的身份或价格:
package main
import (
"fmt"
"time"
)
type Session struct {
Subject string
Tenant string
}
type Confirmation struct {
ID string
Subject string
Tenant string
Action string
ObjectID string
Amount int
ExpiresAt time.Time
Consumed bool
}
func authorize(session Session, token Confirmation, action, objectID string, now time.Time) (bool, string) {
if token.Consumed {
return false, "confirmation_already_consumed"
}
if now.After(token.ExpiresAt) {
return false, "confirmation_expired"
}
if token.Subject != session.Subject || token.Tenant != session.Tenant {
return false, "identity_scope_mismatch"
}
if token.Action != action || token.ObjectID != objectID {
return false, "proposal_changed"
}
return true, "authorized"
}
func main() {
now := time.Date(2026, time.October, 4, 10, 0, 0, 0, time.UTC)
session := Session{Subject: "user-42", Tenant: "tenant-a"}
token := Confirmation{
ID: "confirm-7",
Subject: "user-42",
Tenant: "tenant-a",
Action: "order.approve",
ObjectID: "order-9",
Amount: 12500,
ExpiresAt: now.Add(5 * time.Minute),
}
allowed, reason := authorize(session, token, "order.approve", "order-9", now)
fmt.Printf("allowed=%t reason=%s amount=%d\n", allowed, reason, token.Amount)
}
预期输出:
allowed=true reason=authorized amount=12500
生产 Handler 必须把 Token 消费与 Idempotency Key 原子提交,并保存执行结果。如果下游在执行后超时,应返回 outcome_unknown,直到 Reconciliation 能证明副作用是否发生。
Sandbox 与 CSP:隔离很强,但不是完整信任
稳定规范要求 Host 在 Sandbox 中渲染 HTML View,并根据声明的 Origin 构造 CSP。没有 CSP Metadata 时,规范默认拒绝外部连接和嵌套 Frame,并限制其他资源类型。
这个边界可以减少 View 直接访问 Host DOM、Cookie 和 Storage 的风险,却不能证明:
- UI 文案真实且不会欺骗用户;
- 外部 Origin 因为被声明就一定安全;
- Tool Call 已获授权;
- 返回对象属于当前 Tenant;
- 操作可逆或只执行一次;
- 界面符合可访问性要求。
Host 可以进一步收紧请求的能力。App 不应假设 Camera、Microphone、Geolocation、Clipboard、Open Link 或 Tool Call 权限一定会被批准。
渐进增强与兼容性
Host 支持并不一致,应维护兼容矩阵:
MCP Core Revision
x Apps Extension Revision
x SDK Revision
x Host 与版本
x Browser Engine
x Authentication Profile
x Supported Capabilities
至少验证:
- 不支持 Apps 的 Host 能收到有用文本 Fallback;
- 未知 UI 字段和组件会被安全拒绝;
- Deprecated Metadata 不会静默改变行为;
- Resource Fetch 有大小边界并使用正确 MIME Type;
- CSP 与 Permission 不宽于声明;
- App-only Tool 不出现在模型 Tool List;
- 不支持的请求返回明确协议错误;
- Teardown 能取消订阅并释放资源。
“支持 MCP”不足以构成兼容性承诺。
可访问性与用户信任
Iframe 仍是用户界面。需要测试键盘访问、焦点进入与返回、Accessible Name、Status Announcement、Zoom、Reflow、Contrast、本地化、RTL 和 Reduced Motion。应保留产品自有导航,并清楚区分生成内容或第三方内容。
敏感操作需要由 Host 或产品控制的固定确认界面。不要允许 App 决定可信产品名称、金额、目的地、Permission Scope 或确认文案。
分发、计费与支持属于独立系统
MCP Apps 不会自动形成应用商店。Catalog 和商业层仍需处理:
- 发布者身份与所有权证据;
- 版本固定和 Artifact Provenance;
- 权限审查与凭证隔离;
- 漏洞响应和撤销;
- Install、Upgrade、Rollback、Uninstall 与删除;
- Retry、Cancellation 与重复投递的计量规则;
- 客户可查看的证据和申诉。
应衡量成功用户结果,而不是 View Render 或 Tool Call 数。界面再丰富,如果重试率高、控件不可访问或副作用不清楚,也不是成功产品。
发布清单
- [ ] 固定 Core 与 Apps Extension Revision。
- [ ] 协商支持并提供非 UI Fallback。
- [ ] 使用
_meta.ui.resourceUri和规定 MIME Type。 - [ ] 最小化模型
content与 ViewstructuredContent。 - [ ] 声明最小 CSP 与 Permission。
- [ ] 身份、对象授权、价格和所有权保留在服务端。
- [ ] Confirmation 与 Idempotency 绑定到确切副作用。
- [ ] 测试 App-only Tool Visibility 和跨 Server 拒绝。
- [ ] 测试可访问性、本地化、Teardown、Retry 与 Outcome Unknown。
- [ ] 发布 Host 兼容和回滚矩阵。
常见问题
什么是 MCP App?
它是 MCP Server 通过官方 UI Extension 交付的交互界面。支持该扩展的 Host 会渲染被引用的 ui:// HTML Resource,并代理与 View 的通信。
MCP Apps 属于 Core MCP 吗?
它是官方可选扩展。Client 与 Server 需要协商支持,因此仍需提供文本或结构化数据的渐进降级。
Iframe Sandbox 是否足以保证安全?
不够。它隔离 View 与 Host,但 CSP、Capability、身份、对象授权、确认、幂等和 UI 信任仍由 Host 与 Server 负责。
App 能调用所有已连接 Tool 吗?
不能。调用由 Host 代理,受 Tool Visibility 和 Host Policy 限制,并由 Server 独立授权。
MCP Apps 是应用商店或计费协议吗?
不是。它标准化交互 UI 的交付与通信;Catalog、安装、发布者信任、计费、支持、撤销和删除仍是独立系统。