核心摘要

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,主要增加四类契约:

  1. UI Resource:预声明的 ui:// Resource,内容类型为 text/html;profile=mcp-app。
  2. Tool 与 UI 关联:Tool Metadata 声明用于渲染 Result 的 Resource。
  3. View 与 Host 协议:Iframe 与 Host 通过 postMessage 交换 JSON-RPC 消息。
  4. 安全 Metadata:Resource 声明 CSP Origin、请求的浏览器权限与显示偏好。
text
用户
  |
  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 下:

json
{
  "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:

json
{
  "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 自报的身份或价格:

go
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)
}

预期输出:

text
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 支持并不一致,应维护兼容矩阵:

text
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 与 View structuredContent。
  • [ ] 声明最小 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、安装、发布者信任、计费、支持、撤销和删除仍是独立系统。

延伸阅读

一手来源