浏览文档

维护方

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:

  1. 打开 http://localhost:3000。
  2. 打开 provider 页面,例如 GitHub。
  3. 点击 Configure OAuth Client 或 Edit OAuth Client。
  4. 粘贴 provider app 的 Client ID 和 Client Secret。
  5. 点击 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_apps
  • search_actions
  • get_action_guide
  • execute_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 与已存储记录匹配。

开始使用

开始使用