AIエージェント(Claude Code / Codex)からBacklogを操作したくて公式MCPサーバー(nulab/backlog-mcp-server)を入れたものの、こんな状態になっていませんか。
- 大量のツール定義が毎回コンテキストに載って、Backlogを触らないタスクでもトークンを消費し続ける(tool overload)
- 動かすのにDockerと常駐プロセスが必要で、マシンごとのセットアップが面倒
- Claude CodeとCodexを併用していると、MCP設定を二重に管理することになる
私もこれで消耗した結果、MCPサーバーをやめて、Backlog REST APIを直叩きするワンショットCLI+スキルを自作しました。社内で使って体験がかなり良かったので、公開します。
📦 リポジトリ:https://github.com/RyoheiKawamon-idealive/backlog-cli
何ができるか
エージェントに「PROJ-123の課題を見て対応方針をコメントして」と頼むと、前置きや確認なしでスイスイ動きます。裏で叩かれているのはこういうコマンドです。
# 課題の本文+コメントを1発で取得
node "$SKILL/backlog.mjs" inspect PROJ-1
# 検索
node "$SKILL/backlog.mjs" search --project PROJ --keyword "ログイン" --count 5
# コメント投稿・課題更新・起票(状態・種別・担当は名前で指定OK)
node "$SKILL/backlog.mjs" comment PROJ-1 "対応しました。"
node "$SKILL/backlog.mjs" update PROJ-1 --status 完了 --assignee me
node "$SKILL/backlog.mjs" create --summary "新しい課題" --issue-type タスク --priority 中
特徴は次のとおりです。
- 必要なのはNode.js 18+だけ。npm依存ゼロ(組込みfetchのみ)、Docker・常駐プロセス不要。Windows / macOS / Linux同一動作
- ワンショットCLI:1コマンド実行して結果を返して終わり。MCPのようにツール定義が常時コンテキストに載らないので、Backlogと無関係なタスクでのトークン消費はゼロ
-
Claude CodeとCodexの両対応:スキルはただのフォルダなので、
.claude/skills/と.codex/skills/に置くだけで同じ実装が両方で動く - 操作別の権限つき:既定は読み取りのみ。書き込みはユーザーが明示的に許可するまで動かない
- 複数スペース(複数ドメイン)対応
セットアップはリポジトリをcloneして同梱のインストーラを1回実行し、対話ウィザードでドメインとAPIキーを入れるだけです(詳細はリポジトリのREADMEへ)。
スキルの全体像
スキルの正体は、ただのフォルダです。MCPサーバーのような常駐プロセスはどこにもありません。
backlog-cli/
├── SKILL.md # エージェント向けの指示書(動き方・前提・最短手順)
├── README.md # 人間向けの詳細ドキュメント(セットアップ・トラブルシュート)
├── backlog.mjs # 本体CLI(API呼び出し+権限チェックを内蔵。依存ゼロの単一ファイル)
├── .backlog-perms.example # 操作別の権限設定の雛形(読み/書きのON・OFF)
└── install.mjs # インストーラ(.claude/skills と .codex/skills へコピーする)
このフォルダを.claude/skills/や.codex/skills/に置くと、エージェントはこう動きます。
- ユーザーが「PROJ-123の課題を見て」と頼む
- エージェントがSKILL.mdを読み込む(このときだけコンテキストに載る)
- SKILL.mdの前提に従って、確認なしで
node backlog.mjs inspect PROJ-123を実行 -
backlog.mjsが.backlog-permsで権限を判定 → OKならAPIを叩き、NGなら「次の一手」を出力して停止 - エージェントは結果だけをユーザーに返す
登場人物はこれで全部です。次に各ファイルの中身を見ていきます。
各ファイルの中身
SKILL.md(全文)
エージェントへの指示書です。54行しかないので全文載せます。後述する工夫は、ほぼこのファイルに詰まっています。
---
name: backlog-cli
description: Backlog REST API v2 を Node.js で直叩きする自己完結CLIスキル。公式MCP不要、Win/Mac/Linux 同一動作、npm依存ゼロ(Node 18+ 組込み fetch のみ)。秘密は ~/.config/agent-skills/backlog-cli.env で渡す(AI運用では対話的exportは届かない)。操作別権限つき・既定で書き込みOFF。失敗時はコマンド自身が次の手順を出すので、それに従う
---
## 動き方(最重要)
- **前置き・実況を出さない**(「〜します」等を書かない)。**いきなりコマンドを実行**する。成功時は結果だけ返す。
- 一直線で進む。止まるのは**コマンドが実際にエラーを返したときだけ**。そのときは**コマンドの出力に書かれた手順にそのまま従う**(説明・原因分析もそのとき)。
## 前提(自分で判断しない・毎回考え直さない)
- 外部通信ツール。**ほぼ全コマンドが通信必須**(`config` のみローカル)。サンドボックスでは**最初から通信を許可して実行**し、「通信が要るか」を毎回判断・前置きしない(`fetch failed` を踏まない)。
- 認証は `~/.config/agent-skills/backlog-cli.env` から**自動取得**。**この設定ファイルは絶対に `cat`/中身を見ない**(APIキーが入っている)。値を見たいときは `config`(鍵は非表示)。
- 状態・種別・優先度・担当・PJ は**名前で指定OK**(数値IDも可・CLIが内部解決)。事前に一覧を引かない。担当は `me` も可。
- `config` で `BACKLOG_PROJECT` / `BACKLOG_MY_USER_ID` が設定済みなら、`--project` 省略・`--assignee me` をそのまま使う。リポに `.backlog-cli.json` があれば space/PJ は自動選択(`--space` / `--project` で上書き可)。単一スペース設定なら `--space` 不要(唯一の設定ファイルに自動フォールバック)。
## 実行方法
CLI は **この SKILL.md と同じフォルダの `backlog.mjs`**。cwd 非依存で**絶対パス**で呼ぶ(探し回らない):
```sh
SKILL=/abs/path/to/backlog-cli # 例: <project>/.codex/skills/backlog-cli
node "$SKILL/backlog.mjs" myself
```
全コマンドは `node "$SKILL/backlog.mjs" help` で出る。
## 最短手順
- 課題の中身 → `inspect <issueKey>`(本文+コメントを1発)
- 検索 → `search`/メタ → `statuses` `issue-types` `priorities`/設定確認 → `config`
## 権限・書き込み・整形(詳細はコマンド出力/README)
- 既定は **読みON・書きOFF**。書き込みは直接実行(プレビューは任意・既定OFF)。
- **失敗時はコマンド自身が次の手順を出す**(権限=exit 3 / プレビュー=exit 7 / 通信失敗)。**その指示に従い、指示に反して勝手に「権限有効化」「`--confirm`」「再実行」をしない**。
- 本文・コメントで装飾を使うなら、**そのPJの記法に従う**(Backlog は PJ ごとに Markdown記法/バックログ記法)。記法・書き方の規約は**プロジェクトの `AGENTS.md`/`CLAUDE.md`** を見る。不明なら `project <key>` の `textFormattingRule`(`markdown`/`backlog`)で確認。**プレーンテキストは気にしない**。
## cookbook(代表例。全コマンドは `help`)
```sh
node "$SKILL/backlog.mjs" config # 既定PJ/自分のID/ドメイン(鍵は非表示)
node "$SKILL/backlog.mjs" inspect PROJ-1 # 本文+コメント(推奨)
node "$SKILL/backlog.mjs" search --project 3 --keyword "ログイン" --count 5
node "$SKILL/backlog.mjs" comment PROJ-1 "対応しました。"
node "$SKILL/backlog.mjs" update PROJ-1 --status 完了 --assignee me # 状態/担当は名前でOK
node "$SKILL/backlog.mjs" create --summary "新しい課題" --issue-type タスク --priority 中
node "$SKILL/backlog.mjs" --space acme inspect SOMEKEY-1 # 別スペース(複数設定時)
```
---
**詳細は必要なときだけ同フォルダの [README.md](./README.md)**:セットアップ・権限/プレビュー・配布更新・トラブルシュート・セキュリティ。
backlog.mjs(本体CLI)
依存ゼロの単一ファイル(約1,100行)です。API呼び出し・名前→ID解決・権限チェック・セットアップウィザードまで全部入っています。全文はリポジトリを見てもらうとして、helpの抜粋でコマンド体系だけ紹介します。
読み取り系(権限 ON が既定):
myself 自分のユーザー情報
inspect <idOrKey> [count] 課題本文+コメントを1コマンドで取得(推奨・往復削減)
issue <idOrKey> 課題の取得
comments <idOrKey> [count] コメント一覧
search [オプション] 課題検索(状態/担当/PJは名前でも可)
projects / project / statuses / issue-types / priorities
書き込み系(権限 OFF が既定。未許可なら明確なエラー→ユーザーに許可を求める):
※ 状態/種別/優先度/担当/PJ は「名前」で指定可(IDも可・内部解決)
comment <idOrKey> <content> コメント投稿
update <idOrKey> [オプション] 課題更新
create ... 課題作成
escape hatch(権限 OFF が既定):
raw <METHOD> <path> [key=val ...] 任意の API を直叩き
権限管理:
perms 現在の権限状態を表示
inspect(本文+コメントを1発取得)のような往復削減用の複合コマンドを用意しているのがポイントです。
.backlog-perms.example(全文)
操作別の権限設定です。コピーして.backlog-permsとして置くと有効になります。
# 操作別の許可 (1=許可 / 0=禁止)。このファイルを .backlog-perms にコピーして調整。
# 既定は読みON・書きOFF。`#` 以降は説明ラベル(インストーラの選択画面に表示される)。
read:issue=1 # 課題の取得・検索(読み取り)
read:comments=1 # コメントの閲覧(読み取り)
read:projects=1 # プロジェクト一覧・取得(読み取り)
read:meta=1 # ステータス・課題種別・優先度(読み取り)
write:comment=0 # コメントの投稿(書き込み)
write:update=0 # 課題の更新:状態・担当・件名など(書き込み)
write:create=0 # 課題の新規作成(書き込み)
raw=0 # 任意APIエンドポイントの直叩き(最強・最も危険)
preview=0 # 書き込みプレビュー:ONなら書込前に内容提示→--confirmで実行(既定OFF)
README.md / install.mjs
- README.md:人間向けの詳細ドキュメント。セットアップ・権限・プレビュー・トラブルシュート・セキュリティ(APIキーの取り扱い)を全部ここに寄せています。SKILL.mdから「困ったときだけ」参照される役割です
-
install.mjs:
.claude/skills/と.codex/skills/へコピーするインストーラ。再実行で更新でき、ローカルの.backlog-permsは温存されます
スイスイ動くように工夫したこと
ここからが本題です。ただAPIをラップしただけではエージェントはスイスイ動きません。作る過程で分かったのは、エージェントの自由度を上げるのではなく「迷う余地を消す」ことが、速さと信頼性を同時に上げるということでした。上に載せたファイルたちを、工夫の観点で解説します。
工夫1:「迷い消し」— 前提を先に渡して、判断の往復を消す
エージェントが遅く・不安定になる主因は、モデルの賢さ不足ではなく、毎回考え直す判断の往復です。何も設計しないと、エージェントはこう動きます。
- 「通信が要る操作だけど実行していい?」と前置きする
- 念のため設定ファイルや認証情報を確認しに行く
- ステータス一覧をAPIで取得して、使うIDを探す
- やっと本来のコマンドを実行する
1回のBacklog操作のために、確認・設定読み・ID探しの往復が積もっていく。この実行前の迷いを、設計時に全部潰しておきます。
SKILL.md全文をもう一度見てください。「動き方(最重要)」「前提(自分で判断しない・毎回考え直さない)」という見出しからして、コマンドの説明ではなく振る舞いの固定から始まっています。「通信が要るか」「設定はどこか」「CLIはどこにあるか」を、エージェントに判断させず前提として先に渡す。「〜しない」の連打が目立ちますが、これは全部、実際にエージェントがやって遅くなった行動の打ち消しです。
CLI側にも同じ思想を入れています。ステータスや担当者を「完了」「me」のような名前で指定できるようにして内部でID解決するので事前の一覧取得が不要になり、inspectのような複合コマンドで往復そのものを減らしています。
もう1つ地味に効くのがfrontmatterのdescriptionです。ここはスキル本文と違って常時コンテキストに載るので、「何のスキルか」だけでなく「秘密は設定ファイルで渡す」「失敗時はコマンドの指示に従う」といった、迷いやすいポイントの答えを圧縮して詰め込んでいます。
「調べてから動く」を「いきなり動ける」に変えるほど、エージェントは速く、確実になります。
工夫2:権限チェックは「AI」ではなく「スクリプト」に仕込む
エージェントに書き込み系APIを渡すのは怖い。かといって、プロンプトで「書き込み前に確認して」と指示するのはモデルの判断頼みで確実性がなく、AIに権限設定を読ませて判断させると無駄な1ターンが挟まります。
なので、権限チェックはCLI側(backlog.mjs)に内蔵しました。先ほどの.backlog-permsがその設定で、既定は読みON・書きOFF。AIは権限を一切気にせずコマンドを叩くだけです。
- 権限OK → そのまま実行される(AIの思考が1ターン減る)
- 権限なし → スクリプトが確実にエラーで止める(exit 3)。エラーメッセージに「ユーザーに許可を求め、許可後に
perms enableで有効化して再実行」と書いてあるので、AIは勝手に進めず人間にエスカレーションする
SKILL.md側に書いてあるのは「失敗時はコマンド自身が次の手順を出すので、それに従う。勝手に権限有効化・再実行をしない」の1行だけ。エラー処理のロジックはプロンプトに書きません。
安全をモデルの判断ではなくコードに固定する。そしてエラーメッセージ自体が「次の一手」をAIに指示する。この2つで、どのエージェントから呼ばれても同じ安全弁が効きます。さらに慎重にしたい場合は、書き込み前に送信内容を提示して--confirmで実行するプレビューモード(exit 7)も用意しています。
工夫3:トラブルシュートは「困ったときだけ」参照させる
指示書(SKILL.md)に対処法・設定方法・エッジケースを全部書きたくなりますが、これは逆効果でした。SKILL.mdはスキルが発動するたびに読まれるので、平常時には使わないトラブルシュート集を毎回読ませる=毎回重くなるのです。
- ❌ Bad:SKILL.mdに「401ならAPIキー確認、404ならドメイン取り違え…」と全部書く
- ⭕ Good:SKILL.mdは最短の手順だけにして、「詳細は必要なときだけREADME.mdへ」と誘導する
SKILL.md全文の最後の1行が、まさにこの誘導になっています。平常時は軽く、困ったときだけ詳細を開く。いわゆる段階的開示(progressive disclosure)で、Anthropicがスキル設計で推奨している考え方でもあります。
まとめ
MCPを否定したいわけではありません。状態や双方向通信が必要ならMCPが向いていますが、Backlog操作のような単発のREST API操作なら、ワンショットCLI+スキルの方が軽くて速いというのが実感です。
そして形態がどちらであれ、エージェントに道具を持たせるときの設計原則は同じでした。
- 迷いを消す — 前提を先に渡し、判断の往復を消す
- 安全をコードで固定する — モデルの確認連発に頼らず、スクリプトが止める
- 詳細は困ったときだけ開く — 段階的開示で平常時を軽くする
モデルに考えさせる量を減らすほど、エージェントは速く・安く・確実になる。 Backlog×AIエージェントで消耗している方は、ぜひ試してみてください。フィードバックも歓迎です。