はじめに
Bobは .bob/mcp.json に数行書くだけでMCPサーバーを追加できます。手軽さは大きな利点ですが、開発者が各自の判断で外部MCPサーバーへ接続を増やしていけるということでもあります。
- 誰がどのMCPサーバーに繋いでいるか、組織として把握できているか
- 接続先ごとにトークンやAPIキーがどう管理されているか
- 「この tool は使わせたくない」を後から止められるか
- IDEだけでなく、CI/CDなどヘッドレスで動くBob Shellも同じ統制下に置けるか
接続が増えるほど、これらは「シャドーIT」に近い問題になっていきます。
そこで今回は、mcp-context-forge(以下ContextForge)を Bob の前段に置き、Bobからは常に1つのgatewayにしか繋がせず、実際の接続先はContextForge配下で一元管理するという構成を検証しました。
検証結果としてBob IDE / Bob Shell の両方からContextForge経由でtool discoveryとtool callに成功し、さらにContextForge側でtoolを無効化すると、Bob Shellは次回実行時点でそれを認識するところまで確認できました。
検証したこと
ガバナンスの観点から確認したかったのは4点です。
- 接続の一元化: Bob(IDE・Shell問わず)からはContextForgeを1つのremote MCP serverとして登録し、背後の複数toolをまとめて見せられるか
- アクセス制御: token無し/不正tokenのアクセスを拒否できるか
- tool単位の統制: ContextForge側でtoolを無効化したとき、Bob側に反映されるか
- 可観測性・監査: 誰が・いつ・どのtoolを呼んだかを、後から追跡・確認できるか
仕様整理
Bobからはremote MCP server に対して type: "streamable-http" と url を設定できます。
ContextForge側のコード・docs・テストスクリプトより、Bobからendpointを特定しました。
http://localhost:4444/servers/<SERVER_ID>/mcp/
本記事中の
http://localhost:4444/servers/<SERVER_ID>/mcp/は、今回ローカルのdocker compose環境でContextForgeを構築したことによるものです。localhost:4444の部分(ホスト名・ポート・スキーム)は環境依存で、クラウドや本番相当の環境に構築すれば実際のホスト名やhttps://に置き換わります。
検証環境
- Bob IDE 2.0.3
- Bob Shell 2.0.1
- Rancher Desktop
- ContextForge 1.0.7
- PostgreSQL / Redis / PgBouncerを含むcompose構成
- ContextForgeのtesting profileに含まれる
fast_test_server
compose起動ではlocal環境向けに次の調整が必要でした。
-
gatewayのreplicasを1に変更 -
gatewayのCPU制限をローカル向けに削減 -
GUNICORN_WORKERSを24 -> 4に変更
この調整を入れないと、Rancher Desktop上ではgatewayが不安定でした。
ContextForge側で確認できたこと
ContextForge compose起動後、fast_test_server と register_fast_test を使って、次の状態まで持っていきました。
- gateway slug:
fast-test - tool count:
8 - virtual server name:
Fast Test Server
統制ポイント1: Bob IDEでのtool discovery / tool call
Bob IDE上で contextforge-fast-test のtool列挙を依頼したところ、8つのtoolが返ってきました。
fast-test-convert-timefast-test-echofast-test-flakyfast-test-get-statsfast-test-get-system-timefast-test-schema-errorfast-test-schema-successfast-test-verify-protocol
さらに fast-test-echo に hello from bob を送信したところ、hello from bob がそのままエコーバックされました。
統制ポイント2: Bob Shellでのtool discovery / tool call
同じ構成に対して、Bob Shellのヘッドレスモード(bob run)からも検証しました。
bob run "contextforge-fast-test というMCPサーバーで利用可能なtoolを列挙してください。\
次に fast-test-echo というtoolに 'hello from bob shell' というメッセージを渡して呼び出し、\
返ってきた結果をそのまま出力してください。" \
--max-turns 5 --max-cost 1 --format json --accept-license --trust
結果、IDEと同様に8toolをdiscoveryし、fast-test-echo の呼び出しでは hello from bob shell がそのままエコーバックされました。
ContextForge gateway側のログでも、実際のセッション確立とtool呼び出しを確認できました。
mcpgateway.services.tool_service - INFO - Invoking tool: fast-test-echo with arguments: dict_keys(['message']) ...
mcpgateway.services.structured_logger - INFO - [tool_service] Tool 'fast-test-echo' invoked successfully
IDE経由でもShell(ヘッドレス)経由でも、同じgatewayを通した接続の一元化は機能しています。
統制ポイント3: アクセス制御の検証
token無し/不正tokenのアクセスは弾かれるか
/version に対してAuthorizationヘッダー無しとダミートークンで curl したところ、どちらも 401 Unauthorized でした。
HTTP/1.1 401 Unauthorized
www-authenticate: Bearer
{"detail":"Authentication required. Please login with email/password or use basic auth."}
正しいtokenを付けると、同じendpointは 200 でContextForge 1.0.7の情報を返しました。少なくとも今回の環境では、Bearer tokenを外した匿名アクセスは通りません。
tool無効化はBob側に反映されるか
まず POST /tools/{tool_id}/state?activate=false で fast-test-echo を無効化しました。
-
fast-test-echoの個別取得ではenabledが false相当になる - virtual serverの
/servers/{server_id}/toolsからはfast-test-echoが一覧から消える
この状態で、Bob Shellに対して次のように依頼しました。
bob run "contextforge-fast-test というMCPサーバーで、fast-test-echo というtoolに \
'still there?' というメッセージを渡して呼び出してください。\
toolが見つからない場合は、その旨とエラー内容をそのまま報告してください。"
以下の結果が返ってきました。tool_calls: 0 なので、呼び出しを試みる前に「存在しない」と判断しています。
fast-test-echoというツールを呼び出そうとしましたが、このツールはcontextforge-fast-testMCPサーバーに存在しません。
サーバーに登録されているツールは以下の7つのみです:(fast-test-echoを除く7tool)
Bob Shellは bob run 実行のたびにMCPサーバーへ再接続してtool一覧を取得し直すため、ContextForge側でのtool無効化は次回実行時点で反映されます。無効化前後の8tool→7toolという差分も、APIレスポンスと一致していました。
その後、activate=true で再度有効化し、8 tool構成にも戻せました。
統制ポイント4: ログ・監査証跡の確認と管理
統制の話をするなら、「誰が・いつ・どのtoolを呼んだか」が後から追えないと意味がありません。
生ログでの確認
docker compose logs gateway を見ると、実際のセッション確立からtool呼び出しまでが記録されていました。
mcpgateway.services.tool_service - INFO - Invoking tool: fast-test-echo with arguments: dict_keys(['message']) and headers: dict_keys([..., 'authorization', ...]), server_id=b8e3f1a2c4d5e6f7a1b2c3d4e5f6a7b8
mcpgateway.services.structured_logger - INFO - [tool_service] Tool 'fast-test-echo' invoked successfully
生ログレベルでは、どのtoolが・どのvirtual serverに対して・いつ呼び出されたかを追えます。
管理者向けログ検索・監査APIの存在
コード上には、docker compose logs を都度tailする以外の手段として、管理者向けのログ検索・監査APIが用意されていました(log_search.py、logs:read 権限で保護されたRBAC対応API)。
-
POST /api/logs/search— 構造化ログの全文検索・フィルタ(component、level、時間範囲、エラー有無など) -
GET /api/logs/audit-trails— CRUD操作などの監査証跡 -
GET /api/logs/security-events— 認証失敗などのセキュリティイベント -
GET /api/logs/performance-metrics— パフォーマンス統計 -
GET /api/logs/trace/{correlation_id}— 相関IDで一連の処理を横断的に追跡
gatewayを1箇所に集約すれば、監査ログも1箇所に集まる設計になっています。
ログ保管の設定について
実際に fast-test-echo の呼び出し後、/api/logs/search(search_text: "fast-test-echo")や /api/logs/audit-trails を叩いたところ、いずれも空の結果が返りました。
原因はconfig.py にある structured_logging_database_enabled がデフォルトで false でした。
structured_logging_database_enabled: bool = Field(default=False, description="Persist structured logs to database (enables /api/logs/* endpoints, impacts performance)")
/api/logs/* 系のAPIはコード上存在していても、STRUCTURED_LOGGING_DATABASE_ENABLED=true を明示的に設定しない限りDBへの永続化が行われず、検索も監査もできません。今回のcomposeではこの設定を入れていなかったので、「APIは叩けるが結果は空」という状態でした。
DB永続化はパフォーマンスに影響します。本番導入時は、監査要件のためにこの設定を有効化するか、有効化するならパフォーマンス影響をどう吸収するかを先に決めておく必要があるようです。
まとめ
今回の検証のまとめです。
- Bob IDE / Bob Shellの両方から、ContextForgeを1つのremote MCP serverとして登録し、tool discoveryとtool callに成功した
-
.bob/mcp.jsonはIDE/Shellで共有されるため、プロジェクト単位で1つの接続設定を管理すればよい - Authorizationなし/不正tokenは401で拒否された
- ContextForge側でのtool無効化は、Bob Shellの次回実行時点で反映され、無効化されたtoolを呼び出そうとすらしなかった
- 管理者向けのログ検索・監査API(
/api/logs/*)はコード上存在するが、デフォルトでは無効。有効化にはSTRUCTURED_LOGGING_DATABASE_ENABLED=trueの明示設定が必要
BobからはContextForgeを1つのremote MCP serverとして登録し、実際の複数MCP serverはContextForge配下で一元管理できました。IDE経由・Shell経由のどちらでも、discovery一覧の一元化とtool単位の無効化が動作しました。少なくとも検証レベルでは、開発者が個別にMCP接続を増やしていく状況に対する統制手段として使えそうです。
