OpenConnectorをDocker Composeでセルフホストし、架空SaaS「NebulaDock」に発行した専用APIキーを預けて、MCP経由の読み書きまで検証した。
結論から言うと、OpenConnectorは単なる「大量のAPIカタログ」ではない。資格情報をエージェントの外側に置き、接続確認、Action実行、ポリシー判定、監査ログをひとつの境界に集約できる。一方、認証不要APIを呼ぶだけでは、この価値はほとんど検証できない。実験用には、既存の個人トークンを流用するより、隔離したモックSaaSと使い捨てキーを用意する方が安全で、結果も読みやすかった。
TL;DR
- OpenConnector
v1.3.1をDocker Composeで起動した。 - 保存資格情報の暗号化、管理API認証、MCP/Action実行認証を有効にした。
- 架空のDokploy互換SaaS「NebulaDock」を別コンテナとして実装し、専用APIキーを発行した。
- 誤キーはサービス直呼びで
401、OpenConnectorへの登録で400 credential_verification_failedになった。 - 正しいキーでは、MCPから一覧取得とプロジェクト作成に成功した。OpenConnector再起動後も再接続せず読めた。
- NebulaDockだけをLAN公開し、OpenConnector管理面はlocalhost限定に保った。
- これは「APIキー保管・注入・Action実行・監査」の検証であり、任意SaaS向けネイティブProvider作成や本番運用を証明するものではない。
OpenConnectorとは
OpenConnectorは、AIエージェントと外部サービスの間に置くオープンソースのconnector gatewayだ。公式READMEではComposioの代替と位置づけられ、SDK、CLI、MCP、HTTP、OpenAPIから共通のProvider/Actionカタログを扱える。
今回使用したイメージはv1.3.1。実行時の管理画面では、次の状態を確認できた。
| 項目 | 実測値 |
|---|---|
| 利用可能なProvider | 1,125 |
| ローカル実行可能Action | 12,137 |
| 接続済みProvider | 1 |
| 直近の実行失敗 | 0 |
重要なのは件数ではない。OpenConnectorは次の責務を持つ。
- APIキーやOAuthトークンをエージェントプロセスの外側で保管する
- 接続先アカウントを安全なプロファイルとして公開する
- Actionごとの入力schemaと必要scopeを提示する
- 実行許可・拒否ポリシーを適用する
- 秘密値を除いた実行履歴を残す
MCPクライアントから見えるのは、search_actions、get_action_guide、execute_actionなど少数の発見・実行ツールだ。1万件を超えるActionを最初からLLMのtool schemaへ詰め込まず、検索してから使う設計になっている。
なぜ「認証不要APIを呼べた」だけでは足りないのか
最初の疎通確認にはHacker Newsを使った。MCPを初期化し、Actionを検索し、トップストーリー500件を取得し、先頭5件を個別に展開した。6回のMCP Actionはすべて成功した。
これは有効なtransport smoke testだ。次の項目は確認できる。
- MCPのJSON-RPCを受け付ける
- Action検索とguide取得が動く
- Provider executorが外部APIを呼べる
- 実行履歴に
caller=mcpが残る
しかし、Hacker News接続はno_authだ。次の核心部分は未検証のままだった。
- 誤キーを拒否できるか
- 正しいキーを暗号化保存できるか
- 実行時だけキーをProviderへ注入できるか
- 再起動後に資格情報を復号して使えるか
- 書き込みActionを監査できるか
そこで実アカウントのトークンを使えばよい、と考えるのも早い。広いrepoやworkflow権限を持つ個人トークンは、動作確認には強すぎる。接続できることは証明できても、失敗時の影響範囲が大きくなる。
比較のため、既存のGitHub OAuth tokenでget_current_userと非公開repositoryを含むlist_my_repositoriesが成功するところまでは確認した。しかし、これは技術的な成功であって、安全な探索手順ではない。OpenConnector内のGitHub接続は直ちに削除し、元のcredential ownerには触れないまま、以後の検証を専用キーへ切り替えた。
connector gatewayの評価では、「本物の強い鍵」より「専用の弱い鍵」を先に用意する。
この方針で、実験専用の架空SaaSを作ることにした。
架空SaaS「NebulaDock」を作る
NebulaDockは、Dokployの一部API contractだけを実装したテストダブルだ。OpenConnectorに既存のDokploy Providerがあるため、Providerを新規開発せず、APIキーの検証・注入・読み書きに焦点を絞れる。
実装したendpointは4つ。
| Method | Endpoint | 用途 |
|---|---|---|
GET |
/health |
認証不要の生存確認 |
GET |
/api/project.search |
OpenConnector接続時のキー検証 |
GET |
/api/project.all |
認証付き一覧取得 |
POST |
/api/project.create |
認証付きプロジェクト作成 |
認証はx-api-keyヘッダーで行う。比較にはtimingSafeEqualを使い、リクエスト監査にはキーそのものではなく、認証成否だけを記録した。
import { timingSafeEqual } from "node:crypto";
function keyMatches(candidate = "") {
const expected = Buffer.from(process.env.MOCK_SAAS_API_KEY);
const actual = Buffer.from(candidate);
return expected.length === actual.length &&
timingSafeEqual(expected, actual);
}
const authenticated = keyMatches(request.headers["x-api-key"]);
requestAudit.push({
method: request.method,
path: url.pathname,
authenticated,
at: new Date().toISOString(),
});
if (!authenticated) {
json(response, 401, { error: "invalid_api_key" });
return;
}
テストキーはサービス専用に生成した。
openssl rand -hex 32
この値を.envのMOCK_SAAS_API_KEYへ保存し、ファイルpermissionを600にした。記事やスクリーンショットには実値を出していない。
Docker Composeの境界
OpenConnector管理面とNebulaDockの公開範囲は分けた。
services:
connector:
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK: "true"
depends_on:
mock-saas:
condition: service_healthy
mock-saas:
build:
context: ./mock-saas
restart: unless-stopped
ports:
- "0.0.0.0:3101:8080"
environment:
MOCK_SAAS_API_KEY: ${MOCK_SAAS_API_KEY:?required}
これにより、接続経路は次のようになる。
OpenConnectorコンテナからDocker内部のNebulaDockへ到達させるため、OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK=trueを有効にした。この設定は、対応Providerがprivate networkを参照できる範囲を広げる。今回のような隔離テストでは必要だが、本番では対象Providerとネットワークを限定して判断すべきだ。
また、Provider proxyは次の設定で無効化した。
OOMOL_CONNECT_BLOCKED_PROXIES=*
Action allowlistとProvider proxyは別の制御面である。Actionを制限してもproxyが自動的に閉じるわけではない点に注意したい。
誤キーを先に試す
正しいキーを入れる前に、失敗系を固定した。
NebulaDockへ誤キーで直接アクセスすると401になった。
curl -i \
-H 'x-api-key: deliberately-wrong-key' \
http://127.0.0.1:3101/api/project.all
HTTP/1.1 401 Unauthorized
{"error":"invalid_api_key"}
同じ誤キーをOpenConnectorへ登録すると、Provider validatorが/api/project.searchを呼び、接続作成を拒否した。
HTTP 400
credential_verification_failed
invalid_api_key
この時点でDokploy接続は0件。無効な資格情報が「接続済み」として残らないことも確認した。
正しいキーをOpenConnectorへ登録する
管理APIへ正しい専用キーとDocker内部URLを送る。
curl -X PUT http://127.0.0.1:3000/api/connections/dokploy \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H 'content-type: application/json' \
-d '{
"authType": "api_key",
"values": {
"apiKey": "'"${MOCK_SAAS_API_KEY}"'",
"baseUrl": "http://mock-saas:8080"
}
}'
登録は200で成功し、OpenConnectorが返したのは安全な接続プロファイルだけだった。
{
"service": "dokploy",
"connectionName": "default",
"authType": "api_key",
"profile": {
"accountId": "dokploy:mock-saas:8080",
"displayName": "Dokploy mock-saas:8080",
"grantedScopes": []
}
}
レスポンスにAPIキーは含まれない。
MCPから読み、書き、もう一度読む
MCPのexecute_actionから、まずdokploy.project-allを実行した。初期状態では「月面農園」1件が返った。
続いてdokploy.project-createを実行した。
{
"actionId": "dokploy.project-create",
"input": {
"name": "火星気象ダッシュボード",
"description": "OpenConnector認証テストで作成した架空プロジェクト",
"env": "staging"
}
}
再びproject-allを呼ぶと、2件目として追加されていた。
| Gate | 結果 |
|---|---|
| 誤キー・サービス直呼び | 401 |
| 誤キー・OpenConnector登録 | 400 |
| 正しいキー・接続作成 | 200 |
| 認証付き一覧取得 | 成功 |
| 認証付き作成 | 成功 |
| サービス側project数 | 1 → 2 |
| コア検証の監査ログ | 4/4成功 |
| OpenConnector再起動後の再取得 | 成功 |
コア検証時のAction実行は5〜7msだった。後続のLAN/画面QAでも呼び出したため、掲載スクリーンショット上のDokploy呼び出し数は8回まで増えている。
監査ログにはactionId、接続プロファイル、入力summary、成否、所要時間が残る。一方、APIキーは含まれなかった。サービス側の監査もauthenticated: true/falseだけを記録し、キー値を保存していない。
LANとモバイル画面で確認する
NebulaDockには、APIキー入力とproject一覧を表示する小さなWeb UIも付けた。
未認証状態ではprojectを表示しない。
正しいキーを入力すると、3件の架空projectを取得できる。記事用の画像ではキーをpassword inputで伏字にし、実LAN IPも表示していない。
ブラウザQAはGoogle Chromeを390×844pxのモバイルviewportで実施した。
- 空キー:
APIキーが違います - 誤キー:
APIキーが違います - 正しいキー:
認証済み - 表示project数: 3
- password input: 伏字
- 横方向overflow: なし
- 別のLANホストからpage title取得: 成功
スマートフォンから使う場合のURLは次の形になる。
http://<MAC_LAN_IP>:3101
ここで公開したのはNebulaDockだけだ。OpenConnectorの管理画面とMCP endpointは127.0.0.1:3000のままなので、LANから直接は開けない。
再現手順
前提はDocker DesktopとDocker Composeが動くmacOSまたはLinux環境だ。
git clone https://github.com/oomol-lab/open-connector.git
cd open-connector
.envには実値を直接記事へ貼らず、ローカルで生成する。
openssl rand -hex 32 # OOMOL_CONNECT_ENCRYPTION_KEY
openssl rand -hex 32 # OOMOL_CONNECT_ADMIN_TOKEN
openssl rand -hex 32 # OOMOL_CONNECT_RUNTIME_TOKEN
openssl rand -hex 32 # MOCK_SAAS_API_KEY
chmod 600 .env
最低限のhardening項目は次の通り。
OOMOL_CONNECT_ORIGIN=http://localhost:3000
OOMOL_CONNECT_ENCRYPTION_KEY=<random-secret>
OOMOL_CONNECT_ADMIN_TOKEN=<random-secret>
OOMOL_CONNECT_RUNTIME_TOKEN=<random-secret>
OOMOL_CONNECT_ALLOWED_ACTIONS=*
OOMOL_CONNECT_BLOCKED_PROXIES=*
MOCK_SAAS_API_KEY=<disposable-test-key>
起動する。
docker compose config --quiet
docker compose up -d --build
docker compose ps
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3101/health
期待する状態は、両コンテナがhealthyで、両health endpointが{"ok":true}を返すことだ。その後、誤キー登録、正しいキー登録、MCPのread/write、監査ログ確認の順に進める。
セキュリティ上の注意
暗号化キーを失うと復号できない
OOMOL_CONNECT_ENCRYPTION_KEYはSQLite内のcredentialやOAuth設定を暗号化する。キーを失うと復元できない。DBとは別のsecret storeへ保管する必要がある。
ローカル開発用defaultのまま公開しない
OpenConnectorは開発用に認証なしでも動くが、資格情報を預けるなら管理tokenとruntime tokenを必須にしたい。Dockerイメージはコンテナ内で0.0.0.0へbindするため、host側のport公開範囲も明示する。
LAN公開はTLSではない
今回のNebulaDockは信頼できる実験LAN内のHTTP公開だ。公衆Wi-Fi、共有オフィス、インターネットへそのまま出す構成ではない。恒久運用ならTLS付きreverse proxy、Tailscale、アクセス制御を追加する。
モックSaaSのデータはメモリ上
NebulaDockのproject stateはプロセスメモリ上にある。コンテナ再作成で初期化される。今回はcredential gatewayの検証が目的で、データ永続化は対象外とした。
今回、証明できたこと/できていないこと
証明できたこと:
- 誤APIキーを接続作成前に拒否できる
- 正しいAPIキーをOpenConnectorへ保存できる
- MCP Action実行時にキーをProviderへ注入できる
- 読み書き結果がサービス側stateへ反映される
- gateway側とservice側の両方に秘密値なしの監査証跡を残せる
- OpenConnector再起動後も保存済みcredentialで再実行できる
- mock serviceだけをLANへ公開できる
まだ証明していないこと:
- 任意SaaS用のネイティブOpenConnector Provider実装
- OAuth2の認可code flowとrefresh token更新
- 複数ユーザー、tenant分離、rate limit
- TLS付きLAN/remote access
- mock serviceの永続storage
- 本番負荷と障害復旧
次にやること
次の実験は、NebulaDockをDokploy互換test doubleから独立したnative Providerへ進めることだ。
-
src/providers/nebuladockへdefinition、Action schema、executorを追加する - read/write Actionを明示的なallowlistへ絞る
- APIキーをrotation可能にする
- SQLiteまたはPostgreSQLでmock stateを永続化する
- HTTPSまたはTailscale経由でモバイル接続を再検証する
- credential deletionとrotation後の拒否まで監査する
OpenConnectorを評価するとき、最初に見るべきなのはProvider数ではない。「無効な鍵を拒否し、有効な鍵を隠し、必要な操作だけを実行し、その証拠を残せるか」だ。架空SaaSと専用キーを先に作ると、この境界を実アカウントへ触れずに検証できる。


