跳转到内容

App Protocol

面向开发者。从开发者手册选择 API 接入、App 发布或项目贡献。

OpenBot App Protocol(App 协议)是一种在网上发布 App 的开放方式。发布之后,任何 Bot 宿主都能找到这个 App,连接它,并使用它。你不需要开发者账户、注册中心或审核。有一个你自己控制的 HTTPS 地址就够了。

本页描述的是草案 V1.1。在本页中,“必须”表示规则,“应该”表示强烈建议,“可以”表示可选项。所有域名都只是示例。


这个协议自己新增的内容很少。它挑选现有的标准,并把它们组合在一起。

部分 回答的问题
AI Catalog 1.0 这里有哪些服务?
MCP Server Card v1 我该如何连接?
基于 Streamable HTTP 的 MCP 2026-07-28 这个 App 能做什么?
MCP Apps 2026-01-26 它如何显示视图?

MCP(Model Context Protocol,模型上下文协议)是 AI 宿主调用服务器上工具的标准方式。本协议没有自己的 RPC 方法、固定的工具名或界面语言。


要让别人通过域名找到你,请在 /.well-known/ai-catalog.json 放一个 AI Catalog。以 application/ai-catalog+json 类型提供它。

{
"specVersion": "1.0",
"entries": [
{
"identifier": "urn:air:apps.example.com:mcp:calendar",
"type": "application/mcp-server-card+json",
"url": "https://apps.example.com/mcp/calendar/server-card"
}
]
}

Catalog 可以指向其他域名上的 Card。这样别人就可以替你托管服务。

每个 App 都必须有自己稳定的 Card URL。以 application/mcp-server-card+json 类型提供 Card。它必须至少列出一个支持 MCP 2026-07-28 的 HTTPS 端点。

{
"$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
"name": "com.example/calendar",
"title": "Example Calendar",
"version": "1.0.0",
"description": "Look up schedules and create calendar events.",
"websiteUrl": "https://apps.example.com/apps/calendar",
"remotes": [
{
"type": "streamable-http",
"url": "https://apps.example.com/mcp/calendar",
"supportedProtocolVersions": ["2026-07-28"]
}
]
}

Card URL 就是 App 的身份。name 和 Catalog 的 identifier 只用来描述 App。两个同名的 App 永远不会被合并。

remote 可以要求提供设置,例如 headers 或 URL 的 variables。用 isSecret: true 标记机密值。宿主会为每个用户单独保密这些值。

提供 Catalog 和 Card 时,请带上 ETag 或 Last-Modified。宿主会发送条件请求,内容没变时期望得到 304 Not Modified。

使用 Card 标准的 icons 列表(src、mimeType、sizes、theme)。即使缺少图标,宿主也必须显示你的 App。宿主从不把 SVG 文件当作 HTML 运行。

你可以在 _meta["openbot/presentation"] 下添加使用示例:

{
"_meta": {
"openbot/presentation": {
"version": "1",
"examples": [
{
"id": "meeting-plan",
"title": "Plan the next team meeting",
"prompt": "Find a free 30-minute slot next week and draft an agenda.",
"description": "A sample week with one proposed meeting.",
"preview": {
"src": "https://apps.example.com/assets/meeting.webp",
"alt": "A sample week with one proposed meeting",
"width": 1200,
"height": 800,
"mimeType": "image/webp"
}
}
]
}
}
}
字段 规则
version 必须是 "1"。
examples 1 到 12 项。
id 唯一。最多 100 个字母、数字、.、_ 或 -。以字母或数字开头。
title 最多 100 个字符。
prompt 最多 2000 个字符。
description 可选。最多 500 个字符。
preview 可选。HTTPS 的 src,alt 最多 500 个字符,width 和 height 从 1 到 8192,mimeType 为 PNG、JPEG、WebP、AVIF 或 SVG 之一。

示例中只能包含公开的或编造的数据。宿主以纯文本显示它们。查看或选择一个示例,永远不会添加 App、登录或运行工具。如果展示块有错误,宿主会忽略它,继续使用 Card。


宿主必须接受四种输入:域名、App 页面 URL、Catalog URL 或 Card URL。

  • 域名。 宿主读取 https://<domain>/.well-known/ai-catalog.json。

  • App 页面。 页面用 link 标签或 HTTP Link 头指向它的 Catalog:

    <link
    rel="ai-catalog"
    type="application/ai-catalog+json"
    href="/apps/calendar/catalog.json"
    />

    宿主只读取这些静态链接。它从不运行页面脚本。它保留完整的路径,所以一个 App 的页面不会变成整个域名。

  • 含有多个 Card 的 Catalog。 宿主请用户选择。它从不添加整个 Catalog。

App Link(App 链接)是一个普通的 HTTPS URL,最好是 App 页面。二维码保存的也是同一个 URL。没有特殊的二维码格式或 URI scheme。链接中不能包含令牌或私密数据。打开链接只会开始一次审查。它不授予任何访问权限,也不运行任何东西。


添加、登录和运行是三个独立的步骤。添加会保存连接。登录会授予明确的访问权限。运行需要用户的意图、有效的登录,以及宿主自己的规则。

用户添加 App 之后,宿主会:

  1. 调用 server/discover,读取当前版本和能力;
  2. 调用 tools/list、resources/list 和 prompts/list;
  3. 用 tools/call、resources/read 和 prompts/get 完成工作。

所有消息、结果和错误都遵循 MCP。按照工具做的事情给它命名,例如 create_event。用 inputSchema 描述输入。用 outputSchema 和 structuredContent 返回结构化输出。同时也总要返回一个清楚的文本结果。

需要用户账户的 App 必须使用 MCP 授权。这就是 OAuth 2.1:

  • Protected Resource Metadata(受保护资源元数据)。 App 返回 401 或 403,并带上一个指向其元数据的质询。元数据中写明授权服务器和权限范围。
  • Client ID Metadata Document(客户端 ID 元数据文档)。 宿主的客户端 ID 是一个 HTTPS URL,指向一个公开的 JSON 文档。授权服务器读取它。不需要有人手动注册宿主。双方都必须支持这一点。
  • 使用 S256 的 PKCE。 它保护登录码。请求还会发送确切的 resource。

你的 App 仍然可以要求登录、套餐或业务权限。这不属于注册中心的步骤。


App 可以显示一个交互式视图。工具在它的元数据中链接到一个 UI 资源:

{
"_meta": {
"ui": {
"resourceUri": "ui://calendar/planner.html",
"visibility": ["model", "app"]
}
}
}

该资源必须使用 ui:// URI 和类型 text/html;profile=mcp-app。宿主用 resources/read 读取它。宿主在它的客户端能力中声明 io.modelcontextprotocol/ui。

用途 标准方法 方向
启动并告知已就绪 ui/initialize、ui/notifications/initialized 视图到宿主
获取工具的输入和结果 ui/notifications/tool-input、ui/notifications/tool-result 宿主到视图
执行工作或读取数据 tools/call、resources/read 视图经由宿主到 App
更新模型上下文 ui/update-model-context 视图到宿主
发送聊天消息 ui/message 视图到宿主
打开链接 ui/open-link 视图到宿主
共享主题和尺寸 ui/notifications/host-context-changed、ui/notifications/size-changed 双向
清理 ui/resource-teardown 宿主到视图

视图的规则:

  • 宿主要等视图就绪后,才发送工具数据。
  • 带有 visibility: ["app"] 的工具只供视图使用。可见性不是访问控制。你的服务器仍然必须检查权限。
  • 如果视图加载失败,宿主不能再次运行该工具。文本结果仍然存在。

宿主保存三样不同的东西:

东西 保存什么 是否共享?
App 记录 Card 及其来源。 每个账户一份
Index 条目 公开的 Card 和发布者证据。用于搜索。 公开
用户连接 用户的设置、登录和访问权限。 用户私有

许多用户可以连接同一个 App。删除一个连接永远不会影响其他用户或 Index。公开的 Index 宕机时,不能阻碍已知的连接。


当你在自己的公开 AI Catalog 中列出一个 Card 时,就表示你允许公开索引它。 宿主必须从你自己的文档中确认这一点。别人 Catalog 中的副本只是一条线索。

爬虫在没有用户数据的情况下工作。它使用用户代理标记 OpenBot,并遵守 robots.txt(RFC 9309)。它接受以下来源的发布者证据:

  • Card 所在源的 /.well-known/ai-catalog.json 中的 Catalog;
  • Card 响应上的 ai-catalog Link 头所指的 Catalog;
  • Card 的 websiteUrl 页面所链接的 Catalog。

要退出索引,请在 Card 上发送 X-Robots-Tag: noindex。noindex 不是访问控制。如果你从 Catalog 中删除了 Card,宿主会在下次检查时删除对应的 Index 条目。无法访问的 Catalog 不算删除。

发布、添加或调用 App 都不需要注册中心。宿主也可以读取其他注册中心。OpenBot 的做法请看 App Index。


  • 所有连接都使用 HTTPS。宿主会显示每个 App 的真实来源。
  • 名称、图标、Index 条目和工具说明都不是可信的信号。App 的内容是数据,永远不是给宿主的指令。
  • 公开爬虫会限制大小、时间和重定向。它们检查每个目标,永远不会访问私有地址、回环地址或云元数据地址。库的默认值是 1 MiB、10 秒和 5 次重定向。
  • 主机也会限制每条 MCP 消息的大小。对一个 JSON 响应或事件流中的一个事件,库的默认值是 16 MiB。
  • 对一个输入的发现在读取 64 个文档或 60 秒后停止。它从一个 Catalog 或页面最多跟随 64 个链接。一次公开索引检查最多读取 8 个发布者 Catalog,并在 60 秒后停止。库使用这些限制。
  • 写操作需要权限检查。可能被重试的写操作需要一个业务幂等键。
  • 当一次调用的结果未知时,宿主不能盲目重试。
  • 更换端点、提供方或权限范围时,需要重新审查。旧的令牌永远不会转移到新目标。
  • 删除连接会停止所有调用,并删除它的令牌。

@openbot-build/protocol 用 Effect 4 实现了这个协议。它的版本是 0.1.0,还没有发布到 npm。请在本仓库的 pnpm 工作区中使用它。

入口 用途
McpPublisher 提供 Card、Catalog 和 MCP 端点。
McpPublisher.layerProtected、Protection 用 OAuth 保护端点。
Discovery、CatalogDiscovery 从域名或链接查找 App。
AppConnections、McpClient 添加、调用、重新验证和删除连接。
OAuthAuthorization 运行账户登录的宿主端。
Indexing、PublicIndexing、RobotsPolicy 检查并索引公开的 App。
@openbot-build/protocol/NodeHttpServer 带有公网规则的 Node 网络层。
@openbot-build/protocol/McpApps MCP Apps 视图的浏览器宿主。

McpPublisher.layer 和 layerProtected 接收普通发布数据,在层启动时检查一次。 registerUi 在 Effect 中检查普通 UI 资源数据。错误输入通过有类型的错误返回。

McpClient.open 使用连接标识、端点、请求头和客户端信息打开连接。用 readToken 读取当前令牌,用 check 检查每个操作。宿主还可以提供 registerScope,在任何网络调用前登记连接。客户端不需要 App 或凭据存储,连接由其作用域持有和关闭。已有连接使用 AppConnections.open。这个适配器读取当前连接和凭据、检查访问权限,并登记作用域。读取令牌和发送时仍会检查当前状态。

打开客户端不会读取能力列表。用 client.resolve({ kind, name }, source) 查找一个工具、资源、模板或提示词。它只读取指定类别的列表,找到第一个匹配项后停止。匹配结果带有声明及其 invoke 和 resume 操作。它们使用同一份声明,不会再次读取列表。传给 invoke 的请求参数不含已绑定的名称或 URI;模板使用 { values }。返回的调用对象只在当前连接作用域内使用。恢复后的阶段会重新查找并校验声明。直接调用 readResource 也支持未列入资源列表的 UI 资源。

CatalogDiscovery.sameConnection 比较远端中会影响连接的部分。只改说明文字或扩展字段会保留连接和登录状态。更改输入格式或秘密标记需要重新检查。

OAuth 网络失败保留 NetworkError。这不表示提供方拒绝了授权。关闭连接作用域会停止未完成的 SDK 请求,包括仍在等待响应头或已存令牌的请求。

持久执行宿主使用 OAuthAuthorization.prepare、redeem、renew 和 revokeCredential。这些函数返回流程或凭据,不写入宿主的存储。显示登录 URL 前先保存流程。发送授权码交换或令牌刷新前先记录调用。加密保存返回的凭据,再与连接状态一起提交。交换结果丢失时需要确认;未经确认,不要再次发送授权码或刷新令牌。撤销使用固定凭据,可以重复执行。独立使用的 begin、complete、refresh 和 revoke 也会更新所提供的存储,但不提供跨进程重启恢复。

发布一个小 App:

import { Document, McpPublisher } from "@openbot-build/protocol"
import { Effect, Schema } from "effect"
import { McpServer, Tool, Toolkit } from "effect/ai"
const tools = Toolkit.make(
Tool.make("hello", {
parameters: Schema.Struct({ name: Schema.String }),
success: Schema.Struct({ greeting: Schema.String }),
}),
)
const handlers = tools.toLayer({
hello: ({ name }) => Effect.succeed({ greeting: `Hello, ${name}` }),
})
const origin = "https://apps.example.com"
export const publisher = McpPublisher.layer({
publication: {
card: {
$schema: Document.cardSchemaUrl,
name: "com.example/hello",
version: "1.0.0",
description: "Say hello.",
remotes: [
{
type: "streamable-http",
url: origin + "/mcp",
supportedProtocolVersions: [Document.protocolVersion],
},
],
},
cardUrl: origin + "/mcp/server-card",
catalogUrl: origin + "/.well-known/ai-catalog.json",
mcpUrl: origin + "/mcp",
allowedOrigins: ["https://host.example.com"],
},
register: McpServer.registerToolkit(tools).pipe(Effect.provide(handlers)),
})

用 Effect 的 HttpRouter.serve 提供 publisher。构建这个 layer 不做任何 I/O。publication 还接受 etag(带引号的实体标签)、lastModified(HTTP 日期,例如 Fri, 09 Oct 2026 00:00:00 GMT)、cacheControl 和 noindex。用 McpPublisher.registerUi 添加一个 ui:// 视图资源。

发现一个 App:

import { CatalogDiscovery, Discovery } from "@openbot-build/protocol"
import * as NodeHttpServer from "@openbot-build/protocol/NodeHttpServer"
import { Effect, Layer } from "effect"
const discovery = CatalogDiscovery.layer().pipe(
Layer.provide(CatalogDiscovery.layerRules),
Layer.provide(NodeHttpServer.layerPublicNetwork()),
)
const result = await Effect.runPromise(
Discovery.resolve("apps.example.com").pipe(Effect.provide(discovery)),
)

结果是 Single 或 Multiple。要用真实的宿主测试你自己的 App,请使用协议测试服务和开发者工具。