Keeper Secrets Manager について
keeper-mcpは、Keeper公式Secrets Manager MCPサーバーをstdioで接続し、テナントごとの認証情報を扱うマルチテナント向けStreamable HTTPブリッジです。v1では読み取り専用ツールのみを公開し、リクエスト検証とツール・引数の許可リストでアクセスを制限します。
主な機能
- 01stdioのMCP子プロセスをHTTPで中継
- 02認証情報ごとに子プロセスを遅延起動・分離
- 03読み取り専用の9ツールのみ公開
- 04ゲートウェイ署名とKeeper設定の検証
- 0515分間アイドルの子プロセスを終了
活用シーン
- 01テナントごとのKeeper認証情報をゲートウェイ経由で転送
- 02HTTP MCPクライアントからKeeper Secrets Managerへ接続
- 03ツールと引数の許可リストで秘密管理操作を制限
MCP 対応
MCPブリッジとして、テナントごとにKeeper公式のstdio MCPサーバーを起動し、ツールをStreamable HTTPで中継します。読み取り専用の許可リストを公開し、ゲートウェイとテナントの認証情報を検証します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm -e PORT -e KSM_MCP_BIN -e CHILD_HOME -e IDLE_EVICT_MS -e SPAWN_TIMEOUT_MS ghcr.io/wyre-technology/keeper-mcp:v1.0.0PORTPublic listen port for the bridge (POST /mcp, GET /health)KSM_MCP_BINPath to the upstream ksm-mcp binary the bridge spawns per tenantCHILD_HOMEWritable HOME for upstream child processesIDLE_EVICT_MSIdle tenant timeout before an upstream child is evicted (ms)SPAWN_TIMEOUT_MSHow long to wait for a spawned child to answer the MCP handshake (ms)ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/wyre-technology/keeper-mcp:v1.0.0stdioツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
keeper-mcp
Keeper-Security/keeper-mcp-golang-docker 上に構築されたマルチテナント対応の Streamable HTTP ブリッジです。Keeper 公式の Secrets Manager MCP サーバーを使用し、WYRE conduit ゲートウェイがテナントごとの Keeper 認証情報を HTTP ヘッダーとして転送できるようにします。
アップストリームの固定バージョン:
v2.5.0(MIT)。ソースからビルドします。アップストリームの固定バージョンを更新するを参照してください。v1 は読み取り専用です。v1 で読み取り専用とする理由を参照してください。このベンダーにとって、これはスコープの決定ではなく、セキュリティ境界です。
理由
Keeper のサーバーは stdio 専用です。ソースのどこにも HTTP や SSE のリスナーはありません。独自の Dockerfile にある EXPOSE 8080 は形骸化しており、公開イメージはシェルのない FROM scratch であるため、docker-entrypoint.sh に到達することもありません。認証情報はプロセス起動時に KSM_CONFIG_BASE64 から読み取るため、1 プロセスにつき 1 テナントとなります。
当社のゲートウェイはマルチテナントです。すべてのリクエストには呼び出し元の組織の認証情報が HTTP ヘッダーとして含まれ、ベンダーのコンテナーはそのヘッダーをアップストリームが理解できる形式に変換する必要があります。
アップストリームにはプロキシ先となる HTTP モードがないため、このブリッジはテナントごとの子プロセスに対して stdio 経由で MCP クライアントセッションを保持し、Streamable HTTP として再提供します。
:8080で待ち受け、POST /mcpとGET /healthを提供します。CONDUIT_S2S_SECRETが実際のシークレットでない場合は起動を拒否します。- すべての
/mcpリクエストで、まずX-Gateway-S2S、次にX-Keeper-Config-Base64を検証して 401 を返します。デコードして使用可能な KSM 設定になることも検証します。認証情報がない場合や無効な場合に、環境変数の認証情報へフォールバックすることはありません。そうするとテナント間の情報漏えいが起きるためです。このブリッジはゲートウェイモード専用です。 - 認証情報ごと(ハッシュをキーとして)に
ksm-mcp serve --batchの子プロセスを必要に応じて起動し、そのテナントのKSM_CONFIG_BASE64を設定して、stdio 経由で MCP クライアントを接続します。 - v2 SDK の
createMcpHandler(factory, { legacy: 'stateless' })を使用し、/mcpで両方のプロトコル世代を提供します。2025 年方式のinitializeハンドシェイクを使うクライアント(現在の conduit ゲートウェイなど)と、最新の 2026-07-28 エンベロープ方式のクライアントに対応します。 - 双方向の読み取り専用許可リストによって、子プロセスが提供する機能を制限します。
- アイドル状態が 15 分続いた子プロセスを退避させます(
IDLE_EVICT_MS)。
ツール名は変更せずにそのまま引き継がれます。
v1 で読み取り専用とする理由 — 意図的であり、基盤となる重要な要件
境界 1(最も強固なもの):読み取り専用の KSM アプリケーション
**編集不可の共有設定で Keeper アプリケーションをプロビジョニングしてください。**KSM アプリケーションの共有フォルダーへのアクセス許可は、明示的に編集可能にしない限り、読み取り専用です(secrets-manager share add --app <APP> --secret <FOLDER_UID>。Vault の Application Access パネルには Read-Only と Editable が表示されます)。編集不可のアクセス許可であれば、このブリッジ、許可リスト、バッチモード、プロンプトインジェクションに関係なく、書き込みはすべて Keeper 自身のサーバーで拒否されます。
これは認証情報の契約におけるプロビジョニングの前提条件であり、単なる推奨事項ではありません。このブリッジではこれを検証できません。Keeper SDK はレコードごとに IsEditable を保持しますが、ksm-mcp はそれを読み取らないため、アクセス許可が過剰なアプリケーションをここで検出する手段はありません。アプリケーションの対象範囲はユースケースに必要な最小限のフォルダーに限定し、読み取り専用にしてください。これを実際の制御として扱ってください。
境界 2:このブリッジの許可リスト
Keeper のアップストリームには読み取り専用モードがありません。v2.5.0 の固定バージョンで確認済みです。serve のフラグは 6 つで、いずれも公開範囲を制限しません。getAvailableTools() は設定を参照せず、ハードコードされた 19 要素のスライスを返します。また、Confirmation.DefaultDeny はハードコードで false に設定されており、それを設定するための仕組みもありません。アップストリームではツールの登録を解除できません。
バッチモードについて。 Keeper 独自の Docker ドキュメントでは KSM_MCP_BATCH_MODE=true の設定が案内されており、このブリッジでも設定しています。実際に変わる動作は次のとおりです。
| バッチモード | 動作 |
|---|---|
| オフ | get_field と unmask はハードエラーになります(アップストリームにある単一の Confirmer 呼び出し箇所です)。確認が必要なほかの 8 つのツールは、ksm_confirm_action プロンプトを示す {status: "confirmation_required"} を正常な結果として返し、2 段階での完了を想定しています。 |
| オン | delete_secret を含め、すべての確認が自動承認されます。 |
このブリッジの構造上、2 段階のフローには到達できないため、オフにしてもここで確認の境界を設けることにはなりません。このフローは MCP の プロンプト によって駆動しますが、ブリッジは prompts 機能を宣言していません。また、フローを完了するツール ksm_execute_confirmed_action は後述のとおりブロックされています。つまり、「オフ」にすると、このブリッジのクライアントでは満たせないスタブが返るだけで、安全性は高まりません。そこでオンに固定し、代わりに src/tools.ts で境界を設けています。
読み取り専用の許可では得られず、この許可リストでのみ実現できること: get_all_secrets_unmasked と ksm_execute_confirmed_action のブロックです。どちらも読み取り、あるいはそれ以上の操作ですが、読み取り専用の許可では問題なく許可されてしまいます。
提供するツール(9)
| ツール | Conduit のティア | 備考 |
|---|---|---|
list_secrets | read | レコードのメタデータのみ |
list_folders | read | フォルダーのメタデータ |
health_check | read | |
get_server_version | read | |
search_secrets | admin | メタデータ形式の結果を返しますが、ノートと login/url/hostname/address の値を対象に検索します。秘密情報に対する確認オラクルになります(CWE-200) |
get_secret | admin | レコードを返します。unmask が反映されます |
get_field | admin | KSM 表記のクエリ。最も範囲の狭い読み取りです |
get_totp_code | admin | 有効な第 2 要素 |
generate_password | admin | ローカルで生成します。レコードを作成する引数は削除されています |
Conduit は認証情報の読み取りを admin に分類します(src/access/tool-classification.ts)。これは write より上位です。Keeper の読み取りは、定義上、認証情報の読み取りに該当します。
ブロックするツール(10)
create_secret、update_secret、delete_secret、create_folder、delete_folder、
upload_file、ksm_execute_confirmed_action、get_all_secrets_unmasked、
get_record_type_schema、download_file
説明が必要なものは 4 つあります。
ksm_execute_confirmed_action— ほかのツールと同列ではなく、要となるツールです。original_tool_nameと、呼び出し元が指定するuser_decisionのブール値を受け取りますが、nonce もプロンプトとの関連付けもなく、確認済み実行関数に直接ディスパッチします。これを許可すると、ここに記載したほかのルールはすべて、1 つの名前で無効になります。恒久的にブロックします。get_all_secrets_unmasked— アプリケーションのスコープ内にあるすべての秘密情報を、マスクを解除した状態でモデルのコンテキストに返す単一の呼び出しです。恒久的にブロックします。get_record_type_schema— v2.5.0 のアップストリームでは機能しません。LoadRecordTemplates()の呼び出し元がなく、パッケージにもinit()がないため、呼び出すたびに「レコードテンプレートが読み込まれていません」が返されます。download_file— MCP 経由では動作しません。アップストリームのDownloadFile(uid, fileUID, savePath)はerrorのみを返し、サーバー側のパスにバイトを書き込みます。ハンドラーが返すのは{uid, file_uid, path, message}であり、ファイル自体は返しません。save_pathを使用可能にすると、テナントが任意に指定したパスへ、ほかのすべてのテナントの子プロセスと共有されるコンテナのファイルシステム上で書き込めてしまいます。一方、これを取り除くと書き込み先がなくなります。
引数ポリシー:ブロックリストではなく許可リスト
提供される各ツールは、受け付ける引数を正確に宣言します(src/tools.ts の allowArgs)。これは、公開される inputSchema と受信する tools/call の両方で適用されます。そのため、古いスキーマや無視されたスキーマを使っているクライアントが引数を渡しても、書き込みパスには到達できません。
現在のエントリーよりも、デフォルトの動作のほうが重要です。上流の更新で提供済みツールにパラメーターが追加されても、フェイルクローズとなり、誰かがテーブルへの追加をレビューするまで公開対象から外れます。現在削除されているのは generate_password の save_to_secret と folder_uid のみで、これらを許可するとローカルユーティリティがレコードを作成する機能に変わってしまいます。
書き込みを有効にするには、src/tools.ts と、それに対応する conduit の VENDOR_TOOL_CONFIG 行をレビューしてバージョン管理された変更を行う必要があります。設定の切り替えでも、環境変数でもありません。
認証情報の契約
ゲートウェイは、すべての /mcp リクエストにこのヘッダーを転送します。conduit の vendor-config は完全に一致している必要があります。
| ヘッダー | 子プロセスの環境変数 | 必須 |
|---|---|---|
X-Keeper-Config-Base64 | KSM_CONFIG_BASE64 | はい |
値は、Keeper Vault → Secrets Manager → your application → Devices → Add Device から取得する base64 デバイス設定です(ewog... で始まります)。
前提条件: このデバイスが属するアプリケーションには、読み取り専用(編集不可)の共有が必要です。境界 1 を参照してください。これはシステムで最も強力な制御であり、ブリッジが代わりに検証することはできません。
有効性ルール: 標準の base64 でデコードすると、clientId、privateKey、appKey を含む、文字列値のみのフラットな JSON オブジェクトになる必要があります。hostname は任意です。上流の internal/ksm/client.go にある InitializeWithConfig で検証済みで、必須なのはこの3つのみです。それ以外の場合は、指定された値を決してエコーしない JSON-RPC エラーボディを含む HTTP 401 を返します。
ブリッジは、すべての子プロセスに次の設定も強制します。
KSM_MCP_BATCH_MODE=true # no TTY in a container (see above)
KSM_MCP_LOG_LEVEL=error
argv: ["serve", "--batch"]
KSM_MCP_PROFILE は意図的に設定されません。KSM_CONFIG_BASE64 が存在する場合、上流はメモリ内プロファイルを作成し、ディスク上のプロファイルストアには一切アクセスしません。これにより、テナント間でファイルシステムを通じた状態の共有が防がれます。
設定
| 環境変数 | デフォルト | 備考 |
|---|---|---|
CONDUIT_S2S_SECRET | 空(起動を拒否) | サービス間シークレットです。空、または既知のプレースホルダー(changeme、secret、replace-me、your-s2s-secret、その他のサンプル値)の場合、HTTP はエラーをログに記録してゼロ以外の終了コードで終了します。openssl rand -hex 32 で生成してください。Compose はこの値がないと起動しません。この値がログに記録されることはありません。 |
MCP_ALLOW_INSECURE_DEV | 未設定 | CONDUIT_S2S_SECRET なしで HTTP を起動する場合に限り、ローカル開発用として正確に 1 に設定します。プロセスは警告をログに記録し、X-Gateway-S2S を強制せず、127.0.0.1 のみでバインドします。ループバック以外の MCP_HTTP_HOST は無視され、上書きされたことがログに記録されます。既知のプレースホルダーシークレットが設定されている場合は、引き続き起動を拒否します。 |
MCP_HTTP_HOST | 実際のシークレットが設定されている場合は 0.0.0.0 | バインドアドレスです。シークレットが設定されている場合、この値が適用されます。0.0.0.0 も指定できます。未設定の場合は、従来のコンテナバインドが維持されます。イメージと Compose は、コンテナ内で 0.0.0.0 を設定します。シークレットがない場合、開発用バイパスによって 127.0.0.1 が強制され、この変数は適用されません。 |
PORT | 8080 | 公開リッスンポートです。PORT が未設定の場合は、MCP_HTTP_PORT がフォールバックとして使われます。 |
KSM_MCP_BIN | /usr/local/bin/ksm-mcp | ブリッジが起動するアップストリームバイナリです。 |
CHILD_HOME | /tmp/ksm-mcp-home | 各テナントに専用の HOME サブディレクトリを割り当てるルートディレクトリです。サブディレクトリは起動時に作成されます。 |
IDLE_EVICT_MS | 900000 | アイドル状態のテナントのタイムアウト(15 分)です。nutanix-mcp の 60 分より短く設定しているのは意図的です。Go の子プロセスは数ミリ秒で再起動し、開いたままの時間が 1 分増えるごとに、認証済みの KSM セッションがメモリ上に 1 分長く保持されるためです。 |
SPAWN_TIMEOUT_MS | 30000 | 子プロセスが MCP ハンドシェイクに応答するまでの最大待機時間です。 |
.env.example をコピーしてください。サンプルでは CONDUIT_S2S_SECRET を空のままにし、実際の値は Git で追跡されない .env またはプロセス環境にのみ設定してください。
HTTP 認証
CONDUIT_S2S_SECRET が設定されている場合、すべての /mcp リクエストに X-Gateway-S2S を含める必要があります。チェックは src/s2s-verify.ts の verifyS2sHeader で行います。ヘッダーの値は t=<unix seconds>,v1=<hex> 形式で、hex は CONDUIT_S2S_SECRET をキーとして、リテラル文字列 t=<unix seconds> に対して計算した HMAC-SHA256 です(小文字の 16 進数 64 文字)。タイムスタンプはサーバーの時計との差が 300 秒以内でなければなりません。ヘッダーがない、または無効な場合は、Keeper 認証情報のヘッダーを読み取る前に HTTP 401 が返されます。
TS=$(date +%s)
SIG=$(printf 't=%s' "$TS" | openssl dgst -sha256 -hmac "$CONDUIT_S2S_SECRET" -hex | awk '{print $NF}')
curl -sS http://127.0.0.1:8080/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "X-Gateway-S2S: t=${TS},v1=${SIG}" \
-H "X-Keeper-Config-Base64: $KSM_CONFIG_BASE64" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
ゲートウェイモードのみを使用できます。/mcp には X-Keeper-Config-Base64 も必要です。このヘッダーのみがリクエストの認証情報のソースです。プロセス環境の KSM_CONFIG_BASE64 はフォールバックとして使われません。
/health と /healthz は引き続き認証不要で、認証情報も読み取りません。
Docker
イメージはコンテナ内で 0.0.0.0(MCP_HTTP_HOST)をリッスンします。ポートはループバックに公開し、実際の S2S シークレットを渡してください。Keeper 認証情報はリクエストごとにヘッダーで渡され、環境からは読み取られません。
docker build -t keeper-mcp .
docker run \
-e CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" \
-e MCP_HTTP_HOST=0.0.0.0 \
-p 127.0.0.1:8080:8080 \
keeper-mcp
CONDUIT_S2S_SECRET が未設定の場合、Compose は起動を拒否します。ゲートウェイサービスでは KSM_CONFIG_BASE64 も env_file も設定せず、127.0.0.1:8080:8080 を公開します。
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" docker compose up
シークレットなしでのローカル HTTP は開発専用です。127.0.0.1 にバインドし、警告をログに記録します。ループバック以外の MCP_HTTP_HOST(0.0.0.0 を含む)は無視されます。
MCP_ALLOW_INSECURE_DEV=1 npm start
エンドポイント
POST /mcp— Streamable HTTP MCP、デュアルエラ対応です。有効なX-Gateway-S2SヘッダーとX-Keeper-Config-Base64が必要です。GET /healthとGET /healthz— 認証不要の稼働状態確認です。mode、提供するツールの数、稼働中のテナント数を報告します。認証情報を読み取ることも、子プロセスを起動することもありません。
開発
npm install
npm run lint # tsc --noEmit
npm test # vitest
npm run build
CONDUIT_S2S_SECRET が設定されるまで、HTTP は起動を拒否します。シークレットなしでローカルリスナーを起動するには、次のようにします。
MCP_ALLOW_INSECURE_DEV=1 npm start
テストスイートでは、許可リストポリシー、認証情報の仕様、実際の HTTP スタックを通した 401 ゲートを検証します。Keeper アカウントも、起動中の子プロセスも必要ありません。プールには存在しないバイナリを指定しているため、誤って起動しようとすると明確に失敗します。
アップストリームのピン留めバージョンを更新する
DockerfileのKSM_MCP_REFを変更します。- 再ビルドします。Docker のビルド中に
scripts/check-upstream-tools.mjsが実行され、ピン留めした Go ソースからツールの構成を再導出します。構成に差異がある場合(新しいツール、名前が変更されたツール、すでに提供されているツールへの新しい引数の追加など)は、ビルドを失敗させます。これは強制されるチェックであり、チェックリストの項目ではありません。 - 報告された内容を確認し、
src/tools.tsとsrc/__tests__/fixtures.tsに反映します。新しいツールは許可リストによってデフォルトで拒否されますが、明示的に判断して記録する必要があります。 - 再度ビルドします。ビルド時のスモークテストと差異チェックの両方に合格する必要があります。
- リリースを作成し、その後、conduit's の
azure/vendor-fleet.conduit-prod.bicepparamでダイジェストを再度ピン留めします。
main を追跡してはいけません。また、keeper/keeper-mcp-server:latest を取得してはいけません。ピン留めするのは、レビュー済みの Git タグであり、私たちが自分でビルドするものです。
ライセンス
Apache-2.0(このブリッジ)。アップストリームの Keeper サーバーは MIT ライセンスで、イメージのビルド時にソースからビルドされます。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。