自分用の MCP GitHub Proxy を作った話 (3) ガードレールの考え方

Technical

自分用の MCP GitHub Proxy を作った話 (3) ガードレールの考え方

前回 は OAuth まわりを書いた。OAuth App の repo scope は「read と write 両方ついてくる」粗い権限で、絞り込みはすべてアプリ側でやる、という話をした。

今回はその「アプリ側でどう絞っているか」を書く。実装詳細を延々と並べるより、考え方を中心に整理したい。

  • (1) なぜ作ったか・全体構成
  • (2) GitHub OAuth App と OAuth フロー
  • (3) ガードレールの考え方 ← この記事
  • (4) Codex / ChatGPT 連携時のルール整合

4 つの層で絞る

ガードレールは 4 つの層で構成している。

絞る対象何を見る
ツール層サーバーが提供する MCP ツールそのものdisabledTools でクライアントから見えなくする
リポジトリ層どのリポで何が出来るかlevel (read / issues / full)
ブランチ層リポ内のどのブランチを触れるかglob マッチで branch ごとに level を上書き
ファイル層どのパスを書き換えて良いかallow / deny の glob

上から下に行くほど粒度が細かくなる。上の層で弾ければ、下の層の判定は走らない。

原則として、「届かないものは安全」。ツール層で ghmcp_get_workflow_run_logs を disabled にすれば、そもそもクライアントから呼べない。リポ層で level: "read" にすれば、そのリポに対する書き込み系ツールはすべて「permission denied」で止まる。

ツール層: 見えなければ呼べない

MCP ツールはサーバー起動時にすべて定義しておき、ghmcp.config.tsdisabledTools 配列に含まれるものはランタイムで登録をスキップする。

disabledTools: [
  'ghmcp_get_me',
  'ghmcp_list_repos',
  'ghmcp_list_workflow_runs',
  'ghmcp_get_workflow_run_logs',
  'ghmcp_search_code',
],

これらは MCP クライアント (Claude.ai 等) の tool 一覧にそもそも出てこない。呼び出そうとしても「そんなツール知らん」で弾かれる。

「必要ないものは見せない」は、安全側に倒すだけでなく、モデルの選択肢を減らす意味でも効く。使えるツールが 20 個あるより 13 個の方が、エージェントが迷う余地が減り、想定外の組み合わせで事故る確率も下がる。

リポジトリ層: 3-tier permission

リポジトリ単位の権限は 3 段階にしている。

  • read: 閲覧系のみ (Issue 一覧、PR 詳細、ファイル取得 など)
  • issues: read に加えて Issue / PR / コメントの書き込みも可
  • full: issues に加えて branch 作成とファイル書き込み (commit / push) も可

「なぜ 3 段階か」は、触れる操作の自然な grouping で決まった。閲覧だけ / 書き込むが話す範囲 / コード自体を変える、の 3 つが実運用上はっきり分かれる。5 段階や 7 段階にする案も考えたが、細かすぎると設定側の判断が重くなる。

3-tier の粒度は、GitHub Apps の fine-grained permissions を取り上げた第 2 回と比較すると粗い。粗さを補うのが次の 2 層だ。

ブランチ層: glob + first match wins + inherit

リポが full でも、そのリポ内のあらゆるブランチに書けるわけではない。ブランチ単位で上書きできる。

branches: {
  main: 'read',
  'claude/*': 'full',
  '*': 'issues',
},

仕様は 3 つ。

  • glob マッチ: ブランチ名はワイルドカード (*) が使える
  • first match wins: 上から順に評価し、最初にマッチしたルールが採用される
  • inherit: リポ層の level をそのまま使う ("main": "inherit" のように書ける)

この 3 つで「main への直接 push は禁止、claude/ プレフィクスなら push 可、それ以外は Issue までは触れる」のような運用が、設定だけで書ける。コードで特殊ケースを書かずに済む。

first match wins にしたのは、設定ファイルの上から下への流れを、意味論的な優先順位と一致させるため。「上に書いたルールが強い」が直感的に読める。

ファイル層: allow / deny の glob

コード書き込み時には、さらにパス単位でフィルタがかかる。

files: {
  deny: ['.github/workflows/**'],
},

allow が未指定ならすべて許可、そこから deny で引き算する。必要なら逆に allow でホワイトリストにもできる。

上記の例の .github/workflows/**variables.tsVARIABLES.globalForbiddenPaths にハードコードしていて、config でどう書こうが上書きできない。CI 設定を書き換えるツールは一度入れたら脱出が困難なので、最後の安全網としてハードコードしている。

identity 層: 誰が何をやったかを残す

ガードレールは「止める」だけでなく、「記録する」側面もある。

このサーバー経由で作られる Issue / PR / commit には、エージェント情報が自動で差し込まれる。

  • commit trailer: Assisted-by: Anthropic/ClaudeAI:1.0.0 (claude-opus-4-7)
  • Issue / PR footer: 🤖 Generated by Anthropic/ClaudeAI:1.0.0 via MCP
  • Issue / PR label: bot/Anthropic/ClaudeAI

テンプレートには {agent} / {version} / {user} / {email} / {model} の変数が使える。agentversion は MCP の初期ハンドシェイクでクライアントが名乗った情報、useremail は OAuth で認証された GitHub ユーザー、model は各ツール呼び出し時の optional 引数 modelHint だ。

modelHint を optional にしたのは、「どのモデルが呼んだか」はクライアント側の自己申告に依存する情報だからだ。強制はできない。されど、Opus 4.7 か Sonnet 4.6 か Haiku 4.5 かの区別は、後で log を眺めた時の情報価値が高い。強制しないが推奨する、という立ち位置で置いている。

この仕組みのおかげで、GitHub 上のリポには「どのエージェントが、どのモデルで、どのバージョンのサーバー経由で、何を書いたか」が全部残る。監査可能性 (audit trail) というやつだ。

validator 層: 形式の強制

3-tier permission が「何を触れるか」の話だとすれば、validators は「触れるとして、どんな形で触れるか」の話になる。

サーバー側で検査しているのは 3 つ。

  • title validator: Issue / PR の title。[bracketed] prefix 禁止、Conventional Commits 風の prefix を使うなら許可リストに一致必須
  • commit message validator: <type>(<scope>)?!?: <subject> の形を header 1 行目で強制。allowedTypes のいずれかに一致しない prefix は弾く
  • file path validator: globalForbiddenPaths + allow / deny glob

大事なのは、これらをクライアント側のプロンプトではなくサーバー側で実装していることだ。

プロンプトで「こう書いてね」とお願いしても、長いセッションのなかでは忘れられる。モデルが入れ替わればルールもリセットされる。第 2 回の MCP Proxy の二重構造の話と同じで、規律は端点ではなく中間のサーバーで保証するのがこの設計の核だ。

なお commitTypes はリポ単位で上書きできる。デフォルトは英語の Conventional Commits 系の type 一覧 (feat / fix / chore / ...)、特定リポでだけ emoji prefix 付き (✨ feat / 🛠️ fix / ...) も許可したければ、そのリポの commitTypes 配列に両方入れる。エッジケースではあるが、「このリポは緩めのルールで運用したい」という要望を設定ファイルで吸収できる。

次回

第 4 回では、Codex と ChatGPT との連携時にこのガードレールをどう整合させているかを書く。

  • Codex CLI と Codex Web の MCP 対応状況の差
  • ChatGPT の MCP 対応状況 (developer mode / ベータ)
  • ChatGPT 側に GPT メモリで仕込んである Issue / PR 生成ルールと、このサーバーのルールをどう揃えているか

シリーズ最終回だ。