---
title: OOMOL OpenConnector 自部署指南
description: 自部署開源應用連接服務，保護本地憑據，設定 provider 連接，並透過 MCP 或 HTTP 將 actions 提供給 Agent。
lang: zh-TW
canonical_url: https://oomol.com/zh-tw/docs/openconnector-self-hosting/
markdown_url: https://oomol.com/zh-tw/docs/openconnector-self-hosting.md
---

# OOMOL OpenConnector 自部署指南

[OOMOL OpenConnector](https://github.com/oomol-lab/open-connector/blob/main/README.zh-CN.md) 是開源、可自部署的應用連接服務。適合需要讓 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：

```bash
git clone https://github.com/oomol-lab/open-connector.git
cd open-connector
docker compose up --build
```

開啟 Web 控制台：

```text
http://localhost:3000
```

開啟生成的 API 文件：

```text
http://localhost:3000/docs
```

用一個無需認證的 action 驗證 runtime：

```bash
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 路徑是：

```text
/app/data/connect.sqlite
```

私有伺服器部署時，至少在啟動前設定 admin token 和 encryption key：

請在 Bash 中執行這些範例。僅在隱藏輸入提示中填寫真實憑證，請勿將憑證寫入 shell 命令或命令日誌。

```bash
(
  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：

```bash
(
  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 控制台時發送：

```text
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 或更新版本](https://curl.se/docs/manpage.html#--fail-with-body)，以支援這些範例使用的 --fail-with-body 選項。範例透過隱藏提示讀取機密值，再透過標準輸入傳給 curl，不把憑據放入命令列參數或 shell 歷史紀錄。

```bash
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` 時發送：

```text
Authorization: Bearer oct_...
```

為了啟動腳本和向後相容，仍然可以使用 `OOMOL_CONNECT_RUNTIME_TOKEN`：

```bash
(
  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：

```bash
OOMOL_CONNECT_ALLOWED_ACTIONS="hackernews.*,github.get_current_user" docker compose up --build
```

即使較大的 allowlist 包含某些 actions，也可以單獨封鎖它們：

```bash
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 契約：

```bash
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 連接：

```bash
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：

```bash
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 的安全帳號身分：

```bash
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`：

```bash
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
```

執行時選擇該帳號：

```bash
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：

```text
http://localhost:3000/oauth/callback
```

如果 runtime 透過其他 origin 暴露，請先設定 `OOMOL_CONNECT_ORIGIN`，再啟動 runtime，然後用該 origin 拼出 callback URL：

```text
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 可以連接到：

```text
http://localhost:3000/mcp
```

MCP server 暴露一組面向探索流程的工具：

- `list_apps`
- `search_actions`
- `get_action_guide`
- `execute_action`

預覽 MCP tool metadata：

```bash
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：

```bash
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 權限、目前連接身分和請求範例：

```bash
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 或更新版本。

```bash
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` 會在生成檔案缺失或過期時建立本地檔案。

原始碼執行時，執行時狀態儲存在：

```text
./data/connect.sqlite
```

使用其他資料目錄：

```bash
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 資源並部署：

```bash
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 前，請勿開放公網存取。

```bash
npx wrangler d1 migrations apply oomol-connect --remote --config wrangler.local.jsonc
npm run deploy:cloudflare
```

使用 Wrangler 設定 secrets：

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