3

ツールシステム

RouterHandlersToolstrait ToolHandler: Send + Syncfn handle(invocation) -> ToolOutputToolRegistry.dispatch()name -> handler lookupFunctionHandlerToolKind::FunctionMcpHandlerToolKind::McpShelllocal_shellApplyPatchapply_patchReadFileread_fileListDirlist_dirGrepgrep_searchMCP Toolsexternal serversDynamicruntime registered

ツールシステムの設計思想

Codex CLI のツールシステムは trait-based registration パターンで設計されている。各ツールは ToolHandler trait を実装し、ToolRegistry に登録される。この設計により、新しいツールの追加が容易で、型安全性が保証される。

ToolHandler trait

#[async_trait]
pub trait ToolHandler: Send + Sync {
    fn kind(&self) -> ToolKind;

    fn matches_kind(&self, payload: &ToolPayload) -> bool {
        matches!(
            (self.kind(), payload),
            (ToolKind::Function, ToolPayload::Function { .. })
                | (ToolKind::Mcp, ToolPayload::Mcp { .. })
        )
    }

    /// Returns `true` if the ToolInvocation *might* mutate the
    /// environment of the user (file system, OS operations, ...).
    async fn is_mutating(
        &self, _invocation: &ToolInvocation
    ) -> bool {
        false
    }

    /// Perform the actual ToolInvocation and returns a ToolOutput.
    async fn handle(
        &self, invocation: ToolInvocation
    ) -> Result<ToolOutput, FunctionCallError>;
}

trait の設計ポイント

  • kind(): ツールの種別(Function / Mcp)を返す。ペイロードの型との整合性チェックに使用
  • is_mutating(): ツールが環境を変更する可能性があるかを判定。変更を伴うツールは tool_call_gate による逐次制御を受ける
  • handle(): 実際のツール実行ロジック。非同期で ToolOutput を返す

ToolRegistry

ToolRegistry はツール名からハンドラーへのマッピングを保持し、ディスパッチを行う。

pub struct ToolRegistry {
    handlers: HashMap<String, Arc<dyn ToolHandler>>,
}

impl ToolRegistry {
    pub fn new(
        handlers: HashMap<String, Arc<dyn ToolHandler>>
    ) -> Self {
        Self { handlers }
    }

    pub fn handler(
        &self, name: &str
    ) -> Option<Arc<dyn ToolHandler>> {
        self.handlers.get(name).map(Arc::clone)
    }

    pub async fn dispatch(
        &self,
        invocation: ToolInvocation,
    ) -> Result<ResponseInputItem, FunctionCallError> {
        let tool_name = invocation.tool_name.clone();
        let handler = match self.handler(tool_name.as_ref()) {
            Some(handler) => handler,
            None => {
                return Err(FunctionCallError::RespondToModel(
                    unsupported_tool_call_message(
                        &invocation.payload, tool_name.as_ref()
                    )
                ));
            }
        };
        // ... validation, execution, hooks
    }
}

ToolRegistryBuilder

ツールの登録は ToolRegistryBuilder を介して行われる。

pub struct ToolRegistryBuilder {
    handlers: HashMap<String, Arc<dyn ToolHandler>>,
    specs: Vec<ConfiguredToolSpec>,
}

impl ToolRegistryBuilder {
    pub fn register_handler(
        &mut self,
        name: impl Into<String>,
        handler: Arc<dyn ToolHandler>,
    ) {
        let name = name.into();
        if self.handlers
            .insert(name.clone(), handler.clone())
            .is_some()
        {
            warn!("overwriting handler for tool {name}");
        }
    }

    pub fn build(self) -> (Vec<ConfiguredToolSpec>, ToolRegistry) {
        let registry = ToolRegistry::new(self.handlers);
        (self.specs, registry)
    }
}

ToolPayload: ツール呼び出しの種類

pub enum ToolPayload {
    Function { arguments: Value },
    Custom { input: Value },
    LocalShell { params: LocalShellParams },
    Mcp { server: String, tool: String, raw_arguments: Value },
}

ツールの種類に応じて 4 つのペイロード型がある。

  • Function: JSON Schema ベースの関数呼び出し
  • LocalShell: シェルコマンドの実行
  • Mcp: MCP サーバー経由のツール呼び出し
  • Custom: カスタムツール

並列ツール実行

ツール実行における並列性は ConfiguredToolSpec で制御される。

#[derive(Debug, Clone)]
pub struct ConfiguredToolSpec {
    pub spec: ToolSpec,
    pub supports_parallel_tool_calls: bool,
}
  • supports_parallel_tool_calls: true のツールは並列実行可能
  • 変更を伴うツール (is_mutating() == true) は tool_call_gate によってシリアライズされる
if is_mutating {
    tracing::trace!("waiting for tool gate");
    invocation_for_tool.turn.tool_call_gate
        .wait_ready().await;
    tracing::trace!("tool gate released");
}

これにより、読み取り専用のツール(ReadFileGrep など)は並列実行しつつ、ファイル書き込みやシェルコマンドは安全に逐次実行される。

組み込みツール一覧

Codex CLI は以下の組み込みツールを提供する。

ツール名機能変更操作
local_shellシェルコマンド実行Yes
apply_patchファイルのパッチ適用Yes
read_fileファイル読み取りNo
list_dirディレクトリ一覧No
grepパターン検索No
write_fileファイル書き込みYes
view_image画像表示No

MCP (Model Context Protocol) 統合

Codex CLI は MCP (Model Context Protocol) を通じて外部ツールサーバーとの統合をサポートする。

MCP ツールの登録

MCP サーバーから公開されたツールは ToolKind::Mcp として登録される。

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ToolKind {
    Function,
    Mcp,
}

MCP 命名規則

MCP ツールは server_name__tool_name の命名規則に従う。サーバー名とツール名をダブルアンダースコアで結合することで、名前の衝突を回避する。

Hooks 統合

ツール実行の前後にフック処理が実行される。

async fn dispatch_after_tool_use_hook(
    dispatch: AfterToolUseHookDispatch<'_>,
) -> Option<FunctionCallError> {
    let tool_input = HookToolInput::from(&invocation.payload);
    let hook_outcomes = session
        .hooks()
        .dispatch(HookPayload {
            hook_event: HookEvent::AfterToolUse {
                event: HookEventAfterToolUse {
                    tool_name: invocation.tool_name.clone(),
                    tool_kind: hook_tool_kind(&tool_input),
                    tool_input,
                    executed: dispatch.executed,
                    success: dispatch.success,
                    // ...
                },
            },
        })
        .await;
    // ...
}

フックの結果により、ツール実行を中断 (FailedAbort) することも可能である。これにより、組織のセキュリティポリシーに応じたカスタムガードレールを実装できる。