SSD Nodes Learn 8GB 内存 — 每年 $66
指南 Matt Connor作者: Matt Connor

如何自行托管 Open Connector,保护 AI 代理令牌

在自己的 VPS 上运行 Open Connector 身份验证网关,固定 v1.3.3 镜像,配置 TLS 源站与 OAuth 回调,并完成 SQLite 备份,避免代理持有 SaaS 令牌。

Open Connector 为 AI 代理提供的功能

自行托管 Open Connector,可以在 AI 代理与其调用的所有软件即服务(SaaS)API 之间设置一个身份验证网关,因此代理无需持有提供商令牌。它是 OOMOL Lab 开发的开源网关,采用 Apache 2.0 许可证。它以单个容器运行,将状态保存在一个 SQLite 文件中,并通过 HTTP 和 MCP(模型上下文协议)公开提供商操作。

问题从第二个集成开始。每个提供商都有自己的 OAuth(开放授权)流程、刷新令牌有效期和作用域名称。手动将 5 个提供商接入代理,意味着需要编写 5 个重定向处理程序、5 个凭据存储和 5 个刷新循环,并且这些循环必须在令牌过期前运行。几乎没有人会编写这些代码。他们会为每项服务生成一个长期有效的个人访问令牌,然后将其粘贴到代理配置、环境文件,甚至提示词中。此后,代理运行的每个工具都可以读取该令牌,令牌还会进入转录记录。这正是避免 AI 代理暴露机密所描述的故障。

身份验证网关会将凭据分成两部分。网关存储提供商凭据并运行 OAuth 流程。代理获得的运行时令牌仅对网关有效。当代理调用某个操作时,网关会加载已存储的凭据,在服务器端将其注入出站请求,然后仅返回响应正文。代理永远不会收到提供商访问令牌,因此代理转录记录泄露时,您损失的只是一个可撤销的运行时令牌,而不是整个 GitHub 帐户。

目录列出了超过 1,000 个提供商和 10,000 个预构建操作。这是项目自身提供的数据,无法从外部验证。可以验证的是其结构:每个操作对应一个 HTTP 端点,每个提供商对应一个已存储连接,每个代理对应一个令牌。

为什么选择自行托管 Open Connector,而不是使用托管连接器服务

托管连接器服务执行相同的工作,并保存您连接的每个提供商的刷新令牌。Google 或 GitHub 的刷新令牌是访问您的邮件和代码仓库的长期密钥,通常不会因密码更改而失效。它们遭到入侵,您也会受到影响。自行托管会将这些记录迁移到您租用并管理的计算机上的 SQLite 中,并使用始终留在本机上的密钥进行保护。

开始前,请明确这项工作的成本。此 VPS 将成为您运行的最有价值的服务器。它会在一个文件中保存十几个服务的有效凭据,因此应按照密码管理器主机的标准进行管理:防火墙只开放 443,不使用共享登录凭据,确保至少成功恢复过一次备份,并在服务停止响应时发出警报。如果您不会将密码库放在这台计算机上,也不要将连接器放在上面。

安装任何内容前固定版本

Open Connector 尚处于早期阶段。该仓库首次出现于 29 June 2026。截至 1 August 2026,最新的带标签版本是 v1.3.3,于 30 July 2026 发布,同时带有 latest 标签。registry 还发布了 tip 标签,该标签基于 main 上的最新提交构建。

对于这样新的项目,浮动标签经常变化。跳过两个版本的 docker compose pull 可能会更改 agent 依赖的 endpoint,导致您花费整个晚上将其误判为 agent 问题并进行调试。将镜像固定到发布标签,并在阅读发行说明后,按您的计划进行升级。

在您自己的 VPS 上通过 TLS 部署 Open Connector

容器启动前,您需要准备:

  • 安装了 Compose plugin 的 Docker,运行在 Ubuntu 24.04 或相近版本上
  • 一个 A 记录指向此 VPS 的主机名,例如 connect.example.com
  • 一个已经为该主机名终止 TLS(传输层安全)的反向代理
  • 下面生成的两个随机密钥

适用于多个 Docker Compose 应用的 Traefik 反向代理介绍代理配置。针对单个应用,从头到尾配置证书的流程,请参阅在 VPS 上使用 Docker 和 HTTPS 运行 n8n

先生成密钥。加密密钥用于保护存储的凭据。管理员令牌用于保护 Web 控制台和整个 /api 接口。两者都没有默认值,但运行时在缺少它们时仍会正常启动。

mkdir -p ~/open-connector && cd ~/open-connector
umask 077
printf 'OOMOL_CONNECT_ENCRYPTION_KEY=%s\n' "$(openssl rand -base64 32)" > .env
printf 'OOMOL_CONNECT_ADMIN_TOKEN=%s\n' "$(openssl rand -base64 32)" >> .env
chmod 600 .env

现在就将两个值复制到密码管理器中,之后再进行首次启动。加密密钥无法恢复,原因见下方的故障列表。

现在创建 compose.yaml。它与上游示例有两处不同,而且两处都很重要。

services:
  connector:
    image: ghcr.io/oomol-lab/open-connector:v1.3.3
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - connector-data:/app/data
    environment:
      OOMOL_CONNECT_DATA_DIR: /app/data
      OOMOL_CONNECT_ORIGIN: "https://connect.example.com"
      OOMOL_CONNECT_ENCRYPTION_KEY: "${OOMOL_CONNECT_ENCRYPTION_KEY:?set this in .env}"
      OOMOL_CONNECT_ADMIN_TOKEN: "${OOMOL_CONNECT_ADMIN_TOKEN:?set this in .env}"

volumes:
  connector-data:

第一处改动是使用固定标签,而不是 latest。第二处改动是端口。上游文件发布 3000:3000,这会绑定主机上的所有接口。Docker 会在数据包进入 ufw 过滤链之前,将发布端口写入 NAT(网络地址转换)表,因此 ufw deny 3000 不会关闭该端口;详见Docker 端口为何绕过 ufw中介绍的陷阱。使用 127.0.0.1:3000:3000 只会在回环接口上发布端口,而您的反向代理从同一主机连接。

:? 会将每个变量标记为必需变量。因此,缺少 .env 时,堆栈会拒绝启动,而不会在凭据未加密的情况下启动。将这些值保存在 .env 中,而不是 compose 文件中,这是Docker Compose 环境文件和密钥采用的模式。

docker compose up -d
docker compose logs -n 30 connector
curl -s http://127.0.0.1:3000/health
sudo ss -tlnp | grep 3000

/health 会在运行时启动后回答 { "ok": true }ss 必须输出 127.0.0.1:3000。如果某行显示 0.0.0.0:3000,说明端口映射仍然是上游配置,网关正直接响应整个互联网。健康检查返回“连接被拒绝”表示容器尚未监听,因此请先读取日志,不要先修改代理配置。

同一服务的 Traefik 标签
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.connector.rule=Host(`connect.example.com`)"
      - "traefik.http.routers.connector.entrypoints=websecure"
      - "traefik.http.routers.connector.tls.certresolver=le"
      - "traefik.http.services.connector.loadbalancer.server.port=3000"

当 Traefik 在同一主机上的 Docker 中运行时,请将此服务连接到 Traefik 网络,并删除 ports: 块。Traefik 会通过内部网络访问容器,因此完全不需要向主机发布端口。certresolver=le 必须与 Traefik 静态配置中的解析器名称一致,否则路由器启动时不会获得证书。

为什么 OAuth 要求您使用真实主机名

OOMOL_CONNECT_ORIGIN 是人们经常跳过的设置。跳过后,OAuth 会以看似提供商错误的方式失败。运行时根据该源构建重定向 URI,格式为 <origin>/oauth/callback。如果未设置该值,源会默认为 http://localhost:3000。因此,运行时会向提供商发送 http://localhost:3000/oauth/callback 形式的重定向 URI,而您的 OAuth 应用注册的是 https://connect.example.com/oauth/callback。两个字符串不同,因此 GitHub 返回:

The redirect_uri MUST match the registered callback URL for this application.

OAuth 提供商会将浏览器重定向回该 URI。因此,该 URI 必须是外部网络可以访问的地址。除 localhost 外,提供商会拒绝纯 http://。这就是此部署需要主机名和证书的原因。请在首次启动前设置源,因为该值会在启动时读取:编辑 .envcompose.yaml 后,再次运行 docker compose up -d 以应用更改。

通过 OAuth 连接您的第一个提供商

先在提供商处创建 OAuth 应用。在 GitHub 中,依次进入 Settings、Developer settings、OAuth Apps,然后选择 New OAuth App。将授权回调 URL 设置为 https://connect.example.com/oauth/callback。保存 client ID 和 client secret。

每次 /api 调用都会携带 admin token,因此请在 shell 会话中导出一次。

export ADMIN_TOKEN='paste-the-admin-token'
curl -s https://connect.example.com/api/oauth/configs \
  -H "authorization: Bearer $ADMIN_TOKEN"

该列表显示运行时为每个提供商预期使用的重定向 URI。这是最快的检查方式,可确认您的 origin 已生效。如果仍显示 localhost,说明容器使用的是旧值,OAuth 流程将在最后一步失败。

存储 client credentials,然后开始授权。

curl -s -X PUT https://connect.example.com/api/oauth/configs/github \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"clientId":"...","clientSecret":"..."}'

curl -s -X POST https://connect.example.com/api/oauth/authorizations \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"service":"github"}'

第二次调用会返回一个 authorizationUrl。在浏览器中打开它并批准所需权限。提供商随后会将浏览器重定向回 /oauth/callback,运行时会在此处交换 code 并存储 credential。您可以在 origin 处的 Web 控制台中使用表单完成相同步骤,该控制台同样受 admin token 保护。使用普通 API key 的提供商无需执行这些步骤:使用 {"authType":"api_key","values":{"apiKey":"..."}} 调用 PUT /api/connections/<service> 可直接存储该 key。

为每个代理分配运行时令牌,不要分配凭据

代理使用由管理 API 签发的运行时令牌向网关进行身份验证。

curl -s -X POST https://connect.example.com/api/runtime-tokens \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"research-agent"}'

响应中包含一个以 oct_ 开头的令牌。为每个代理签发一个令牌,并使用该代理的名称命名。否则,撤销无法识别的令牌时,可能会撤销所有令牌。代理随后通过普通 HTTP 调用操作。

curl -s -X POST https://connect.example.com/v1/actions/github.get_current_user \
  -H "authorization: Bearer oct_..." \
  -H 'content-type: application/json' \
  -d '{"input":{}}'

正常响应是一个信封,其中 success 字段为 true,提供商负载位于 data 中。该响应中的任何位置都不包含 GitHub 令牌。对于 MCP 客户端,将其指向 https://connect.example.com/mcp,并使用相同的 bearer 标头。网关会提供 search_actionsexecute_action 等发现工具,而不是为每个 API 提供一个工具,从而减少代理的工具列表。在 VPS 上运行 MCP 服务器介绍了连接配置的客户端部分。

在确认完成前,再执行一次检查。删除 authorization 标头后,重复调用操作。项目自己的快速入门会在完全不带 bearer 的情况下调用 /v1。因此,如果安装未配置运行时身份验证,任何能够访问该端口的用户都可以执行操作。如果未经过身份验证的调用成功,有两种处理方式:配置运行时令牌,并确认匿名调用现在会失败;或者在反向代理上,将 /api/v1/mcp 限制为代理来源的地址。只有 /oauth/callback 必须对公网开放,因为这是提供商的浏览器重定向所需的唯一路径。

将操作列表缩减到代理所需的范围

后端连接了上千个提供商时,直接交给语言模型的操作面会很大。可以通过两个控制项缩小范围。

OOMOL_CONNECT_ALLOWED_ACTIONS 接受以逗号分隔的允许列表,并支持 service.**OOMOL_CONNECT_BLOCKED_ACTIONS 是拒绝列表,且拒绝列表优先。将允许列表设为 github.get_current_user,github.list_issues 后,其他所有操作都会被拒绝,无论代理请求执行什么操作。这是错误与事故之间的区别。运行时令牌还会在全局规则之上使用各自的操作规则,其 allowedProxies 列表默认为空,因此在授权之前会拒绝 POST /v1/proxy/:service。该代理端点会将原始请求转发给提供商,并附加您的凭据。除非某个特定代理需要,否则请将其留空。

OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK 默认为 false。这会阻止自托管提供商连接指向私有地址,例如 169.254.169.254 上的云元数据服务,或同一网络中的数据库。请保持关闭。只有对于您自行托管的提供商,才将其开启。

备份存放所有令牌的服务器

有两项内容很重要,缺少任何一项,另一项都无法使用。connector-data 卷中的 /app/data/connect.sqlite 数据库保存着已封存的凭据。.env 中的加密密钥用于解封这些凭据。只有卷的备份而没有密钥,无法恢复任何内容;只有密钥而没有卷,同样无法恢复任何内容。因此,密钥应存放在密码管理器中,卷应纳入常规备份轮换。

复制 SQLite 文件时,请先停止容器。写入期间进行复制,恢复后可能得到损坏的数据库。

docker volume ls | grep connector-data
docker compose stop connector
docker run --rm -v open-connector_connector-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/connector-data.tgz -C /data .
docker compose start connector

卷名称由项目目录加上 _connector-data 组成,这就是保留第一条命令的原因:将实际名称粘贴到第三条命令中。使用 从 VPS 备份 restic 将归档发送到 VPS 外部。该工具会在归档离开 VPS 前对其加密,因为该归档就是凭据存储。

运行时默认将最近的操作运行记录保留为审计记录,共 5,000 条,因此控制台可以显示哪个代理在何时执行了什么操作。代理行为异常时,应首先查看此日志。还应将 Uptime Kuma 状态页面 指向 https://connect.example.com/health。网关停止响应时,代理会以难以判断的方式失败;确认网关已停止运行,可以省去一小时的代理输出排查时间。

会出现的问题及相应消息

提供商处的 redirect_uri_mismatch 源站 URL 与已注册的回调 URL 不一致。将 /api/oauth/configs 中的精确字符串与提供商的应用设置进行比较,同时检查 httpshttp 是否一致,以及是否存在尾随斜杠。

每次 /api 调用都返回 401。 管理员令牌请求头缺失或拼写错误。请求头是 Authorization: Bearer <token>,Web 控制台也要求使用同一个令牌。

容器正在运行,但凭据以明文存储。OOMOL_CONNECT_ENCRYPTION_KEY 从未传入容器时,就会发生这种情况,因为运行时会以未加密的方式存储凭据记录,而不是拒绝启动。请在自己的安装中验证这一点:连接一个提供商,使用一个你能识别的 API 密钥,然后在数据库中搜索该密钥。

docker compose cp connector:/app/data/connect.sqlite /tmp/connect.sqlite
grep -c 'github_pat_' /tmp/connect.sqlite
shred -u /tmp/connect.sqlite

计数大于 0 表示密钥未生效。因此,请检查 .envcompose.yaml 位于同一目录,并确认 docker compose config 显示了该值。设置密钥后,同样的搜索会返回 0,因为该记录已使用 AES-256-GCM 加密(高级加密标准、256 位密钥、Galois/计数器模式)。

恢复后无法解密任何内容。 加密密钥已更改或丢失。根据设计,密钥不会写在数据旁边,因此不存在恢复路径,提交支持工单也无法解决问题。请重新连接所有提供商。运行时支持通过单独的密钥变量和数据命令进行密钥轮换,因此在轮换前请先阅读当前版本说明。

代理报告的错误指出某个操作,但该操作明明出现在目录中。 发现和执行是两个独立过程。某个操作可以出现在 search_actions 中,但仍可能被 OOMOL_CONNECT_ALLOWED_ACTIONS、拒绝列表或该运行时令牌自身的规则拒绝。

升级。 备份卷,将镜像标签编辑为新版本,然后执行 docker compose pull && docker compose up -d。监控 docker compose logs -n 50 connector,查找迁移日志行;然后重新运行运行状况检查和一个实际操作,确认一切正常后再继续使用。回滚表示恢复旧标签。之所以可行,是因为你固定了标签。

FAQ

我需要公共域名来托管 Open Connector 吗?

对于使用 API key 的提供商,不需要:127.0.0.1 上的网关就足够了。对于 OAuth,实际需要公共域名。提供商会将浏览器重定向到您的回调 URL,因此该 URL 必须能从公共互联网解析,并且提供商会拒绝 localhost 之外的普通 http://。在首次启动前,将 OOMOL_CONNECT_ORIGIN 设置为您的 https:// 主机名,并在提供商的 OAuth 应用中注册 <origin>/oauth/callback

如果丢失 Open Connector 加密密钥,会发生什么?

存储的凭据将无法解密,而且无法恢复。该密钥会被特意单独存储,不会与数据放在一起,因此持有数据库的任何人都无法读取这些凭据,包括您自己。您唯一的选择是设置新密钥,然后重新连接每个提供商。请将密钥保存在密码管理器中,并将数据库纳入备份轮换,因为恢复时两者缺一不可。

我的 AI agent 能看到提供商访问令牌吗?

通过网关调用时不能。agent 使用以 oct_ 开头的运行时令牌进行身份验证,网关会在服务器上将提供商凭据注入出站请求,并且只返回响应。以下两种情况会破坏这一隔离:/v1/proxy/:service 端点会转发附带您凭据的原始请求,其授权范围之所以默认为空是有原因的;您自行将 API key 粘贴到 agent 中也会完全绕过网关。

网关应当能从公共互联网访问吗?

只有 /oauth/callback 必须能从公共互联网访问。在 127.0.0.1 上发布容器端口,使 Docker 的 NAT 规则无法越过您的防火墙暴露该端口,并将反向代理置于前端。然后在不带 authorization 标头的情况下测试一次操作调用。如果调用成功,就在代理上将 /api/v1/mcp 限制为 agent 使用的地址,直到只有经过身份验证的调用能够正常工作。

Open Connector 已经可以用于生产环境了吗?

它采用 Apache 2.0 许可证,开发进展很快:代码仓库于 29 June 2026 发布,v1.3.3 于 30 July 2026 发布,因此请将本指南中的每个版本号都视为 1 August 2026 的快照。请固定使用发布标签运行,绝不要使用 latesttip;每次升级前阅读发布说明,并保留一个曾经成功恢复过的卷备份。对于您拥有的服务器,该设计是可靠的;风险在于版本频繁变化,而不在于架构。