カスタムシェルツールを使う
ローカルコマンドを MCP なしのモデル呼び出しツールとして公開する方法。
lait.config.yml の tools: に、ローカルのコマンドをそのままモデルから呼び出せるツールとして
定義できます。MCP サーバーを書いたり立てたりしなくても、rg/jq/gh のような 1 コマンドだけを
モデルに使わせたいときに使います。MCP サーバーのツール・サブエージェント
と同列の tool loop で扱われ、tool_policy/--approve-tools の対象にもなります。
# lait.config.yml
default:
model: local
tools: [ripgrep] # 全経路(チャット / agent / workflow)の最終フォールバック
tools:
ripgrep:
description: "リポジトリ内をパターン検索する"
command: ["rg", "--json", "{{ input.pattern }}"]
parameters:
type: object
properties:
pattern: { type: string, description: "検索パターン" }
required: [pattern]
timeout: 10
cargo run -- "TODO を検索して" --tool ripgrep
tools: の登録(lait.config.yml)
各エントリは次のフィールドを持ちます。
| フィールド | 説明 |
|---|---|
command(必須) |
実行するコマンドの argv。command[0] がプログラム、command[1..] が引数です。シェルは介さず直接 exec されます(;/` |
description(任意) |
モデルに見せるツールの説明。省略もできますが、モデルが正しく使うためには書いておくことを推奨します。 |
parameters(任意) |
モデルの呼び出し引数を表す JSON Schema。OpenAI のツール定義にそのまま渡されます。省略時は引数なしのツール({"type":"object","properties":{}})として扱われます。JSON オブジェクトである必要があります。実行前に required/type/enum と、properties/items の入れ子を検証します。これはよく使う制約を検証するもので、完全な JSON Schema 実装ではありません。 |
timeout(任意、秒) |
コマンドの実行時間の上限。省略時は 30 秒です。超過すると呼び出しはタイムアウトエラーとして扱われます。 |
env(任意) |
コマンドの子プロセスに渡す環境変数の許可リスト。1件以上指定すると、子プロセスはここに列挙した変数だけを見ます — lait 自身が親プロセスから継承している環境変数(API キーなどの秘密情報を含みうる)は一切引き継がれません。省略時(既定)は、従来どおり親プロセスの環境変数をそのまま継承します。各値は mcp_servers[] の env/cwd と同じく ${VAR_NAME} で環境変数を参照できます。 |
cwd(任意) |
コマンドの作業ディレクトリを固定します。省略時(既定)は lait 自身のカレントディレクトリを継承します。${VAR_NAME} 展開に対応します。 |
tools:
probe:
command: ["sh", "-c", "echo $SAFE_TOKEN"]
env:
PATH: "${PATH}" # command[0] を名前解決するために必要(下記の注意を参照)
SAFE_TOKEN: "${SAFE_TOKEN}"
cwd: "/tmp/lait-sandbox"
注意: env を1件でも指定すると環境変数は完全な許可リストになるため、command[0] が絶対パス
でない場合は PATH も明示的に含めてください。含めないと、コマンド自体が見つからずエラーになります
(PATH 自体は機密情報ではないので、多くの場合そのまま ${PATH} として渡して構いません)。
標準出力と標準エラー出力はそれぞれ 16 MiB までを保持します。どちらかが上限を超えるとコマンドの プロセスツリーを終了して回収し、ツールの結果にエラーを返します。これにより大量出力で tool loop が メモリを使い続けることを防ぎます。
同じ env/cwd による封じ込めは、ワークフローの run: ステップ
(ワークフロー)でも指定できます。
引数のテンプレート展開
command の各要素は、モデルが渡した JSON 引数を input として、ワークフローの
prompt:/command: と同じ handlebars テンプレートで展開されます。モデルの呼び出し引数が
{"pattern": "TODO"} なら、{{ input.pattern }} は TODO に展開されます。steps/vars は
空のまま渡されるため参照できません。
引数は JSON オブジェクトで指定します。空文字列または null は空のオブジェクト({})として扱い、
配列・文字列・数値・真偽値などはコマンドを実行せずエラーにします。JSON Schema の実行時検証に失敗
した場合もコマンドは実行せず、その理由をツール結果としてモデルに返します。
各経路での指定方法
tools:(使うツール名のリスト)は、mcp:/subagents: と全く同じ経路でフォールバックします。
| 経路 | 優先順位 |
|---|---|
チャット(lait "prompt") |
--tool(CLI フラグ、複数指定可)→ lait.config.yml の default.tools |
lait agent run |
agent ファイルの frontmatter tools: → lait.config.yml の default.tools |
lait run(workflow) |
ステップの tools: → (agent: ステップなら)エージェント定義の tools: → ワークフローの default.tools → lait.config.yml の default.tools |
lait "prompt" --tool ripgrep --tool jq
---
model: local
tools: [ripgrep]
---
{{ input.task }} を実行してください。
# workflow.yml
steps:
- prompt: "{{ input }} について調べてください。"
tools: [ripgrep]
ツール名の扱い
モデルに渡すツール名は tool__<名前>(例: tool__ripgrep)の形に修飾されます。MCP の
<サーバー名>__<ツール名>、サブエージェントの agent__<名前> と同じ規則です。MCP・サブエージェント・
シェルツールの間で修飾後の名前が衝突した場合はエラーになります。
tool_policy/--approve-tools との関係
シェルツールも tool_policy(allow/deny)と --approve-tools(対話的承認)
の対象です。修飾済みツール名(tool__ripgrep など)でパターンを書いてください。
tool_policy:
deny: ["tool__*"] # シェルツールを一律禁止する例
--approve-tools の確認プロンプトでは、シェルツールに限り「モデルが渡した JSON 引数」に加えて
「command: テンプレートを実際に展開した後の argv」も表示されます。command: は引数をそのまま
渡すとは限らず、より大きなシェルコマンドの一部に埋め込むといった変換をしうるため、JSON 引数だけ
見て承認すると実際に実行される内容と食い違うおそれがあるからです。
同じ応答に複数の tool call がある場合は最大 8 件を並行して実行し、モデルが指定した順番で結果を 返します。
エラー処理
コマンドの終了コードが非 0、またはタイムアウトした場合、ツール呼び出し全体は失敗しません。 標準エラー出力を含むエラー文字列がツールの実行結果としてモデルに返され、tool loop は継続します (MCP ツールの失敗と同じ扱いです)。一方、モデルが渡した引数が JSON オブジェクトとして解釈できない 場合や、未知のツール名を呼び出した場合はリクエスト全体がエラーになります。 ユーザーのキャンセルは中断エラーとして上位へ伝播し、ツールの通常結果文字列には変換されません。
lint
lait lint は次を静的にチェックします。
tools:を参照するdefault.tools/ステップのtools:/エージェント定義のtools:がlait.config.ymlのtools:に実在するか- 各
tools:エントリのcommandが空でないか、parametersが JSON オブジェクトかどうか (参照されているかどうかに関わらず、定義されている全エントリをチェックします)