Skip to content

ToolExecutorCoordinator:ツールルーティングテーブル

源码版本v4.0.10

役割

ToolExecutorCoordinator は Cline の「ツール名 → ハンドラ (handler)」ルーティングテーブルである。LLM が出力する tool_use block には name フィールド (例: read_filewrite_to_fileuse_mcp_tool) が付いており、Coordinator はこの名前をもとに対応する handler インスタンスを探し出し、実行権を渡す。Coordinator 自身は一切の業務ロジックを持たず、登録・検索・転送の三つだけを行う。

これは ToolExecutor と各 handler の間に位置する。ToolExecutor は Task が唯一呼ぶツール入口 (executeTool:212) であり、拒否チェック、plan mode 制限、partial/complete の振り分け、PostToolUse hook といった共通動作を処理した後、個々のツールをどう実行するかは Coordinator に委譲する (coordinator.execute:575)。Coordinator 内部では Map<string, IToolHandler> でレジストリを管理しており、見つからなければ No handler registered for tool を投げる。

もう一つの特殊な役割は、MCP ツール名の正規化と動的 subagent ツールの遅延インスタンス化である。MCP ツール名は mcp__server__tool の形式をとり、CLINE_MCP_TOOL_IDENTIFIER プレフィックスを持つものは UseMcpToolHandler に畳み込まれる (mcp normalize:137)。動的 subagent ツール名が AgentConfigLoader に登録済みであれば、SharedToolHandlerUseSubagentsToolHandler をくるんで動的テーブルに注入する (dynamic subagent:146)。

設計動機

  • handler テーブルで巨大 switch を置換: 旧バージョンの ToolExecutor は数百行の switch 文で、ツールを追加するたびに主ファイルを書き換える必要があった。Coordinator はこの表を toolHandlersMap として切り出し、新規ツールの追加は handler クラスを書いて map に一行追加するだけで済む (toolHandlersMap:79)。
  • 直接インスタンスではなくファクトリ関数: map が保持するのはインスタンスではなく (v: ToolValidator) => IToolHandler | undefined ファクトリである。registerByName のタイミングで初めてインスタンスを生成することで、ToolValidator を生成時に注入できる (registerByName:118)。
  • undefined 返却は合法値: TODO (focus_chain) に対応するファクトリは undefined を返す (todo undefined:97)。これは「このツール名は当面 handler を受け付けない」ことを意味し、上位の has チェックは未登録と判定する。
  • SharedToolHandler で実装を再利用: 複数のツール名が同一の handler 実装を共有する場合 (例: write_to_filereplace_in_filenew_rule はすべて WriteToFileToolHandler を使用)、ラッパークラスでベース handler をくるみ、name フィールドだけを差し替える (SharedToolHandler:52)。これにより同じクラスを三つ書く手間を省く。
  • MCP 名前正規化を前置: MCP ツール名は動的に変化するが、実際の振る舞いは一種類 (MCP server の呼び出し) しかない。getHandler の入口で CLINE_MCP_TOOL_IDENTIFIER を含むすべての名前を MCP_USE に畳み込むことで、一つの handler で全 MCP ツールを賄える (mcp normalize:137)。

主要ファイル

  • IToolHandler:34 — handler インターフェース: nameexecutegetDescription の三点セット。
  • IPartialBlockHandler:40 — ストリーミング partial block 向けインターフェース。handler は任意実装。
  • IFullyManagedTool:44 — complete と partial の両インターフェースを実装するマーカー型。
  • SharedToolHandler:52 — ラッパークラス。一つの handler 実装を複数のツール名に紐付けられる。
  • ToolExecutorCoordinator:75 — クラス定義。handlersdynamicSubagentHandlers の二つの Map を保持する。
  • toolHandlersMap:79 — 静的レジストリ。ClineDefaultTool 列挙値 → ファクトリ関数。
  • register:114 — handler インスタンスを name をキーに handlers Map に格納。
  • registerByName:118 — 列挙値を受け取り、map からファクトリを取り出してインスタンスを生成し register する。
  • has:128 — ツール名が登録済みかを判定。内部では getHandler を呼び出して非 null かを見る。
  • getHandler:135 — 検索ロジック: MCP 正規化 → 静的テーブル → 動的 subagent。
  • dynamic subagent branch:146 — 動的 subagent ツール向けに SharedToolHandler(UseSubagentsToolHandler) を遅延生成。
  • execute:162 — handler 取得後に handler.execute(config, block) を呼ぶ。自身は try/catch しない。
  • registerToolHandlers:201 — ToolExecutor のコンストラクタで toolUseNames を走査して全登録。
  • toolUseNames:40ClineDefaultTool 列挙から生成した全ツール名配列。登録ループを駆動する。

データフロー

登録フェーズは ToolExecutor のコンストラクト時に一回だけ行われ、実行フェーズでは各 tool_use block が lookup + execute を一度ずつ通る。登録ループは短い:

typescript
// apps/vscode/src/core/task/ToolExecutor.ts
private registerToolHandlers(): void {
    const validator = new ToolValidator(this.clineIgnoreController)
    // Register all tools via toolUseNames
    for (const tool of toolUseNames) {
        this.coordinator.registerByName(tool, validator)
    }
}

toolUseNamesClineDefaultTool 列挙から自動生成される (toolUseNames:40)。列挙値を一つ追加すれば自動的に登録ループに入るが、前提として toolHandlersMap の対応行にもファクトリを書く必要がある。Validator は ClineIgnoreController から生成され、file/path 系 handler はすべてアクセスチェックにこれを使う (ToolValidator:10)。

実行フェーズの検索ロジック:

typescript
// apps/vscode/src/core/task/tools/ToolExecutorCoordinator.ts
getHandler(toolName: string): IToolHandler | undefined {
    // HACK: Normalize MCP tool names to the standard handler
    if (toolName.includes(CLINE_MCP_TOOL_IDENTIFIER)) {
        toolName = ClineDefaultTool.MCP_USE
    }

    const staticHandler = this.handlers.get(toolName)
    if (staticHandler) {
        return staticHandler
    }

    if (AgentConfigLoader.getInstance().isDynamicSubagentTool(toolName)) {
        const existingHandler = this.dynamicSubagentHandlers.get(toolName)
        if (existingHandler) {
            return existingHandler
        }
        const handler = new SharedToolHandler(toolName as ClineDefaultTool, new UseSubagentsToolHandler())
        this.dynamicSubagentHandlers.set(toolName, handler)
        return handler
    }

    return undefined
}

三段階の検索: MCP 名を正規化した上で静的テーブルを引く。ヒットしなければ動的 subagent ツールか確認し、そうであれば SharedToolHandlerUseSubagentsToolHandler を包んで動的テーブルに登録して返す。どちらもヒットしなければ undefined を返し、上位の ToolExecutor.execute は未登録と判定して旧分岐へ進む (has check:316)。動的 subagent テーブルを別 Map にしているのは、その name が実行時文字列で ClineDefaultTool 列挙に入れられないためである。

境界と失敗

  • 未登録時の例外送出: executegetHandler で取れない場合、No handler registered for tool: ${block.name} をそのまま投げる (throw on missing:165)。ただし実際には上位の ToolExecutor.executehas で事前チェックしており、ここに到達した時点で必ずヒットするため、この例外は安全網である。
  • TODO ツールは handler を紐付けない: ClineDefaultTool.TODO (focus_chain) のファクトリは undefined を返す (todo undefined:97)。registerByName は undefined を見ると登録をスキップし、結果として「このツール名は Coordinator が管理しない」ことになり、focus chain モジュールが別途処理する。
  • MCP 名正規化は HACK: コメントにも HACK と明記されている (hack comment:136)。MCP ツール名に CLINE_MCP_TOOL_IDENTIFIER 部分文字列が含まれていれば正規化する仕様であり、server 名と tool 名のどちらにもこの識別子が含まれないことに依存している。
  • 動的 subagent handler はクリーンアップされない: dynamicSubagentHandlers Map は増えるだけで削除されず、ライフサイクルは Coordinator と一致する (dynamic cache set:152)。Coordinator は ToolExecutor と、ToolExecutor は Task とライフサイクルを共有するため、一つの Task が全ライフサイクルで出会ったすべての subagent ツール名がキャッシュされる。
  • ファクトリは呼び出しごとに新インスタンスを生成: registerByName はファクトリを呼ぶたびに新しい handler インスタンスを生成する (factory call:119)。ただし toolUseNames には重複がないため、実際には各 name は一度しか登録されず、インスタンスも一つしかできない。
  • SharedToolHandler は state を共有しない: SharedToolHandlerbaseHandler をプライベートフィールドに保持し、すべての呼び出しを同一のベースインスタンスへ転送する (shared execute:62)。したがって write_to_filereplace_in_filenew_rule の三つの名前は実質的に一つの handler インスタンスであり、内部状態は共有される。

まとめ

Coordinator はツール名と実装を疎結合にし、新規ツールの追加が map の一行変更と handler クラス一つで済むようにする。検索は三層構造: MCP 正規化、静的テーブル、動的 subagent。handler の中身や登録の様子を見るには /tools/handlers-overview へ、実行前の権限・パラメータ検証を見るには /tools/validator へ。

公式資料: Cline 文档 · README