/note/tech

社内のメンバーだけがアクセス可能な CLI / MCP を作る

要約:

■ 1. 背景と課題

  • スタートアップで、AI が人間と同じ文脈にアクセスできる基盤システムを開発中
  • API キーによる認証の課題:
    • 秘密情報である API キーを AI に渡すリスクおよび漏洩の危険性がある
    • 発行・配布・保管に手間がかかる
    • 入退社時の失効や有効期限の個別管理が必要になる

■ 2. 解決方針

  • API キーを配布せず、API 自体を IdP 認証で保護し、CLI と MCP で認証を共有する構成を採用
  • Cloudflare Access を認証エッジとして採用した理由:
    • IdP(Google Workspace 等)と直接連携でき、本人確認を IdP に委ねられる
    • cloudflared がトークンの取得・キャッシュを担うため、CLI・MCP が秘密情報を持たずに済む
    • API の実行基盤(Workers / D1)も Cloudflare で構築し、同一プラットフォームで完結する
  • 構成の要点:
    • API を Cloudflare Access の内側に置き、IdP 認証済みリクエストだけを通す
    • CLI と MCP サーバを同一バイナリで提供し、同じ認証・同じ API を使う

■ 3. 全体構成

  • 3 つの要素で構成:
    • API(保護対象): Cloudflare Workers 上で動作し、Cloudflare D1 をデータベースとする業務データの正本
    • CLI(コマンドライン入口): 人間およびシェルを扱う AI エージェントが使用するコマンドラインツールであり、配布の単位も兼ねる
    • MCP サーバ(AI ホスト向け入口): MCP 対応の AI ホストから呼び出すための入口で、CLI と同一バイナリが提供する
  • 入口の使い分け:
    • 人間: 主にターミナルで CLI を使用
    • AI: MCP 対応ホストからは MCP を、シェルを扱えるエージェントからは CLI を直接実行
    • CLI は出力先がパイプの場合に JSON 形式へ自動切り替えし、AI からも扱える

■ 4. 認証の仕組み

  • リクエストの流れ: クライアント → Cloudflare Access → API
  • トークン取得手順:
    • クライアントがログイン操作でブラウザを起動
    • Cloudflare Access 経由で IdP のログイン画面に遷移し、利用者が認証
    • 認証成功後、Cloudflare Access が短命の JWT を発行
    • cloudflared が JWT をローカルにキャッシュし、期限切れ後は再ログインで取り直す
  • 二段階の JWT 検証:
    • エッジ(Cloudflare Access)が JWT を検証し、失敗すれば拒否
    • 検証成功後、Cf-Access-Jwt-Assertion ヘッダを付与して API へ転送
    • オリジン(API)が JWKS で再検証し、署名・issuer・audience を確認
    • 多層防御として、設定ミスや想定外の経路で直接到達したリクエストも弾ける
  • アカウント管理:
    • 「誰が社員か」は IdP のみで管理し、Cloudflare Access も API も独自のユーザ名簿を持たない
    • 入社時は IdP にアカウントを追加するだけで全経路にアクセス可能
    • 退職時は IdP からアカウントを削除するだけで全経路のアクセスが即座に断たれる

■ 5. CLI の配布

  • GitHub Packages の npm レジストリに非公開パッケージとして publish
  • GitHub Org のメンバーシップがインストールのゲートになる
  • 配布の流れ:
    • バージョンタグの push を契機に GitHub Actions が起動し、ビルド・型チェック・publish を実行
    • publish は GITHUB_TOKEN(ワークフロー実行中のみ有効)で行い、長期トークンの管理が不要
    • 利用者は GitHub 認証を通したうえでグローバルインストール
  • install 側の認証方法:
    • 動的取得(主): GitHub CLI のセッションからトークンを取得し環境変数経由で npm に渡す(ディスクへの平文保存なし)
    • 個人アクセストークン(従): read:packages 権限を持つトークンを設定ファイルに保存する代替手段(有効期限管理が必要)
  • セキュリティ境界の位置付け:
    • CLI バイナリ自体は秘密情報を含まず、流出しても API にはアクセスできない
    • GitHub Packages の非公開化は入口を一段減らす層であり、本体のセキュリティ境界は Cloudflare Access
    • 退職時に GitHub Org と IdP の両名簿から外すことで、入手経路と実行権限が同時に失われる

■ 6. MCP の設定

  • CLI と同一バイナリをサブコマンドで起動し、AI ホストの設定ファイルに登録する
  • 通信方式: ホストがプロセスを spawn し、stdio 越しに JSON-RPC で通信
  • 遅延認証: 起動時には認証せず、ツール呼び出しのたびにトークンを取得し、認証エラー時は一度だけ再試行
  • 読み取り専用: AI に公開するツールはデータの参照系のみに限定し、型レベルで変更操作を排除
  • AI は端末利用者の cloudflared ログインセッションをそのまま使い、AI 専用の認証情報は発行しない

■ 7. IdP ログインを行えない経路の扱い

  • 対象: 外部サービスの Webhook、外形監視のヘルスチェック、ローカル開発
  • Cloudflare Access の Bypass ポリシーで保護対象から外し、別手段で正当性を担保:
    • Webhook: 送信元が付与する署名(HMAC 等)をオリジン側で検証
    • ヘルスチェック: 機密情報を返さない軽量エンドポイントを用意
    • ローカル開発: 認証バイパス経路を設けるが、本番環境には持ち込まない

■ 8. 運用コスト

  • 50 ユーザー以下の組織では IdP 以外はほぼ無料で運用可能
  • コスト内訳:
    • Cloudflare Access(Zero Trust): 50 ユーザーまで無料
    • Cloudflare Workers・D1: 小〜中規模の利用では無料枠に収まる
    • 実質的な費用は IdP のみで、多くの組織が既存契約を流用できる