自分用の MCP GitHub Proxy を作った話 (3) ガードレールの考え方
自分用の 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.ts の disabledTools 配列に含まれるものはランタイムで登録をスキップする。
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.ts の VARIABLES.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} の変数が使える。agent と version は MCP の初期ハンドシェイクでクライアントが名乗った情報、user と email は 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/denyglob
大事なのは、これらをクライアント側のプロンプトではなくサーバー側で実装していることだ。
プロンプトで「こう書いてね」とお願いしても、長いセッションのなかでは忘れられる。モデルが入れ替わればルールもリセットされる。第 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 生成ルールと、このサーバーのルールをどう揃えているか
シリーズ最終回だ。