OOMOL OpenConnector 自部署指南
OOMOL OpenConnector 是开源、可自部署的应用连接服务。适合需要让 Agent 或内部工具调用真实外部服务,同时把 provider 凭据、权限和执行记录留在自己环境里的场景。它通过 MCP、HTTP 暴露 GitHub、Gmail、Notion、Hacker News、Ably、Abstract、A-Leads 等服务的类型化 action。
Agent 只能看到 schema、scope、执行状态和安全的账号标签;原始 provider token、action 权限和执行记录由你的部署边界管理。
它提供什么
- 一个 runtime,通过 MCP、HTTP、OpenAPI 和 Web 控制台暴露 provider actions。
- 凭据存储,支持 API key、自定义凭据、OAuth2 连接和无需认证的 provider。
- 类型化 action schema,让 Agent 在调用前先知道自己能调用什么。
- 连接身份和 scope,让用户和 Agent 都能看到 action 会以哪个账号执行。
- 临时文件中转,供需要文件 URL 的 action 使用。
- 最近运行记录,包含脱敏后的输入摘要和 provider 错误。
- provider catalog 和本地 executor;executor 只在 action 被使用时才加载。
先选择 runtime 的运行位置,再配置存储、访问控制、provider 连接,以及提供给 Agent 使用的 MCP 或 HTTP 入口。
选择部署方式
| 方式 | 适用场景 | 存储 |
|---|---|---|
| Docker Compose | 需要最快完成本地或单服务器部署。 | Docker volume 挂载到 /app/data,SQLite 位于 /app/data/connect.sqlite。 |
| 源码运行 | 你正在开发 OOMOL OpenConnector 或 provider executor。 | 默认使用本地 ./data/connect.sqlite;也可设置 OOMOL_CONNECT_DATA_DIR。 |
| Cloudflare Workers | 需要由 Cloudflare 托管 runtime state 和 metadata。 | D1 保存运行时记录,R2 保存临时中转文件。 |
准备 runtime
部署前先确定这些值:
| 值 | 用途 |
|---|---|
OOMOL_CONNECT_ADMIN_TOKEN | 当 Web 控制台、/api 或本地 API 文档可被你的 shell 之外访问时,用它保护管理面。 |
OOMOL_CONNECT_ENCRYPTION_KEY | 加密保存 provider 凭据和 OAuth client secret。请放在你的密钥管理系统中。 |
OOMOL_CONNECT_ORIGIN | 设置 OAuth callback URL 使用的公开 origin。浏览器通过 tunnel、域名或 Worker URL 访问 runtime 时需要设置。 |
| Runtime tokens | 让 Agent 和 client 调用 /v1 与 /mcp。可在 Web 控制台 Access 页面或管理 API 中创建。 |
| Action policy | 限制哪些 actions 可以通过 /v1 和 MCP 执行。使用 OOMOL_CONNECT_ALLOWED_ACTIONS 和 OOMOL_CONNECT_BLOCKED_ACTIONS。 |
Runtime 数据库需要按敏感文件处理。未设置 OOMOL_CONNECT_ENCRYPTION_KEY 时,OOMOL OpenConnector 仍可运行,但 provider secret 会存放在敏感的本地 SQLite 文件中。
使用 Docker Compose 运行
先克隆 OOMOL OpenConnector 仓库,进入项目目录,再启动 runtime:
git clone https://github.com/oomol-lab/open-connector.git
cd open-connector
docker compose up --build
打开 Web 控制台:
http://localhost:3000
打开生成的 API 文档:
http://localhost:3000/docs
用一个无需认证的 action 验证 runtime:
curl -s -X POST http://localhost:3000/v1/actions/hackernews.get_top_stories \
-H 'content-type: application/json' \
-d '{"input":{}}'
Docker Compose 会把运行时状态保存到 connector-data volume。容器内的 SQLite 路径是:
/app/data/connect.sqlite
私有服务器部署时,至少在启动前设置 admin token 和 encryption key:
请在 Bash 中运行这些示例。仅在隐藏输入提示中填写真实凭证,不要将凭证写入 shell 命令或命令日志。
(
set -eu
read -r -s -p "OOMOL_CONNECT_ADMIN_TOKEN: " OOMOL_CONNECT_ADMIN_TOKEN
echo
test -n "$OOMOL_CONNECT_ADMIN_TOKEN"
export OOMOL_CONNECT_ADMIN_TOKEN
read -r -s -p "OOMOL_CONNECT_ENCRYPTION_KEY: " OOMOL_CONNECT_ENCRYPTION_KEY
echo
test -n "$OOMOL_CONNECT_ENCRYPTION_KEY"
export OOMOL_CONNECT_ENCRYPTION_KEY
docker compose up --build
)
如果 runtime 通过公开域名或 tunnel 暴露,需要同时启用管理端与运行端鉴权,并设置公开 origin:
(
set -eu
export OOMOL_CONNECT_ORIGIN="https://connect.example.com"
read -r -s -p "OOMOL_CONNECT_ADMIN_TOKEN: " OOMOL_CONNECT_ADMIN_TOKEN
echo
test -n "$OOMOL_CONNECT_ADMIN_TOKEN"
export OOMOL_CONNECT_ADMIN_TOKEN
read -r -s -p "OOMOL_CONNECT_RUNTIME_TOKEN: " OOMOL_CONNECT_RUNTIME_TOKEN
echo
test -n "$OOMOL_CONNECT_RUNTIME_TOKEN"
export OOMOL_CONNECT_RUNTIME_TOKEN
read -r -s -p "OOMOL_CONNECT_ENCRYPTION_KEY: " OOMOL_CONNECT_ENCRYPTION_KEY
echo
test -n "$OOMOL_CONNECT_ENCRYPTION_KEY"
export OOMOL_CONNECT_ENCRYPTION_KEY
docker compose up --build
)
通过环境变量提供 runtime token 适合公开部署的首次启动。如果希望改用控制台创建的 oct_… token,应先让 runtime 保持私有,创建第一个 token 后再开放公网访问。
Docker 镜像会在容器内绑定 0.0.0.0。外部访问范围由宿主机防火墙、反向代理或容器平台控制。
保护管理面和运行面
管理端 HTTP client 调用 /api、/docs 或 Web 控制台时发送:
Authorization: Bearer replace-with-an-admin-token
在 Web 控制台 Access 页面为 Agent 和 SDK 类 client 创建 runtime token。token 只显示一次,SQLite 中只保存 hash。未配置 token 的 runtime 必须留在 localhost 或私有网络中。
也可以通过管理 API 创建:
以下带认证的请求示例需要 Python 3 和 curl 7.76.0 或更新版本,以支持这些示例使用的 —fail-with-body 选项。示例通过隐藏提示读取秘密值,再通过标准输入传给 curl,不把凭据放入命令行参数或 shell 历史记录。
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("OOMOL_CONNECT_ADMIN_TOKEN: ")
if not token:
raise SystemExit("A token is required")
payload = {'name': 'Claude Desktop'}
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
config += "data = " + json.dumps(json.dumps(payload)) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'POST', 'http://localhost:3000/api/runtime-tokens', '--header', 'content-type: application/json'],
input=config, text=True, check=True,
)
PY
之后 runtime client 调用 /v1 或 /mcp 时发送:
Authorization: Bearer oct_...
为了启动脚本和向后兼容,仍然可以使用 OOMOL_CONNECT_RUNTIME_TOKEN:
(
set -eu
read -r -s -p "OOMOL_CONNECT_ADMIN_TOKEN: " OOMOL_CONNECT_ADMIN_TOKEN
echo
test -n "$OOMOL_CONNECT_ADMIN_TOKEN"
export OOMOL_CONNECT_ADMIN_TOKEN
read -r -s -p "OOMOL_CONNECT_RUNTIME_TOKEN: " OOMOL_CONNECT_RUNTIME_TOKEN
echo
test -n "$OOMOL_CONNECT_RUNTIME_TOKEN"
export OOMOL_CONNECT_RUNTIME_TOKEN
docker compose up --build
)
限制 Agent 可以执行的 actions:
OOMOL_CONNECT_ALLOWED_ACTIONS="hackernews.*,github.get_current_user" docker compose up --build
即使较大的 allowlist 包含某些 actions,也可以单独阻止它们:
OOMOL_CONNECT_ALLOWED_ACTIONS="github.*" \
OOMOL_CONNECT_BLOCKED_ACTIONS="github.delete_repository" \
docker compose up --build
连接 API-key provider
GitHub 是一个简单的 API-key 示例,因为它可以使用 personal access token。
查看 provider 契约:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("OOMOL_CONNECT_ADMIN_TOKEN: ")
if not token:
raise SystemExit("A token is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'GET', 'http://localhost:3000/api/providers/github'],
input=config, text=True, check=True,
)
PY
保存默认 GitHub 连接:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("OOMOL_CONNECT_ADMIN_TOKEN: ")
if not token:
raise SystemExit("A token is required")
payload = {'authType': 'api_key', 'values': {'apiKey': None}}
payload["values"]["apiKey"] = getpass.getpass("GitHub API key: ")
if not payload["values"]["apiKey"]:
raise SystemExit("An API key is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
config += "data = " + json.dumps(json.dumps(payload)) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'PUT', 'http://localhost:3000/api/connections/github', '--header', 'content-type: application/json'],
input=config, text=True, check=True,
)
PY
通过 runtime 调用 GitHub:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("Runtime token: ")
if not token:
raise SystemExit("A token is required")
payload = {'input': {}}
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
config += "data = " + json.dumps(json.dumps(payload)) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'POST', 'http://localhost:3000/v1/actions/github.get_current_user', '--header', 'content-type: application/json'],
input=config, text=True, check=True,
)
PY
查看已配置的连接,以及会暴露给 Agent 的安全账号身份:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("OOMOL_CONNECT_ADMIN_TOKEN: ")
if not token:
raise SystemExit("A token is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'GET', 'http://localhost:3000/api/connections'],
input=config, text=True, check=True,
)
PY
命名连接
同一个 provider 需要多个账号时,添加 connectionName:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("OOMOL_CONNECT_ADMIN_TOKEN: ")
if not token:
raise SystemExit("A token is required")
payload = {'authType': 'api_key', 'connectionName': 'work', 'values': {'apiKey': None}}
payload["values"]["apiKey"] = getpass.getpass("GitHub API key: ")
if not payload["values"]["apiKey"]:
raise SystemExit("An API key is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
config += "data = " + json.dumps(json.dumps(payload)) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'PUT', 'http://localhost:3000/api/connections/github', '--header', 'content-type: application/json'],
input=config, text=True, check=True,
)
PY
执行时选择该账号:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("Runtime token: ")
if not token:
raise SystemExit("A token is required")
payload = {'input': {}}
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
config += "data = " + json.dumps(json.dumps(payload)) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'POST', 'http://localhost:3000/v1/actions/github.get_current_user', '--header', 'x-oo-connector-alias: work', '--header', 'content-type: application/json'],
input=config, text=True, check=True,
)
PY
也可以使用 alias query 参数。
连接 OAuth provider
OAuth provider 使用你自己的 provider OAuth app。先在 provider OAuth app 中设置 callback URL。这个 callback URL 是 OpenConnector origin 加上 /oauth/callback。
使用默认端口时,GitHub 使用这个 callback URL:
http://localhost:3000/oauth/callback
如果 runtime 通过其他 origin 暴露,请先设置 OOMOL_CONNECT_ORIGIN,再启动 runtime,然后用该 origin 拼出 callback URL:
https://connect.example.com/oauth/callback
把准确 callback URL 填入 provider OAuth app。
在 Web 控制台中保存 OAuth client:
- 打开
http://localhost:3000。 - 打开 provider 页面,例如 GitHub。
- 点击 Configure OAuth Client 或 Edit OAuth Client。
- 粘贴 provider app 的 Client ID 和 Client Secret。
- 点击 Save OAuth Client。
如果 provider 需要额外 client config 字段,请在同一个 OAuth client 表单中填写。继续配置前,请使用能展示该 provider 所需字段的 OpenConnector 控制台版本。
保存 OAuth client 后,在 provider 页面点击 Connect。在 provider 授权页批准授权。Provider 跳回 OpenConnector 后,确认 provider 页面显示账号已连接。
给 Agent 使用工具
支持 MCP 的 client 可以连接到:
http://localhost:3000/mcp
MCP server 暴露一组面向发现流程的工具:
list_appssearch_actionsget_action_guideexecute_action
预览 MCP tool metadata:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("Runtime token: ")
if not token:
raise SystemExit("A token is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'GET', 'http://localhost:3000/mcp/tools'],
input=config, text=True, check=True,
)
PY
HTTP client 使用 /v1 runtime API:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("Runtime token: ")
if not token:
raise SystemExit("A token is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'GET', 'http://localhost:3000/v1/actions'],
input=config, text=True, check=True,
)
PY
每个 action 都有一份本地 Markdown guide,包含输入 schema、scope、provider 权限、当前连接身份和请求示例:
python3 - <<'PY'
import getpass
import json
import subprocess
import warnings
warnings.simplefilter("error", getpass.GetPassWarning)
token = getpass.getpass("OOMOL_CONNECT_ADMIN_TOKEN: ")
if not token:
raise SystemExit("A token is required")
config = "header = " + json.dumps("authorization: Bearer " + token) + "\n"
subprocess.run(
['curl', '--disable', '--silent', '--show-error', '--fail-with-body', '--config', '-', '--request', 'GET', 'http://localhost:3000/api/actions/github.get_current_user/agent.md'],
input=config, text=True, check=True,
)
PY
Web 控制台也可以为每个 action 复制 cURL、TypeScript 和 agent prompt 示例。
从源码运行
开发 OOMOL OpenConnector 或 provider executor 时使用源码工作流。请使用 Node.js 22 或更新版本。
git clone https://github.com/oomol-lab/open-connector.git
cd open-connector
npm install
npm run build:web
npm run dev
npm install 和 npm run dev 会在生成文件缺失或过期时创建本地文件。
源码运行时,运行时状态保存在:
./data/connect.sqlite
使用其他数据目录:
OOMOL_CONNECT_DATA_DIR=/path/to/data npm run dev
Admin token、encryption key、origin、runtime token 和 action policy 这些环境变量与前面相同。
部署到 Cloudflare Workers
Cloudflare Workers 支持作为 metadata 和 runtime state 的部署目标。
先克隆仓库,再创建 Cloudflare 资源并部署:
git clone https://github.com/oomol-lab/open-connector.git
cd open-connector
cp wrangler.example.jsonc wrangler.local.jsonc
npm install
npm run generate:catalog
npm run build:web
npx wrangler d1 create oomol-connect
npx wrangler r2 bucket create oomol-connect-transit-files
部署前,把 Cloudflare 返回的 D1 database_id 填入被忽略的 wrangler.local.jsonc。
首次设置期间保持 Worker 私有:在本地 Wrangler 配置中将 workers_dev 和 preview_urls 设为 false,不配置公开路由或自定义域名。设置完下面的三个 secret 前,不要开放公网访问。
npx wrangler d1 migrations apply oomol-connect --remote --config wrangler.local.jsonc
npm run deploy:cloudflare
使用 Wrangler 设置 secrets:
npx wrangler secret put OOMOL_CONNECT_ADMIN_TOKEN --config wrangler.local.jsonc
npx wrangler secret put OOMOL_CONNECT_RUNTIME_TOKEN --config wrangler.local.jsonc
npx wrangler secret put OOMOL_CONNECT_ENCRYPTION_KEY --config wrangler.local.jsonc
设置完三个 secret 后,在本地配置中启用所需的公开路由并重新部署。
在 wrangler.local.jsonc 中把 OOMOL_CONNECT_ORIGIN 设为公开 Worker origin。Admin token 保护控制台与 /api,runtime token 从第一次公开请求开始保护 /v1 和 /mcp。
Cloudflare 使用相同的环境变量名配置 origin、auth tokens、action policy、中转文件限制和凭据加密。PORT、HOST 和 OOMOL_CONNECT_DATA_DIR 只适用于本地 Node runtime。
Worker runtime 会提供 catalog metadata、/api 和 /v1 metadata endpoints、连接、runtime tokens、OAuth config/state、基于 R2 的中转文件,以及生成版 provider action executor registry。如果希望自动清理未读取的过期中转文件,请为 transit bucket 配置 R2 lifecycle rule。
运维部署
为支持和恢复保留这些记录:
| 记录 | 查看位置 |
|---|---|
| Runtime 数据库 | Docker volume、本地 OOMOL_CONNECT_DATA_DIR 或 Cloudflare D1。 |
| 临时中转文件 | 本地 runtime 的 OOMOL_CONNECT_DATA_DIR/files,或 Cloudflare R2。 |
| Admin token 和 encryption key | 你的密钥管理系统。OOMOL OpenConnector 不会替你保存 encryption key。 |
| Runtime token 前缀 | Web 控制台 Access 页面或 /api/runtime-tokens。完整 runtime token 只显示一次。 |
| 执行历史 | Web 控制台最近运行记录,或 GET /api/runs。 |
对于文件上传类 action,本地中转文件保存在 OOMOL_CONNECT_DATA_DIR/files 并按时间清理。使用 OOMOL_CONNECT_TRANSIT_FILE_TTL_SECONDS 和 OOMOL_CONNECT_TRANSIT_FILE_MAX_BYTES 调整生命周期和上传大小。
排查建议
| 现象 | 检查项 |
|---|---|
Web 控制台或 /api 返回 unauthorized。 | 发送 Authorization: Bearer <admin-token>,并确认 OOMOL_CONNECT_ADMIN_TOKEN 与当前运行环境一致。 |
/v1 或 /mcp 返回 unauthorized。 | 使用 Access 页面或 POST /api/runtime-tokens 创建的 runtime token。Admin token 用于管理面,不用于 runtime client。 |
| OAuth 回调到了错误主机。 | 将 OOMOL_CONNECT_ORIGIN 设为用户在浏览器中打开的 origin,重启 runtime,然后在 provider app 中使用 <openconnector-origin>/oauth/callback。 |
| Action 找不到凭据。 | 查看 /api/connections、当前 x-oo-connector-alias,以及连接是否仍然 available。 |
| Action 被阻止。 | 检查 OOMOL_CONNECT_ALLOWED_ACTIONS 和 OOMOL_CONNECT_BLOCKED_ACTIONS。Blocked actions 优先于更宽泛的 allowlist。 |
| Provider 凭据之前可用,现在失败。 | 如果 token 已过期且没有 refresh token,请重新连接 provider;同时确认 encryption key 与已存储记录匹配。 |