
うっかりBacklog MCP Server経由で別プロジェクトに課題を登録しないようにhooksでガードレールを設定してみた
リテールアプリ共創部@大阪の岩田です。
Backlog MCP ServerはClaude Code等のAIエージェントからBacklogを操作するのにとても便利なツールです。
ただ、便利だからといって適切なガードレールを設定せずに利用するとAIの意図せぬ挙動によって事故が起きるリスクもあります。このブログではBacklog MCP Server利用時のガードレールとしてhooksを利用し、操作対象を特定のBacklogプロジェクトに制限する実装サンプルを紹介します。
※以後の「プロジェクト」という記述はClaude Codeを実行しているディレクトリという意味で利用しています。ユーザーレベルの設定/プロジェクトレベルの設定という文脈で使用しており、Claudeのprojectsではありません。また、Backlogのプロジェクトについては明示的に「Backlogプロジェクト」と記載します。
はじめに
Backlog MCP Serverを接続したBacklogスペース内に複数のBacklogプロジェクトが存在する場合、本来BacklogプロジェクトAに課題を登録したかったのに、BacklogプロジェクトBに課題を登録してしまうといったリスクが懸念されます。Claude.mdに「Backlog MCP Serverを利用する時のBacklogプロジェクトIDは必ず12345を指定すること!!!」と書いてもそれが守られる保証はありません。意図せぬ動作を確実に防ぐにはhooksによる決定論的な防御が有効です。
やってみる
ということで、さっそくhooksを登録していきましょう。
設計について考えてみる
まずどのスコープでhooksを設定すべきかについて考えてみました。プロジェクトレベルで設定すると今後新しく増えたプロジェクトに対するhooks設定漏れの懸念があるため、ユーザーレベルの設定として追加するのが良さそうです。
ただし、ユーザーレベルの設定にするとプロジェクト固有の設定が書けなくなるため、hooksには汎用性が求められることになります。シェルスクリプトにハードコードでBacklogプロジェクトのIDを記載するようなことはできません。一方で課題の操作を許可したいBacklogプロジェクトはプロジェクトによって異なるはずなので、プロジェクト内のテキストファイルに許可対象のBacklogプロジェクトを列挙し、スクリプトからはこのテキストファイルを読み込んでツール呼び出しの可否を判断することにしました。
今回はbacklog.allowというテキストファイルに操作を許可するBacklogプロジェクトのIDを列挙する形で実装していきます。
PreToolUseのhookを登録
まず~/.claude/settings.jsonに以下のようにhooksの記述を追加します。
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__backlog__add_issue",
"hooks": [
{
"type": "command",
"command": "${HOME}/.claude/hooks/backlog/project-check.sh || { echo 'hook script execution failed' 1>&2; exit 2; }"
}
]
}
]
}
matcherにはmcp__backlog__add_issueを指定しています。Backlog MCP Serverをbacklogという名前で登録するという前提がありますが、この記述によりBacklog MCP Serverのadd_issueツールの呼び出しによってhooksが発火するようになります。
commandにはproject-check.shというシェルスクリプトを指定しています。もしこのシェルスクリプトが存在しない場合は完了コード2で終了して、ツールの呼び出しがブロックされるよう|| { echo 'hook script execution failed' 1>&2; exit 2;を指定しています。
シェルスクリプトを実装する
続いて先ほどhooksで指定したproject-check.shを実装します。中身はこんな感じです。
#!/bin/bash
PROJECT_ID=$(jq -r '.tool_input.projectId')
ALLOW_FILE="${CLAUDE_PROJECT_DIR}/.backlog.allow"
if [ ! -f "$ALLOW_FILE" ]; then
jq -n "{
hookSpecificOutput: {
hookEventName: \"PreToolUse\",
permissionDecision: \"deny\",
permissionDecisionReason: \"Allow file not found: $ALLOW_FILE\"
}
}"
exit 2
fi
if ! grep -Fxq "$PROJECT_ID" "$ALLOW_FILE"; then
jq -n "{
hookSpecificOutput: {
hookEventName: \"PreToolUse\",
permissionDecision: \"deny\",
permissionDecisionReason: \"Project ID $PROJECT_ID not allowed\"
}
}"
exit 2
fi
exit 0
まずPROJECT_ID=$(jq -r '.tool_input.projectId')でツール呼び出しの引数からBacklogプロジェクトのIDを取得しています。
後続処理ではプロジェクトのディレクトリ配下に.backlog.allowというファイルが存在するかチェックし、存在しない場合はツールの呼び出しを拒否します。ファイルが存在する場合はツールの引数で指定されたBacklogプロジェクトのIDが.backlog.allow内に存在するかをチェックしてツール呼び出しの可否を判定します。
テストしてみる
hooksの準備ができたのでテストしていきましょう。以後は全て以下のプロンプトとsonnet5でテストしています。
mcp__backlog__add_issue ツールを使ってissueを登録して
プロジェクトIDは826921
タイトルは"test from claude code" issueTypeIdは4436305 priorityIdは2
まずはシェルスクリプトに実行権限を付与し忘れた場合...

フックスクリプト ~/.claude/hooks/backlog/project-check.sh に実行権限がないため、issue登録がブロックされています。というエラーになり課題の登録に進みませんでした。
続いて.backlog.allowというテキストファイルが存在しない場合の実行結果です。

これも想定通りエラーです。
.backlog.allow ファイルが見つからないというエラーが出ました。この作業ディレクトリに .backlog.allow ファイルが必要なようですが、心当たりはありますか?というエラーで課題の登録まで進みませんでした。
今度はプロンプトで指定されたBacklogプロジェクトIDが.backlog.allow内に存在しない場合の実行結果です。

プロジェクトID 826921 へのアクセスが許可されていないため、issue登録に失敗しました("Project ID 826921 not allowed"というエラー)。と表示され、こちらも課題の登録まで進みませんでした。
最後にプロンプトで指定されたBacklogプロジェクトIDが.backlog.allow内に存在する場合の実行結果です。

Issueを作成しました。TEST-9("test from claude code"、種別: タスク、優先度: 高)としてプロジェクトID 826921に登録済みです。というメッセージで無事に登録成功です!ちゃんと課題も登録されています。

まとめ
今回は取り急ぎadd_issueのみ試してみましたが、delete_issueやupdate_issueに関してもissueId指定のツール呼び出しを拒否しつつ、issueKeyのプレフィックスをチェックするようなhooksを実装すれば特定のBacklogプロジェクトのみ課題の操作を許可するようなガードレールが実装できそうです。
※テキストファイルにBacklogプロジェクトIDを列挙するという設計は拡張性が乏しいため、json形式で設定を保持するようにしておけばよかったと後悔しました。
あとはMCPサーバーをbacklogという名前で登録しておかないとツール名がmcp__backlog__add_issueにならず、hooksのマッチャーにマッチしないという点が課題でしょうか...



