如何在VPS上自行托管 Open Connector
在自己的VPS运行 Open Connector身份验证网关:固定容器镜像、配置TLS源站与OAuth回调,并备份SQLite状态,让AI agent不再持有SaaS令牌。
AI agent 的 Open Connector 作用
自行托管 Open Connector,可在 AI agent 与其调用的所有软件即服务(SaaS)API 之间设置一个统一的身份验证网关,因此 agent 无需持有任何提供商令牌。它是 OOMOL Lab 开源的网关,采用 Apache 2.0 许可证。它以单个容器运行,将状态保存在一个 SQLite 文件中,并通过 HTTP 和 MCP(模型上下文协议)提供提供商操作。
从第二个集成开始,问题就会出现。每个提供商都有自己的 OAuth(开放授权)流程、刷新令牌有效期和权限范围名称。手动将 5 个提供商接入 agent,意味着需要编写 5 个重定向处理程序、5 个凭据存储和 5 个刷新循环,并且必须在令牌过期前运行这些循环。几乎没有人会编写这类代码。他们通常为每项服务生成一个长期有效的个人访问令牌,再将其粘贴到 agent 配置、环境文件,甚至提示词中。随后,agent 运行的每个工具都可以读取该令牌;令牌还会进入对话记录。这正是让机密信息离开 AI agent所描述的安全问题。
身份验证网关将凭据分为两部分。网关存储提供商凭据并运行 OAuth 流程。agent 获取的运行时令牌只能用于访问网关。当 agent 调用某项操作时,网关会加载已存储的凭据,在服务器端将其注入出站请求,并且只返回响应正文。agent 永远不会收到提供商访问令牌。因此,即使 agent 对话记录泄露,损失的也只是一个可撤销的运行时令牌,而不是整个 GitHub 账户。
该目录宣称支持超过 1,000 个提供商和 10,000 个预构建操作。这是项目方提供的数据,无法从外部验证。可以验证的是其结构:每个操作对应一个 HTTP 端点,每个提供商对应一个已存储连接,每个 agent 对应一个令牌。如果 agent 端的概念仍然陌生,tool call 或 MCP server 等术语也尚未明确,那么从零开始学习 AI agent 的方法提供了循序渐进的路径,帮助您逐步理解循环、工具以及这类网关默认您已经掌握的安全实践。
为何自行托管 Open Connector,而不是使用托管连接器服务
托管连接器服务执行相同的工作,并保存您连接的每个提供商的 refresh token。Google 或 GitHub 的 refresh token 是访问您的邮件和代码仓库的长期凭据,通常不会因密码更改而失效。它们一旦遭到入侵,您的系统也会随之受影响。自行托管后,这些记录会存储在您租用并管理的计算机上的 SQLite 中,并由始终留在本机上的密钥加密。
在开始前,先明确这项成本。这个 VPS 会成为您运行的最重要服务器。它会在一个文件中保存十几个服务的有效凭据,因此应按密码管理器主机的标准进行管理:防火墙只开放 443,不使用共享登录凭据,准备一份您确实恢复过一次的备份,并在服务停止响应时发出告警。如果您不会把密码库放在这台主机上,也不要把连接器放在上面。
安装前固定版本
Open Connector 尚处于早期阶段。该仓库首次出现于 29 June 2026;截至 1 August 2026,最新的带标签版本是 v1.3.3,于 30 July 2026 发布,并且同样带有 latest 标签。镜像仓库还发布了 tip 标签,该标签基于 main 上的最新提交构建。
对于这样新建的项目,浮动标签经常变化。一个跨越两个版本的 docker compose pull 可能会更改代理所依赖的端点,导致您花费整个晚上排查代理问题。请将镜像固定到发布标签,并在阅读发行说明后,按计划升级。
在自己的 VPS 上通过 TLS 部署 Open Connector
在启动容器前,您需要:
- Ubuntu 24.04 或相近版本系统,以及带 Compose 插件的 Docker
- 一个 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 后,端口只发布到 loopback 接口,反向代理会从同一台主机连接。
:? 会将每个变量标记为必需变量。因此缺少 .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,说明端口映射仍是上游配置,网关正直接响应整个互联网的请求。健康检查返回 connection refused,表示容器尚未监听端口;请先读取日志,再处理代理配置。
同一服务的 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://。这就是此部署需要主机名和证书的全部原因。请在首次启动前设置该来源,因为该值会在启动时读取:编辑 .env 或 compose.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_actions 和 execute_action 等发现工具,而不是为每个 API 提供一个工具,从而减少代理的工具列表。在 VPS 上运行 MCP 服务器介绍了客户端配置部分。
在确认完成前,再执行一次检查。删除 authorization 标头后,重复调用该操作。项目自身的快速入门指南在不提供 bearer 的情况下调用 /v1,因此,如果未配置运行时身份验证,任何能访问该端口的人都可以执行操作。如果未进行身份验证的调用成功,有两种处理方式:配置运行时令牌,并确认匿名调用现在会失败;或者在反向代理中限制 /api、/v1 和 /mcp,只允许代理所在的地址访问。只有 /oauth/callback 必须对公网开放,因为提供商的浏览器重定向只需要这条路径。
缩减操作列表,仅保留代理实际需要的操作
在网关后接入 1000 个提供商,会将很大的操作面交给语言模型。模型一旦开始读取它没有生成的文本,操作面会进一步扩大。例如,响应代理 Web 搜索的自建 SearXNG 实例返回的页面,可能包含针对代理可执行操作的指令。同样,让编码代理采用可行的最小改动这一原则,也适用于权限配置:只授予任务实际需要的少数操作,不要授予其他权限。两个控制项可以缩小操作面。
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 外部。restic 会在归档离开 VPS 前对其加密,因为该归档就是凭据存储。
运行时会将最近的操作运行记录保存为审计记录,默认保存 5,000 条。因此,控制台可以显示哪个代理在何时执行了什么操作。代理行为异常时,应首先查看这些日志。同时将 Uptime Kuma 状态页面 指向 https://connect.example.com/health。网关停止响应时,代理会以难以判断的方式失败;确认网关已停止运行,可以避免花费一小时阅读代理输出。
故障现象及对应消息
redirect_uri_mismatch(提供商端)。源地址与已注册的回调 URL 不一致。将 /api/oauth/configs 中的完整字符串与提供商的应用设置进行比较,包括 https 与 http 之间的内容以及末尾斜杠。
每次 /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 表示密钥未生效,因此请确认 .env 与 compose.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 代理能看到提供商访问令牌吗?
通过网关调用时不能。代理使用以 oct_ 开头的运行时令牌进行身份验证,网关在服务器上将提供商凭据注入出站请求,并且只返回响应。以下两种情况会破坏这一隔离:/v1/proxy/:service 端点会转发附带您凭据的原始请求,其授权范围默认为空是有原因的;您自行将 API key 粘贴到代理中,则会完全绕过网关。
网关是否应该可以从公网访问?
只有 /oauth/callback 需要从公网访问。将容器端口发布到 127.0.0.1,这样 Docker 的 NAT 规则就无法让流量穿过防火墙暴露该端口,并在前面部署反向代理。然后在不带 authorization 标头的情况下测试一次操作调用。如果调用成功,就在代理上将 /api、/v1 和 /mcp 限制为代理使用的地址,直到只有经过身份验证的调用能够正常工作。
Open Connector 是否已经可以用于生产环境?
它采用 Apache 2.0 许可证,开发迭代很快:代码仓库于 29 June 2026 发布,v1.3.3 于 30 July 2026 发布,因此请将本指南中的每个版本号都视为 1 August 2026 时的快照。请固定使用 release tag 运行,绝不要使用 latest 或 tip;每次升级前阅读 release notes,并保留一个已经实际恢复过的卷备份。对于您拥有的服务器,其设计是可靠的;真正的风险在于版本频繁变化,而不是架构本身。