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 與已儲存記錄相符。 |
Wanta