跳转至

集成和登录

本指南说明登录如何到达 OpenBot、已加入的服务器如何通过其用户的计算机进行登录,以及如何添加提供方、应用程序或连接器。每一节都包含带有真实文件路径的可用示例。

登录如何到达 OpenBot

运行 OpenBot 的计算机保留所有授权。在已加入的服务器上,这是宿主机,而不是执行登录的用户的计算机。登录使用以下类型之一:

类型 示例 在本计算机上 在已加入的服务器上
回环回调 URL MCP 服务器(McpOAuth)、Codex 浏览器登录、代理的 CLI 打印的页面 页面将浏览器发送到 http://127.0.0.1:<port>/...。MCP 监听器使用端口 28417 和路径 /mcp-auth(mcp-oauth-redirect-server.ts)。 回调中继(sign-in-relay-v1)。较旧主机上的 MCP:宿主机实时视图(mcp-sign-in-v1)。
设备代码 Codex(codex-device)、Grok、Cline、Muse、GitHub 连接器 用户在提供方页面上输入一个短代码。不会有任何内容返回给 OpenBot。 宿主机启动它,客户端显示代码(providers-v1 到 providers-v5)。Muse:中继打开其设备页面,没有回调。GitHub:connectors-v1。
粘贴的代码 Linux 上的 Claude(cli / paste)、Cursor(cli / link) 页面显示一个代码,用户将其粘贴到 OpenBot 中。 宿主机启动它,客户端获取代码(providers-v3 到 providers-v5)。
CLI 自有的浏览器登录 Claude(auth login)、Codex、Gemini(acp-authenticate) 提供方的 CLI 打开浏览器并接收其自己的回调。 中继(provider/start):宿主机捕获页面,客户端将回调查询发送到宿主机上 CLI 的监听器。Grok、Cursor 和 Cline:属于代码类型。
深度链接 Slack、Discord(openbot://slack-workspace、openbot://discord-guild)、MCP 回退(openbot://mcp-auth) 托管页面以 openbot:// 链接结尾,该链接只能到达打开它的计算机。 Slack 和 Discord:connectors-v1。客户端将密封的授权转发给启动安装的宿主机。浏览器客户端从自身来源的返回页面获取授权(web-messaging-install.ts)。
终端 Pi OpenBot 在宿主机上的终端中打开 CLI(provider-terminal-login.ts)。 在宿主机上登录。
密钥或本地会话 API 密钥、1Password、Bitwarden 用户输入密钥或提供 CLI 会话。 密钥通过加密通道发送到宿主机。Vault 审批保留在宿主机上。Bitwarden 会话密钥仅在其创建的计算机上有效;已加入的服务器获取 API 密钥和主密码。

提供方的登录类型位于 provider-drivers.ts 中的 ProviderSignIn 和 ProviderCodeSignIn。应用程序的类型是目录条目的 auth 类型:link、key 和 local。

MCP 登录如何续期和结束

McpOAuth(mcp-oauth-provider.ts)在线程启动或测试之前续期令牌,无需浏览器。续期调用 SDK 的 refreshAuthorization,而不是 auth(),因此它永远不会注册新客户端。令牌端点的响应决定保留的内容:

响应 令牌 客户端注册 登录应用、发现 结果
新的令牌集 已替换 已保留 已保留 已登录
invalid_grant 已丢弃 已保留 已保留 需要登录(lostAt)
invalid_client、unauthorized_client 已丢弃 已丢弃 已保留 需要登录(lostAt)
无网络、服务器错误、超时 已保留 已保留 已保留 无变化

被丢弃的令牌不会交给代理:accessToken 返回 null。IPC McpSignInState.needsSignIn 指示面板显示登录徽章和登录按钮,并且不附带测试。冻结的 mcp-sign-in-v1 协议仅保留 mcpServerId 和 signedIn。

注销和移除会先删除本地令牌。然后 OpenBot 对每个令牌发送一次尽力而为的 RFC 7009 请求,优先发送刷新令牌,发送到授权服务器的 revocation_endpoint。该请求通过 secureOAuthFetch 发送,携带注册的客户端身份验证,并设置 5 秒限制。失败仅记录来源和状态,并且不会显示。

secureOAuthFetch 限制每个 OAuth 请求:请求体最多 1 MiB,并且响应必须在 20 秒内到达。外部截止时间保持不变。对 MCP URL 本身的请求属于 MCP 流量,因为探测使用相同的 fetch 作为传输,所以没有大小或时间限制。当 MCP URL 的主机仅解析到公共地址时,每个 OAuth 主机(metadata、authorization server、token、registration 和 revocation 端点)也必须仅解析到公共地址,否则请求会在建立连接之前以 error.backend.mcpOAuthPrivateEndpoint 失败。查找失败也会停止请求。局域网或 localhost 的 MCP URL 没有此类检查。该检查不会固定地址,因此 DNS 可能在检查与连接之间发生变化;这种残余风险可接受。运行 bun run test:mcp-oauth 以针对本地授权服务器检查登录路径。

代理如何访问已登录的 MCP 服务器

Agent 永远不会获得 OAuth token。McpGateway.endpoint 会为每个 http 行提供一条指向 McpOAuthProxy(mcp-oauth-proxy.ts)的路由,只要它在这台计算机上有登录态,或有服务器已结束的登录态,并且没有用户输入的 Authorization 头。GitHub 行保留自己的代理。其他行、stdio 行和 Test 探针保持不变。

部分 规则
地址 http://127.0.0.1:<port>/mcp/<route>。路由是行 ID 及其 URL 的摘要,因此 URL 改变就会产生新路由。
Bearer 路由与代理密钥的 HMAC。一个 bearer 只能打开一条路由。它使用 timingSafeEqual 进行比较,并注册为需要脱敏的内容。
以 403 拒绝 Host 不是 127.0.0.1:<port> 或 localhost:<port>、未知路径、错误的 bearer。
上游 该行的 URL 及其请求头,并附上当前 token。不会发送 provider 的 Authorization 和 cookies。只跟随同源的 307 和 308 重定向,最多 3 次。
服务器返回 401 执行一次 McpOAuth.refreshAccessToken,然后用新 token 重试一次。
无登录态 每个请求都会收到 JSON-RPC 错误 -32001,通知收到 202,GET 和 DELETE 收到 405。代理永远不会返回 401,因此 provider 不会启动自己的登录。
服务器不可达 返回 502,并附带 JSON-RPC 错误 -32603。

每个请求都会从已启用的行和已存储的登录态中读取其目标。移除、关闭、编辑或注销某一行会立即使其路由失效;不存在需要保持最新的路由列表。无法启动的代理会以 error.backend.mcpOAuthProxyUnavailable 将该行从交接中移除。原始 token 永远不会作为回退。

端口和密钥以加密方式保存在 openbot-mcp-oauth-proxy-v1.json 中。Codex 会保留恢复线程的 MCP 配置,因此重启后同一地址必须仍然有效。当保存的端口已被占用时,代理会使用新端口和新密钥,因此旧的 bearer 会失效。Codex 工具清单会记录代理地址和被代理的行;如果有变化,会在下一次轮次时替换 Codex 会话。没有代理行的计算机保留其清单。

运行 bun run test:mcp-oauth 可对照本地授权服务器和本地 MCP 服务器检查这些路径。

远程登录:回调中继

用户连接到一个已加入的服务器,例如云服务器。主机启动登录,但登录页面会在用户自己电脑的浏览器中打开。用户的浏览器保留着他们的密码、通行密钥(passkeys)和会话。用户登录后,页面会将浏览器重定向回一个回环地址。用户的 OpenBot 接收该回调并将其发送给主机。主机完成登录并保留授权。

可选的 Team API 能力 sign-in-relay-v1 承载该中继。冻结的契约是 sign-in-relay-v1.ts。它有三个启动路由,provider-account-relay-v1 增加了第四个。它们共享 status、complete 和 cancel:

启动路由 登录什么 主机如何传递回调
mcp/start 一个基于 URL 的 MCP 服务器,来自服务器设置或 Marketplace 它将代码交给 McpOAuth(自有流程)。
provider/start SIGN_IN_RELAY_PROVIDERS 中某个 provider 的 CLI:Codex、Claude、Gemini(antigravity)和 Muse 它将查询重放到主机上该 CLI 自己的回环监听器。Muse 设备页面没有回调:客户端只负责打开它。
link/start 主机 agent 在其对话中打印出来的登录页面 它将查询重放到该页面的回环地址。
provider-account/start Codex 或 Claude 第一个之后的账户(PROVIDER_ACCOUNT_RELAY_PROVIDERS),新账户或已注销的账户。独立能力 provider-account-relay-v1 增加了这条路由(provider-account-relay-v1.ts) 它会像 provider/start 一样,将查询重放到主机上该账户 CLI 的回环监听器。

时序

User's computer (client)                        Host (joined server)
------------------------                        --------------------
POST mcp/start {config}  ---------------------> McpHostSignIns.startRelay: start the sign-in,
                                                open no tab, keep the page address
                         <--------------------- {flowId, expiresAt}
POST status {flowId}     ---------------------> SignInRelayHost.status
                         <--------------------- {authorizeUrl, callback {port, path, state}, result}
Read port, path and state from authorizeUrl
(refuse a page with no state)
Listen on that port (or use the own
MCP listener on 28417 /mcp-auth, by state)
Show the page's host name; the user clicks
"Open sign-in page" (https only)
        ... the user signs in ...
Browser -> http://127.0.0.1:<port><path>?code&state
POST complete {flowId, query} ----------------> check state, give code to McpOAuth
                         <--------------------- {result: null} -> read status until result

组成部分

部分 文件
冻结的线上契约 packages/contracts/src/team-protocol/sign-in-relay-v1.ts
主机流程(单次使用、过期、成员、状态) src/main/sign-in-relay-host.ts
主机路由(每条路由均使用 requireAdmin) src/main/team-api/route-sign-in-relay.ts
不带标签页的主机登录("relay" 模式) src/main/host-sign-in-pages.ts, src/main/mcp-host-sign-ins.ts
客户端流程(监听、打开、完成、粘贴) src/main/sign-in-relay-client.ts
回环监听器检查(Host、方法、路径) src/main/loopback-callback-server.ts
桌面端 IPC(servers:mcp:sign-in-relay、servers:mcp:open-sign-in-relay、servers:mcp:complete-sign-in-relay) src/main/ipc/mcp-server-handlers.ts
粘贴对话框 McpSignInRelayDialog in packages/ui/src/features/settings/McpSignInPage.tsx
浏览器客户端(仅粘贴) src/renderer/src/features/web-client/web-server-settings.ts
Marketplace src/renderer/src/features/settings/marketplace-controller.ts、web-marketplace.ts
宿主上的 Provider 页面捕获(BROWSER 命令、ACP 链接) src/backend/agent/browser-capture.ts、src/main/provider-host-sign-ins.ts
已加入服务器上的 Provider Connect src/main/ipc/provider-admin-handlers.ts、web-provider-relay.ts
Agent 链接(agent:open-message-link、agent:paste-message-link、agent:cancel-message-link) src/main/message-link-relay.ts、web-message-link-relay.ts

客户端如何接收回调

客户端按以下顺序尝试这些方式:

  1. 它自己的 MCP 授权监听器。 回调指定端口 28417 和路径 /mcp-auth,而客户端自己的 MCP 授权监听器正使用该端口。监听器会向 SignInRelayClient.deliver 传递一个没有启动任何本地登录的 state。openbot://mcp-auth 链接也会执行相同操作。
  2. 精确端口。 客户端在登录期间通过 listenLoopbackExact 绑定 127.0.0.1:<port>。它绝不会占用其他端口,因为页面会将浏览器导向宿主的端口。
  3. 粘贴的地址。 端口已被占用、回调不是回环地址,或者客户端是浏览器客户端。对话框会要求用户粘贴浏览器在末尾显示的地址。客户端只提取查询部分,并通过 complete 发送。

没有回调的页面(例如 Muse 设备页面)就没有可供接收的内容。客户端会打开它并读取 status,直到宿主上的 CLI 结束登录。

不具备 sign-in-relay-v1 的宿主会回退到 mcp-sign-in-v1 的实时视图:宿主会在自己的私有标签页中打开页面,用户通过实时视图完成登录。两种能力都不具备的宿主会回应:登录必须在宿主机上完成。

规则

  • 只有所有者或管理员可以使用中继路由。requireAdmin 会在每条路由上运行。
  • flowId 是 128 个随机位。一个流程只会完成一次,且只对启动它的成员生效,并在 SIGN_IN_RELAY_TTL_MS(10 分钟)后结束。未知流程、过期流程以及其他成员的流程都会返回相同的 404。
  • 客户端只发送回调的查询部分。宿主会对照其自身登录流程生成的页面地址检查 state。客户端永远无法选择宿主端口、主机名或路径。
  • 宿主只会为 127.0.0.1、localhost 或 [::1] 上的 http: 地址(带端口)命名回调。
  • 宿主选择页面,因此不诚实的宿主可能会请求用户浏览器中另一个账户的授权。因此,客户端会:
  • 只打开 https: 页面,并且只在用户点击显示页面主机名的对话框中的“打开登录页面”时才会打开;
  • 从页面的 redirect_uri 读取回调端口和路径,而不是从宿主的 callback 中读取;
  • 拒绝没有 state 的页面,因此其监听器只会传递本次登录的回调。
  • 客户端监听器会拒绝 Host 头不是回环名称的请求、不是 GET 或 HEAD 的方法,以及任何其他路径。它的页面是一条固定的目录消息。
  • 授权码是机密信息。宿主会在传递前对其调用 registerSecretValue。任何日志行、分析事件或诊断导出都不会包含该授权码、state 或查询内容。
  • 授权码只通过加密的 Team 通道传输。账户 Worker 永远不会看到它。
  • 重放投递会向宿主自身页面的回环地址发送一个 GET 请求。它不跟随重定向,只保留状态码并丢弃响应体。
  • link/start 只接受带有 state 和回环 redirect_uri(relayableSignInLink)的 https 页面,并且只有当 agentId 在过去一小时内的某条 Agent 消息将该确切地址作为完整链接包含(agentPrintedLink)时才接受。任何其他链接都会返回 404,因此客户端无法让宿主向它选择的端口发送查询。重放只发送在宿主机上点击该链接时会发送的内容。
  • Agent 链接会立即打开,因为用户已点击它。桌面应用只有在宿主指定的页面正是用户点击的链接时才打开它;任何其他页面都会在未打开的状态下结束登录。浏览器客户端一如既往地在点击时打开链接,因为浏览器会阻止未经点击而打开的页面;只有当宿主指定的是同一页面时,它才接受粘贴的地址。
  • 当桌面应用无法绑定端口时,以及在浏览器客户端中始终如此,openMessageLink 会返回该页面,对话中显示带有粘贴字段的 McpSignInRelayDialog。pasteMessageLink 在登录结束时结束;cancelMessageLink 会在宿主上取消它。

新中继类型的安全检查清单

  • [ ] 主机根据自身记录构建完整回调地址。客户端仅发送查询。
  • [ ] 主机在传递前检查 state。客户端拒绝没有 state 的页面。
  • [ ] 在用户看到其主机名并发出请求之前,不得打开该页面。
  • [ ] 一个流程在截止时间前为一个成员只完成一次。
  • [ ] 代码通过 registerSecretValue 注册,且绝不写入日志。
  • [ ] 路由使用 requireAdmin,且其编解码器位于可选能力中(而非 bootstrap 中)。
  • [ ] 检查单次使用、对方成员、过期和状态不匹配。另请参阅 测试通过已加入的服务器进行登录。

添加提供方

提供方是编码代理 CLI,例如 Codex、Claude 或 Gemini。请先阅读 提供方。 描述符行和驱动是起点;类型检查器随后会指出其余大部分位置。

  1. 描述符。 将 id 添加到 AGENT_PROVIDERS,并在 packages/contracts/src/agent-providers.ts 中向 AGENT_PROVIDER_DESCRIPTOR_TABLE 添加一行: displayName、cliName、authKind、signInMessage、localCodeSignIn、skillFolders 以及 其他字段。该表使用 satisfies Record<AgentProviderId, ...>,因此缺少某行将无法编译。 不要添加到 AGENT_IDS_RESERVED_AT_RELEASE:它是冻结的。
  2. 驱动。 在 src/backend/provider-drivers.ts 中向 BUILT_IN_PROVIDER_DRIVERS 添加一行。选择登录方式:
  3. signIn:browser、cli-command、external、terminal、muse-device 或 acp-authenticate。
  4. codeSignIn(可选):codex-device、muse-device,或使用 device、paste 或 link 流程的 cli。代码登录正是让已加入服务器现在就能登录的方式。 分派由 provider-runtime.ts 中的 #signIn 和 #startProviderCode 完成。复用这些辅助函数: acp-sign-in.ts、 cli-code-login.ts、 codex-login.ts 和 provider-terminal-login.ts。
  5. 后端。 src/backend/cli.ts 中的 resolve<X>Cli、src/backend/process-confinement.ts 中的状态路径、 src/backend/agent/thread-items.ts 和 src/backend/agent/provider-diagnostics.ts 中的 providerLabel。
  6. 托管运行时(当 OpenBot 安装 CLI 时):native-runtime.lock.json、 scripts/agent-runtime-lock.ts,以及 src/main/provider-runtime-descriptors.ts、 provider-runtime-releases.ts 和 provider-runtime-manager.ts。
  7. 契约和预加载。 ipc-agent-status.ts 中的 AgentAuthState 分支,然后是 ipc-provider-admin.ts、src/preload/provider-runtime.ts 和 src/preload/agent-decoding.ts。
  8. 渲染器。 SetupProviderPicker.tsx、ProviderSettingsSection.tsx、app-defaults.ts,以及 预览文件 mock-provider-runtimes.ts。
  9. 徽标。 位于 packages/brand/src/ProviderLogo.tsx 中的 ProviderLogoVariant 联合类型及其 SVG,以及 packages/brand/src/tokens.css 中的颜色。
  10. Team API。 已发布能力的提供方列表是冻结的。已加入的服务器上的新提供方需要新的 providers-vN 能力(见 提供方 中的“协议兼容性”),以及 provider-visibility.ts 中的对等规则。
  11. 文本和文档。 error/provider.ts 中的错误键以及所有语言中的引导文本 (docs/i18n.md)。在 提供方 中添加一节,并添加 changelog.d/<branch>.md 文件。

工作示例:Codex 浏览器登录。 驱动行使用 signIn: { kind: "browser" }。 codex-login.ts 中的 CodexLoginFlow.startBrowser 会向 Codex app-server 请求 account/login/start, 获取 authUrl,并使用 openExternal 打开它。Codex CLI 自行处理回环回调。 对于另一台计算机,该行还包含 codeSignIn: { kind: "codex-device" }:主机启动设备码, 客户端将其显示出来。

在带有 sign-in-relay-v1 的已加入服务器上,Connect 使用 provider/start。 主机以 "relay" 模式运行相同的 startBrowser,因此 openPage 会保留 authUrl 且不打开标签页。客户端在自己的浏览器中打开该页面,并监听页面 redirect_uri 的端口(当前 Codex 版本中为 1455)。主机将回调查询重放到该端口上的 Codex 监听器。

要中继新的提供方,请在能力发布之前将其添加到 SIGN_IN_RELAY_PROVIDERS, 并为主机提供其页面:

  • 遵循 BROWSER 的 CLI:使用 browser-capture.ts 中的 captureBrowser 运行它。Claude 就是这样做的。
  • 打印其链接的 ACP agent:使用 acp-authenticate 捕获方式。Gemini 就是这样做的。
  • 返回页面地址的 CLI(例如 Codex app-server):将它交给 openPage。

自行打开浏览器且不遵循 BROWSER 的 CLI 无法中继。请为它提供代码登录方式。

添加应用程序

应用程序是 Marketplace 安装的 MCP 服务器,可带有可选技能。源码位于 marketplace/plugin-catalog/ 中,每个插件对应一个文件夹。

  1. 添加 marketplace/plugin-catalog/plugins/<slug>/plugin.json,并将该 slug 添加到 catalog.json 的 order 中。字段包括 slug、name、tagline、description、category、creatorName、iconUrl、version、prompts、apps、skills(目前为 [])以及政策 URL。类型见 scripts/build-plugin-catalog.ts。

  2. 每个应用都有一个 server:http(带 url),或 stdio(带 command 和 args)。它的 auth 描述用户如何连接:

  3. link:浏览器登录。OpenBot 是 OAuth 客户端(mcp-oauth-provider.ts 中的 McpOAuth):发现、动态注册、PKCE 和刷新。令牌保存在加密存储中。无需桥接程序。
  4. key:一个或两个字段,放入 header(http)或 env 值(stdio)中。
  5. local:用户在运行服务器的计算机上执行的步骤。

通过 AppleScript 驱动 Apple 应用的 stdio 应用会在 macAutomation 中列出这些应用(calendar、contacts、mail、notes、reminders、messages、safari)。在运行服务器的 Mac 上,列表页面随后可以在安装时向 macOS 请求每个应用(mac-automation.ts)。移动端副本会省去该字段。

  1. 绝不要把机密放在目录中。用户在连接对话框中输入它。

  2. 构建并检查生成的文件:

    bun run marketplace:build:plugins
    bun run marketplace:build:plugins -- --check
    
    构建会写入 src/renderer/src/features/settings/marketplace-plugin-catalog.ts、apps/auth-api/src/lib/plugin-catalog.generated.ts、apps/mobile/src/features/marketplace/model/plugin-catalog.generated.ts 和 resources/plugin-catalog/。请勿手动编辑它们。移动端副本声明自己的类型,并省略厂商图标地址:手机从 /plugins/icon/<slug>?app=<id> 加载图标。

  3. 对于 http 服务器,运行 bun run test:mcp-catalog(mcp-catalog-smoke.ts)。它会在没有账户的情况下连接在线服务器,并写入 .openbot-build/mcp-catalog-smoke/report.json。它需要网络,因此是手动运行,不是 CI 门禁。

冒烟测试会检查没有 auth 或使用 link 流程的 http 服务器。它会跳过 stdio 服务器以及仅包含 key 或 local 流程的服务器:这些服务器需要用户的密钥或计算机。仅当冒烟测试通过时,才添加或保留对应条目:

auth 服务器必须
None 在无凭据的情况下应答 initialize。
link 以 401 和 Bearer 质询拒绝 initialize。发布受保护资源元数据,指明授权服务器。该服务器的元数据列出 registration_endpoint、code_challenge_methods_supported 中的 S256 以及 code 响应类型。其端点使用 https 和公共地址。

没有 registration_endpoint 的 link 服务器会失败:每个用户都必须先注册一个登录应用。这种情况下应改用 key 流程,或者将其排除在外。冒烟测试只读取元数据。它从不注册客户端,也从不发送凭据。

没有自行注册 OpenBot 的服务器需要一个登录应用(sign-in app):管理员向提供商注册的 OAuth 应用。用户在 MCP 服务器的“登录应用”区域输入其客户端 ID(McpSignInAppSection.tsx)。回调 URL 为 http://127.0.0.1:28417/mcp-auth。在已加入的服务器上,主机保留该应用(mcp-sign-in-app-v1)。

工作示例:Linear(marketplace/plugin-catalog/plugins/linear/plugin.json):

"apps": [
  {
    "id": "app-linear-mcp",
    "name": "Linear",
    "description": "Issue search, triage, status updates, and issue creation, over Linear's MCP server with browser sign-in.",
    "iconUrl": "https://linear.app/favicon.ico",
    "server": {
      "name": "linear",
      "transport": "http",
      "url": "https://mcp.linear.app/mcp",
      "auth": [{ "id": "linear-oauth", "kind": "link", "label": "Sign in" }]
    }
  }
],

在本计算机上,“连接”会在浏览器中打开 Linear 的页面,授权通过 28417 端口返回。在带有 sign-in-relay-v1 的已加入服务器上,Marketplace 会在主机上安装该应用,并通过中继在本计算机的浏览器中打开页面。

添加连接器

连接器将 OpenBot 与外部账户连接起来,供所有智能体使用。每个连接器都有一个主进程服务、一个 IPC 契约、一个渲染器控制器和一个面板。Marketplace › 应用、聊天建议卡片和浏览器客户端读取同一个列表,因此连接器在它们每个上都显示相同的状态。

连接器 服务 登录 契约
GitHub github-connector-service.ts, github-device-flow.ts GitHub App 的设备流程,仅使用公共客户端 ID ipc-github-connector.ts;已加入服务器:connectors-v1 GitHub 路由
Slack src/backend/messaging/slack/ 账户 Worker 中的托管 OAuth,以 openbot://slack-workspace 结束并附带密封授权 slack-workspace-grant.ts、ipc-messaging.ts
Discord src/backend/messaging/discord/ 相同,以 openbot://discord-guild 结束 discord-guild-grant.ts、ipc-messaging.ts
Telegram src/backend/messaging/telegram/ t.me 链接中的一次性链接代码。无 OAuth telegram-app.ts、ipc-messaging.ts
1Password onepassword-connector-service.ts 用户的 op 会话创建只读服务账户,或用户粘贴令牌 ipc-onepassword-connector.ts;已加入服务器:onepassword-connector-v1(仅令牌)
Bitwarden bitwarden-connector-service.ts 一个仅保存在内存中的 bw 会话密钥;已加入的服务器使用 API 密钥和主密码登录 ipc-bitwarden-connector.ts;已加入的服务器:bitwarden-connector-v1

要添加一个连接器:

  1. 在 packages/contracts/src/ipc-<name>-connector.ts 中添加 IPC 契约,在 src/main/ipc/<name>-connector-handlers.ts 中添加处理程序(阅读 src/main/ipc/AGENTS.md),并添加预加载解码器和预览 mock。
  2. 在 src/main/<name>-connector-service.ts 中添加服务。将其机密保存在加密存储中,并在打印第一条日志行之前对每个 token 调用 registerSecretValue。
  3. 优先使用设备码:它可以在任何计算机上以及通过任何已加入的服务器工作。深度链接(openbot://...)只能到达打开该页面的计算机。将其类型添加到 deep-link-router.ts,并让授权不进入渲染器。
  4. 将 ID 添加到 CONNECTOR_IDS,并将其条目添加到 connectors.ts 中的 CONNECTORS。server 是让已加入的服务器运行它的能力;当只有用户自己的计算机可以运行时(如保险库应用),则为 null。connector-access.ts 会读取它;没有任何界面拥有自己的门控。
  5. 将其名称、标语、Marketplace 类别和标记添加到 connector-catalog.tsx 中的 CONNECTOR_LISTINGS;将其面板添加到 ConnectorDetail 和 ConnectorDetailPanel;将其行状态添加到 connectorSummary。
  6. 添加一个控制器(位于 connector-controller.ts 中的 ConnectorController),并为其操作提供 createConnectorActions,然后在 connector-registry.ts 的 createConnectorSet 中创建它。connect 是 Marketplace 卡片上的一键 Connect;当面板必须先要求输入时,则将其省略。watch 跟踪一个在主进程不发送事件的情况下会发生变化的状态。类型检查器会指出每个仍然缺少新 ID 的位置。
  7. 在所有语言中添加文本键,并添加一个 changelog.d/<branch>.md 文件。当连接器向外发送新数据时,更新 PRIVACY.md。

connectors-context.tsx 在桌面端创建两个注册表:一个用于当前屏幕上的服务器(Marketplace 和聊天卡片),另一个用于设置已打开的服务器。浏览器客户端在 WebWorkspace.tsx 中创建一个。注册表为每个连接器提供其访问状态:available(带有其控制器)、local-only、hidden、unsupported(例如没有 GitHub App 的 GitHub),或在首次读取之前为 loading。

在已加入的服务器上,可选能力 connectors-v1(connectors-v1.ts,主机路由 route-connectors.ts)承载 GitHub、Slack、Discord 和 Telegram:

  • GitHub:主机运行设备码流程。客户端显示 userCode 并打开 verificationUri。主机会自行得知登录完成。
  • Slack 和 Discord:主机返回安装页面和一个 nonce,并且不打开任何内容。客户端打开该页面。安装最终在客户端上以 openbot:// 链接结束,该链接带有一个授权(grant),该授权被密封到主机的一次性密钥上。RelayedMessagingInstalls(relayed-messaging-installs.ts)仅当这台计算机为某台服务器打开了安装页面时,才会将带有 messagingComplete 的授权发送给该服务器,且只发送一次。客户端无法打开该授权。
  • Telegram:客户端打开 t.me 链接。没有回调。
  • 没有任何路由会携带 token。概览会省略掉 bot token。

已加入服务器上的新连接器需要在一个新能力中提供新路由,因为 connectors-v1 一旦发布就会被冻结。

可选能力 discord-access-v1(discord-access-v1.ts,主机路由 route-discord-access.ts)可以读取和设置哪些 Discord 角色能够在主机的每个 Discord 服务器中使用 OpenBot,并通过 Signal(listRoles)列出该服务器的角色。主机将该列表保存在 projection_messaging_allowed_roles 中。空列表表示允许所有人。如果存在列表,MessagingService 会在作出反应或发帖之前忽略来自不具备任何这些角色的成员的提及。Signal 会在每条提及中附带作者的 role ID;来自旧版 Signal 的提及不会带有任何 role ID,因此有列表的服务器会忽略该提及。

可选能力 onepassword-connector-v1(onepassword-connector-v1.ts,主机路由 route-onepassword-connector.ts)承载 1Password。服务器通常没有用于 1Password 应用的屏幕,因此所有者或管理员会在 Marketplace › Apps › 1Password 中粘贴服务账户 token。该 token 会在 connect 中一次性发送给主机。主机用 1Password 校验该 token,并将其保存在自己的加密存储中。没有任何响应会携带它。状态和断开操作只作用于该主机。早于此能力的主机会隐藏 1Password。

已加入服务器上的 Bitwarden

可选能力 bitwarden-connector-v1(bitwarden-connector-v1.ts、主机路由 route-bitwarden-connector.ts)支持 Bitwarden 和自托管的 Vaultwarden。会话密钥只能解锁生成它的计算机上的 CLI 数据,因此主机使用自己的 bw 登录。所有者或管理员在“市场 › 应用 › Bitwarden”中输入服务器地址、个人 API 密钥和主密码。这些凭据只会在 connect 中一次性传给主机;不会有应答携带它们。connect 会立即返回 connecting,因为首次登录可能需要下载 bw 并同步保管库,耗时可能超过 15 秒的请求限制。客户端每 2 秒读取一次 status,直到状态改变。主机只有在解锁保管库并找到 Shared with OpenBot 文件夹后才会存储该记录,并在重启后再次从中解锁。主机必须通过 HTTPS 访问服务器地址;单主机上多个实例的实例防火墙会阻止私有地址。版本早于此能力的主机会隐藏 Bitwarden。

手机在“服务器设置 › 连接器 › Bitwarden”中使用相同的路由(apps/mobile/src/features/servers/components/bitwarden-connector-page.tsx)。与其他连接器一样,它在页面显示期间每 3 秒读取一次 status,并且不会保存 API 密钥或主密码的副本。对于版本早于此能力的主机,手机会将 Bitwarden 显示为“仅限桌面应用”,且不提供任何操作。

添加 Team API 能力

新的远程功能是一种新的可选能力。不要更改已发布的能力:已发布的 Team API 适配器是永久性的(参见 Team API 兼容性)。示例:sign-in-relay-v1,它沿用 mcp-sign-in-app-v1。

  1. 冻结的编解码器。 添加 packages/contracts/src/team-protocol/<name>-v1.ts。使用 admin-wire.ts 的辅助函数(adminRoute、fields、string(max)、nullable)构建每个路由。导出能力名称、路由和编解码器映射。将头部注释作为契约编写:说明每个路由的作用以及谁可以使用它。
  2. 包导出。 将 "./team-protocol/<name>-v1" 添加到 packages/contracts/package.json。
  3. 编解码器。 将编解码器映射展开到 optional-routes.ts 中的 CODECS。
  4. 能力列表。 将能力添加到 current.ts 中的 TEAM_CURRENT_CAPABILITIES,并将其从 TEAM_BOOTSTRAP_CAPABILITIES 和 legacyTeamCapabilities 中排除。较旧的对端读取的引导能力数量有限,因此新的可选能力不进入引导列表。
  5. 主机路由。 添加 src/main/team-api/route-<name>.ts。对于其他路径返回 "unmatched",当该能力关闭时以 400 拒绝,并对管理员路由调用 requireAdmin。在 team-api-server.ts 的 TeamApiServer.#handle 中注册它,并且仅当其依赖存在时,才在 #protocolSupport 中公布该能力。将依赖类型添加到 team-api/dependencies.ts 中,并在 application-services.ts 中创建它,然后通过 host-service.ts 传递它。
  6. 客户端门控。 将能力添加到 server-capabilities.ts 中 serverSupportsCapability 的显式列表中,这样从未发送过该能力的较旧主机就不会被默认视为支持。在主进程中,使用 remoteServers.supportsCapability(serverId, CAPABILITY) 进行门控。
  7. 浏览器客户端。 将请求添加到 team-admin-requests.ts,并在 Web 客户端中根据主机的能力对它们进行门控。
  8. 引导(Bootstrap)。 确保该能力不在引导列表中。
  9. 文本。 错误消息使用 sourceText(key),键位于 @openbot/i18n 的所有语言中。

测试通过已加入服务器的登录

  1. 两个客户端:bun run dev:test-client 会启动服务、应用,以及一个带有自身配置文件的第二个客户端(参见 README.md: 命令)。从第二个客户端以管理员身份加入应用。在主机上添加一个带 OAuth 的 URL MCP 服务器,然后在客户端上点击登录。页面必须在客户端的浏览器中打开,并且主机必须显示该服务器已登录。
  2. 粘贴回退:用另一个程序占用客户端上的 28417 端口,然后再次登录。对话框会要求输入地址。从浏览器粘贴地址。主机必须登录成功。
  3. 浏览器客户端:打开 /app 并登录到同一服务器。对话框显示“打开登录页面”和粘贴字段。
  4. 检查两台计算机的日志。它们不得包含 code=、code 或 state。

使用 bun run dev:automation 进行快照和截图。不要操作另一个 worktree 的应用。

哪些内容无法中转

流程 原因
登录会返回提供方的公共 https: 地址 提供方会自行完成登录。主机只是等待。不会有任何内容返回到回环端口。
在主机上打开应用(如 Pi)的登录,Pi 会打开 Terminal 该步骤发生在主机的屏幕上。没有可在其他位置打开的页面地址。
在桌面应用中批准操作的密钥库,如 1Password 或 Bitwarden 批准操作发生在主机上的密钥库应用中。中继无法代为批准。已加入服务器上的 1Password 使用服务账户令牌(onepassword-connector-v1);Bitwarden 使用 API 密钥和主密码(bitwarden-connector-v1)。
Slack 开发返回地址(returnUrl) 仅限开发构建。它会返回到主机上的 http://127.0.0.1:<port>/...,中继不会携带该地址。请使用托管安装。
非管理员成员的代理链接 中继路由仅限管理员使用。链接会原样打开,其回调无法到达主机。
命令、工具输出或评论中的代理链接 主机仅中继代理消息文本中包含的链接。
打开自身浏览器且不遵守 BROWSER 的 CLI 主机永远不会获得页面地址。Grok、Cursor 和 Cline 使用一种代码类型。
iPhone 应用 它不接受中继登录。它保持当前行为。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 openbo/openbot