ModsCode日本語ブログ Claude から Mod のコード例を探す

ModsCode / 日本語ブログ / 初めての Claude Code Mod の作り方と確認手順

初めての Claude Code Mod の作り方と確認手順

Claude Code の Mod を 3 ファイルで作り、スピナーにツールの使用回数を出して /toolcount コマンドを足すまでを手順どおりに解説。validate と test で確認済みのコード付きです。

この記事では、Claude Code の Mod を 1 から作ります。作るのは tool-counter という小さな Mod で、Claude が使ったツールの回数をスピナーの横に出し、/toolcount と打つとツールごとの回数を表示します。コードは Claude Code 2.1.289 の claude plugin validate と claude plugin test で確認したものです。

公式ドキュメントにも同じ流れのチュートリアルがあります(Mod を作成する)。ここでは、公式の例とは別のコードを使い、つまずきやすい所と、書いた後の確認方法を厚めに説明します。

作り方は 2 通りある

  • Claude に頼む:Claude Code に「現在の Git ブランチをプロンプトの上に出す Mod を作って」のように頼みます。Claude が plugin-authoring スキルを使い、セッション専用のフォルダ(~/.claude/dev-mods/ の下)に Mod を書きます。最初のファイルを保存するとき、ホットリロードを有効にするか聞かれます。
  • 自分で書く:以下の手順です。Node.js もバンドラーもビルドも不要で、Claude Code が .js と .ts を直接読み込みます。

Claude が書いた Mod は、書かれたセッションの中だけで読み込まれます。残したいときは、そのフォルダを ~/mods/ のような自分の場所へコピーし、claude --plugin-dir ~/mods/名前 で読み込むか、マーケットプレイスに入れます。

用意するもの

ターミナルの Claude Code が v2.1.287 以降であることが必要です。claude --version で確認できます。デスクトップアプリでは、同梱の Claude Code が v2.1.286 以降なら使えます。

手順 1:フォルダを作る

Mod のフォルダと、その中の 2 つのフォルダを作ります。Bash や Zsh では次のとおりです。

mkdir -p tool-counter/.claude-plugin tool-counter/hooks

PowerShell(Windows)では次のとおりです。

New-Item -ItemType Directory -Force tool-counter\.claude-plugin, tool-counter\hooks

手順 2:plugin.json を書く

Mod はプラグインなので、名前とバージョンを書いた説明ファイルが必要です。tool-counter/.claude-plugin/plugin.json に保存します。

tool-counter/.claude-plugin/plugin.json
{
  "name": "tool-counter",
  "version": "0.1.0",
  "description": "Shows how many tools Claude has used beside the spinner, and adds a /toolcount command",
  "author": { "name": "Your Name" }
}

名前を決めるときは、claude- で始まる名前のように Anthropic 自身のものに見える名前を避けてください。claude plugin validate が名前を断ります。

手順 3:hooks.json でコードの場所を教える

tool-counter/hooks/hooks.json に、コードのファイルを 1 つだけ指定します。パスは hooks.json から見た相対パスです。この modules があることで、プラグインが Mod になります。

tool-counter/hooks/hooks.json
{
  "description": "The tool-counter hooks module",
  "modules": ["./register.js"]
}

手順 4:コードを書く

tool-counter/hooks/register.js が本体です。Claude Code は Mod を読み込むとき、このファイルが書き出す register 関数を 1 回呼び、on という関数を渡します。on を呼ぶたびに、フック(イベントハンドラ)が 1 つ登録されます。

tool-counter/hooks/register.js
// ツールごとの回数。下の複数のフックが同じ変数を使う
const counts = {}

function total() {
  return Object.values(counts).reduce((sum, n) => sum + n, 0)
}

export function register(on) {
  // セッション開始時に /toolcount を登録する
  on('session.start', async ($, e, next) => {
    await $.command.register({
      name: 'toolcount',
      description: 'Claude が使ったツールの回数を表示する',
    })
    return next(e)
  })

  // Claude がツールを使う直前に呼ばれる。数えるだけで、ツールはそのまま動かす
  on('tool.call', async ($, e, next) => {
    counts[e.tool] = (counts[e.tool] ?? 0) + 1
    $.ui.invalidate('ui.render')
    return next(e)
  })

  // /toolcount のときだけ呼ばれる(matcher で絞っている)
  on('command.run', { command: 'toolcount' }, async () => {
    const lines = Object.entries(counts).map(([tool, n]) => tool + ': ' + n)
    return { text: lines.length ? lines.join(' / ') : 'まだツールは使われていません' }
  })

  // スピナーの横に合計を足す。スピナー自体は Claude Code のものを使う
  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
    if (total() === 0) return next(e)
    return next({ ...e, props: { ...e.props, suffix: ' · ツール ' + total() + ' 回…' } })
  })
}

フックは 4 つです。session.start はセッション開始時に 1 回呼ばれ、コマンドを登録します。tool.call は Claude がツールを使う直前に呼ばれ、回数を数えて画面の再描画を頼みます。command.run は /toolcount を打ったときだけ呼ばれ、文字を返します。ui.render はスピナーを描くたびに呼ばれ、合計を足します。

手順 5:読み込んで試す

--plugin-dir を付けて Claude Code を起動すると、インストールせずにそのフォルダを 1 セッションだけ読み込みます。

claude --plugin-dir ./tool-counter

「このフォルダのファイルを一覧にして README を読んで」のように、ツールを何回か使う依頼をしてください。Claude が作業している間、スピナーの横に ツール 2 回… のように数が増えていきます。終わったら /toolcount と打つと、Bash: 1 / Read: 1 のようにツールごとの回数が出ます。

補足:スピナーに文字を足す仕組み(Spinner の suffix)は、公式ドキュメントのチュートリアルと同じものです。この記事のコードで確認したのは claude plugin validate と claude plugin test で、実際の画面の見た目は、お使いの環境で確かめてください。

/toolcount が候補に出ないときは、Mod が読み込まれていません。Mod が動かないときの確認手順 を見てください。

手順 6:書き換えて、すぐ反映する

セッションを開いたまま register.js の ' · ツール ' を別の言葉に変えて保存してください。Claude Code は --plugin-dir で読み込んだフォルダを監視していて、ファイルが変わると自動で読み込み直します。トランスクリプトに Mod が再読み込みされたことと、そのフックの一覧が出て、次のスピナーから新しい文字になります。

再読み込みのたびに register がもう一度呼ばれるので、モジュールの変数(counts)は 0 に戻ります。回数を残したいときは、$.store(セッションをまたいで残る保存場所)か $.state を使います。

コードの読み方:$・e・next

どのフックも、同じ 3 つの引数を受け取ります。

  • $(Mods API):画面に描く、コマンドを足す、ファイルを読む、プログラムを動かすなど、Mod が外へ働きかける手段は、すべてここにあります。$.ui や $.command のように名前空間に分かれています。
  • e(イベント):ツール名や引数のような、イベントの中身です。書き換えはできない読み取り専用のデータです。
  • next:イベントを次のハンドラ(ほかの Mod、最後は Claude Code 自身の動き)に渡す関数です。

フックが next をどう使うかで、3 つの動きに分かれます。

  1. 観察する:自分の処理をして return next(e) で通す。上の tool.call がこれです。
  2. 書き換える:変えた複製を next({ ...e, ... }) に渡す。上の ui.render がこれです。
  3. 代わりに答える:next を呼ばずに結果を返す。上の command.run がこれです。

型定義ファイルを使う

--plugin-dir で読み込んだ Mod(または Claude が書いた Mod)のフォルダには、Claude Code が .claude-plugin/types/ に型定義ファイルを書き出します。使っているバージョンのイベント、メソッド、描ける要素がすべて入っているので、エディタで補完と型検査ができます。イベントやメソッドはバージョンで変わるので、記事やドキュメントと食い違うときは、このファイルを信じてください。

書いた後に validate で確認する

claude plugin validate は、Claude Code が Mod を読むときと同じ静的な解析を、セッションを起動せずに実行します。

claude plugin validate ./tool-counter

tool-counter では、次の行が出ます。

validate の出力(抜粋)
  ❯ ./register.js hooks: session.start, tool.call, command.run{command=toolcount}, ui.render{component=Spinner}
  ❯ ./register.js calls: $.command.register, $.ui.invalidate

✔ Validation passed

hooks: は Mod が受け取るイベント(絞り込みは {} の中)、calls: は呼ぶ Mods API です。ここに自分が書いたはずのイベントがなければ、そのフックは呼ばれません。たとえばイベント名を tool.calls と書き間違えると、"tool.calls" is not an event というエラーで失敗します。

静的な解析が読み取れるように、次の規則を守ってください。

  • Mods API は $.store.get('notes') のように、$・名前空間・メソッドをそのまま書く。const ui = $.ui と変数に入れると失敗します。
  • on に渡すイベント名は、文字列そのもの(リテラル)で書く。変数やループで作ると失敗します。
  • register の中で、on という名前の変数を宣言し直さない。
  • 読み込むのは、プラグインのフォルダの中のファイルだけで、相対パスの import 宣言で書く。動的な import() や require は使えません。

インストールしたあとは、編集が反映されない

マーケットプレイスからインストールした Mod は、Claude Code がバージョンごとにキャッシュしたコピーを動かします。元のフォルダを編集しても反映されません。開発中は常に --plugin-dir で作業フォルダを指し、配るときにバージョンを上げて入れ直してください。

次のステップ

自動テストを書けば、セッションを起動しなくても動作を確かめられます。手順は Mod のテストの書き方 にあります。実用的な Mod の例は、危険なコマンドを止める Mod と 画面に状態を表示する Mod が続きます。他の人が書いた実際のコードを見たいときは、ModsCode の MCP が、パターン別に本物のコードを返します。