---
author: OOMOL
author_url: https://oomol.com/zh-cn/about/
title: OOMOL OpenConnector 自部署指南
description: 自部署开源应用连接服务，保护本地凭据，配置 provider 连接，并通过 MCP 或 HTTP 把 actions 提供给 Agent。
lang: zh-CN
canonical_url: https://oomol.com/zh-cn/docs/openconnector-self-hosting/
markdown_url: https://oomol.com/zh-cn/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 与已存储记录匹配。 |
