mcpField
T
Tool Guardian

Tool Guardian

ローカル
github.com/LuminariSoftwaresAI・エージェント26 日前に更新

Tool Guardian について

Tool Guardian は、複数の MCP サーバーを束ねるルーターです。バックエンドのツール定義を要求時に取得し、長い結果をアーカイブしてコンテキストの使用を抑えます。

主な機能

  1. 01ツールのオンデマンド検索
  2. 02バックエンドツールの検索と説明
  3. 03バックエンドツールへの呼び出し転送
  4. 04ツールグループのトークンコスト測定
  5. 05長い結果のアーカイブ

活用シーン

  1. 01複数の MCP サーバーの定義トークン削減
  2. 02キーワードによるツール検索
  3. 03長いコマンド結果のコンテキスト占有防止

MCP 対応

他の MCP サーバーの前段に置く MCP サーバーです。クライアントはルーターツールを通じ、バックエンドのツールを必要に応じて一覧表示、検索、説明、呼び出しできます。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"luminarisoftwares-tool-guardian": {
"command": "uvx",
"args": [
"tool-guardian"
]
}
}
}
起動コマンドuvx tool-guardian

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
pypitool-guardian0.8.1

ツール

6

掲載時のツール一覧 · 5 日前に更新

パラメーター型必須説明
serverstringいいえサーバー名。省略するとすべてのサーバーが対象です

詳細

Tool Guardian

7つのMCPサーバーを、20,005トークンではなく1,084トークンで利用できます。ツールの結果がコンテキストウィンドウにあふれることもありません。

すべてのMCPサーバーの前段に置く、1つのMCPサーバーです。Claude Code、OpenCode、Cline、Continueでテスト済みです(各クライアントでスクリプト化したモデルを1ターン実行。詳細は対応状況をご覧ください)。Claude Desktop、Cursor、その他のstdio MCPクライアントは動作する想定ですが、まだテストしていません。

テスト プライバシー ライセンス:MIT Python 3.9+ 依存関係:なし MCPサーバー

mcp-name: io.github.LuminariSoftwares/tool-guardian

このリポジトリのベンチマークが、ここで示す数値の根拠です。公開MCPサーバー7つのtools/listの記録済み応答を対象に実行しました(ネットワークもモデルも使用せず、注記のある箇所は要約しています)。

$ python bench/bench_tokens.py
# Tool-Guardian Token Benchmark

Token counting method: estimate:chars/3.5

| server | tools | tokens (full schemas) |
...
| **Total** | **95** | **20005** |

**Every request:** 20005 tokens of tool schemas without tool-guardian, 1084 with it (94.6% less).

クイックスタート · 対応状況 · DeepSeek Harness · 両方をインストール · 最初の5分間 · 開発の背景 · 2つの実行方法 · アーキテクチャ · インストール · 削減量を測定 · 出力の段階 · DSHバンドル · 互換性 · 変更履歴

他のMCPサーバーの前段に置き、特定のツールを何十個も公開する代わりに、6つのルーターツール(スキルを使う場合は8つ)を公開するMCPサーバーです。4つの汎用ツールで背後にあるツールの一覧表示、検索、説明、呼び出しを行い、さらに2つのツールでツールグループのコストとアーカイブ済みの結果を扱います(ツールを参照)。残りのツールは必要に応じて検出するため、モデルが読む前にツール定義がコンテキストウィンドウを圧迫することはありません。

ルーターはモデルのバックエンドと通信しません。そのため、モデルの提供元がOllama、LM Studio、vLLM、llama.cppのどれであっても関係ありません。重要なのは、クライアントのモデルがツールを呼び出せることです(Ollamaで実行済み。他は動作する想定ですが、未テストです)。

Context Guardianと連携します。**Context Guardianはウィンドウがいっぱいになる前に会話を圧縮し、Tool Guardianはそもそもツールによってウィンドウがいっぱいになるのを防ぎます。**同じ問題を解決する、2つの要素です。

Tool Guardianなし使用時
32KモデルでMCPサーバー7つ(ベンチマーク)リクエストごとにスキーマが20,005トークン(ウィンドウの61%)1,084トークン(3%)。モデルが要求したときだけスキーマを取得します
エラーが1つある3,000行のビルドログ(ベンチマーク)115,945文字が会話に取り込まれます4,540文字。元の全文はアーカイブされ、呼び出し1回で取得できます
バックエンドの起動に失敗モデルが何事もなかったかのように対処する、空のツールリストUNKNOWNステータスと実際のエラー(例:could not start: …)を返し、空のリストにはしません

クイックスタート(60 秒)

インストールは不要です。uvx を使って PyPI から実行できます。以下の例のサーバーは MCP ファイルシステムサーバーで、npx を使って実行するため、この 1 行には Node.js が必要です。ルーター自体は Python 3.9 以降と標準ライブラリのみで動作します。

uvx tool-guardian --version
uvx --from tool-guardian tool-guardian-setup add files -- npx -y @modelcontextprotocol/server-filesystem "$HOME"
uvx tool-guardian --selftest
claude mcp add --scope user tool-guardian -- uvx tool-guardian   # or any client: see "Run" below

インストールして使いたい場合は、pip install tool-guardian を実行してから、uvx のプレフィックスを外してください(tool-guardian-setup add ...、tool-guardian --selftest)。

成功すると、added 'files' to ~/.tool-guardian/mcp.json と表示され、その後 --selftest で 1 backend(s) up, 14 tools reachable. と router tools cost ~N tokens vs ~M の行が表示されます。Claude Desktop、Cursor、Windsurf にすでにサーバーを設定していますか?tool-guardian-setup import を使うと、add の代わりに設定をコピーできます。クライアントで、サーバーのいずれかが対応する操作を依頼してください。モデルは list_capabilities(または search_capabilities)を呼び出してから、call_tool を実行するはずです。

実際の動作例

2 つのサーバー(files と git)を使った実際のセッションです。省略箇所にはその旨を記しています。モデルはカタログを取得し、対象のサーバーを絞り込み、ツールの引数を確認してから呼び出します。

> list_capabilities()
[files] 14 tools: read_file, read_text_file, read_media_file, read_multiple_files, write_file,
        edit_file, create_directory, list_directory, list_directory_with_sizes, directory_tree,
        move_file, search_files, get_file_info, list_allowed_directories
[git] 12 tools: git_status, git_diff_unstaged, git_diff_staged, git_diff, git_commit, git_add,
      git_reset, git_log, git_create_branch, git_checkout, git_show, git_branch

NEXT STEP: use search_capabilities with a keyword (or list_capabilities with one server name) to
see a tool's args, then call_tool.

> list_capabilities(server="files")
files.read_file: Read the complete contents of a file as text. DEPRECATED: Use read_text_file...
    args: path:string, ?tail:number, ?head:number
files.read_text_file: Read the complete contents of a file from the file system as...
    args: path:string, ?tail:number, ?head:number
... 11 more tools ...
files.list_allowed_directories: Returns the list of directories that this server is allowed to access....
    args: (no arguments)

NEXT STEP: you have not answered the user yet. Pick the tool above that does the job and use
call_tool with server files, that tool's name, and the args listed under it.

> describe_tool(server="files", tool="read_text_file")
files.read_text_file: Read the complete contents of a file from the file system as...
required args: path:string

NEXT STEP: use call_tool with server files, tool read_text_file, args: path:string, ?tail:number, ?head:number.

> call_tool(server="files", tool="read_text_file", args={"path": "project/README.txt"})
hello from tool-guardian

NEXT STEP: answer the user from this result.

その間、モデルのコンテキストに含まれていたのはルーター独自の 6 つのツールだけです。バックエンドの 26 個のツールが送信されることはありませんでした。

ルーターを使うべきでない場合

ルーターが適さないケースは 2 つあります。事前に知っておくほうが、後から気付くより手間がかかりません。

  • クライアントがすでにツールを検索してくれる場合。 一部のクライアント(および一部のモデル API)は、必要に応じてツール定義を自ら読み込みます。そうしたクライアントの下にルーターを配置すると、節約にならないうえに、中継が 1 つ増え、カタログも二重になります。
  • 小規模なサーバーを少数だけ使う場合。 ルーター独自のツールは tools/list で約 1,100 トークンを消費します。背後に置く小規模なサーバーが 1 つだけの場合、ほぼ損益分岐点です(導入前に --selftest が、実際に使用するサーバーに基づいて計算結果を表示します)。サーバーを追加するたびに節約効果が大きくなり、最も効果が重要な環境、つまり 32K のローカルモデルと複数のサーバーを使う場合に最大になります。

そもそもモデルがツールを確実に呼び出せない場合にも役立ちません。切り替える前に、モデル要件をご覧ください。

対応クライアント

pip install tool-guardianを実行し、クライアントにエントリを1つ追加してください。以下の例はすべて同じコマンドtool-guardianを起動します。このコマンドは~/.tool-guardian/mcp.jsonを読み込みます。すでに使用しているサーバーから内容を反映するには、tool-guardian-setup importを実行してください。

クライアント例設定先テスト済み
Claude Codeproject.mcp.json、フック:settings.json、メモclaude mcp add --scope user tool-guardian -- uvx tool-guardianを実行するか、プロジェクトの.mcp.jsonにコピーします(Claude Codeではプロジェクトのサーバーを一度承認する必要があります。このファイルはクライアントの設定であり、ルーターの設定ではありません)。フックは~/.claude/settings.jsonまたは.claude/settings.jsonに設定します2.1.296:全ターン、稼働中セッションでのフック
OpenCodeopencode.jsonプロジェクトのopencode.json、または~/.config/opencode/opencode.json1.18.35:全ターン
Clinecline_mcp_settings.jsonVS Code:Cline → MCP Servers → Configure MCP Servers。CLI:~/.cline/data/settings/cline_mcp_settings.jsonCLI 3.0.70:全ターン
Continuetool-guardian.yamlワークスペースの.continue/mcpServers/(IDE)、またはconfig.yamlのmcpServersエントリcn 1.5.47:全ターン(config.yaml内のエントリ)

「全ターン」の意味。 Linuxで2026-10-10に、各クライアントにこの例のtool-guardianエントリを変更せずに設定しました(Claude Codeではclaude mcp add-jsonを使用。OpenCodeとContinueではテスト用モデルプロバイダーを併せて追加。Clineではファイルをそのまま使用)。そして、スクリプトで制御されたモデル(tests/clients/mock_model.py。実際のモデルもAPIキーも使用しません)と通信させました。クライアントはtool-guardianのルーターツールをモデルに送信しました。モデルのlist_capabilities呼び出しはtool-guardianを経由してスタブMCPサーバーに届き、応答はモデルに返されました。tool-guardianの呼び出しログには、クライアント名(claude-code、opencode、@cline/core、continue-cli-client)が記録されました。再実行するには、pip install -e .を実行してからpython tests/clients/run_clients.pyを実行してください。これは接続設定を検証するものであり、お使いのモデルがルーターを利用することを保証するものではありません。モデルの要件をご覧ください。

稼働中セッションでのClaude Codeフック。 settings.jsonのフックコマンドに--mode denyを追加し(入力を記録するようラップして)、実行中のClaude Code 2.1.296セッション内で、ルーターツールを指定したBash呼び出しをブロックしました。Claude Codeにはフックの理由として、Use call_tool(server="probe", tool="tg_live_probe_tool", args={...}) instead of the shellと表示され、呼び出しログにはbypassの行が記録されました。Claude Codeから送信されたペイロードは、Claude Codeのフックドキュメントの例と並べて、テスト用フィクスチャになっています(tests/test_claude_code_hook.py)。

まだテストしていないもの:ClineのVS Code拡張機能(ファイル名と形式は同じですが、テストにはCline CLIを使用)、Continueの.continue/mcpServers/フォルダー(IDE拡張機能が読み込みます。cnが読み込むのはconfig.yamlのみ)、およびWindowsまたはmacOSでのクライアント実行(ユニットテストはCIですべてのOSで実行されます)。Windowsでクライアントがtool-guardianを見つけられない場合は、where tool-guardianで確認したフルパスをcommandに指定してください。

DeepSeek Harness の結果

DeepSeek Harness バンドル

Tool Guardian は、ネイティブの DeepSeek Harness バンドル dsh-tool-guardian としても提供されています(追加される機能)。以下の2つの数値は DSH の実行のみから得られたもので、このリポジトリ内のものでは再現できません。

  • 実行されたのではなく、テキストとして書かれたツール呼び出しで終了したセッション:7/16 → 0/32 — DeepSeek Harness(バージョン未記録)、qwen3-coder:30b、8件のタスク。dsh-tool-guardian 0.4.0-beta.2 で16セッション(8 × 2)、0.5.0-beta.1 で32セッション(8 × 4)。修復処理は DSH バンドル内でのみ実行。作者が測定。スクリプトと生の実行結果は未公開(CHANGELOG の 0.5.0-beta.1 の項目を参照)。他のセットアップでは未測定。
  • DSH の初回リクエスト: バンドルなしでは46個のツールと37,154文字のスキーマ、バンドルありでは22個のツールと18,503文字 — 1つの DSH セットアップで作者が測定。スクリプトは未公開。
# 1. Add both bundles to the DSH profile you use (`web` is the one `dsh web` uses)
dsh plugin --profile web add dsh-tool-guardian
dsh plugin --profile web add dsh-context-guardian

# 2. Tool Guardian: copy in the MCP servers you already use (Claude Desktop / Cursor / Windsurf / .mcp.json), then check them
cd ~/.dsh/profiles/web/node_modules/dsh-tool-guardian          # Windows: cd %USERPROFILE%\.dsh\profiles\web\node_modules\dsh-tool-guardian
npm run setup                  # asks before it writes ~/.tool-guardian/mcp.json, then prints a doctor report

# 3. Context Guardian: add its compaction row to your agent preset (a dry run until --apply)
cd ~/.dsh/profiles/web/node_modules/dsh-context-guardian       # Windows: cd %USERPROFILE%\.dsh\profiles\web\node_modules\dsh-context-guardian
npm run setup
npm run setup -- --apply

# 4. Start DSH and open a NEW session with the preset setup named
dsh web

そのセッションで /guardian と入力してください。Context Guardian のエンジンリビジョンと、モデルのコンテキストウィンドウが表示されます。次に /toolguardian と入力してください。Tool Guardian が起動した各 MCP サーバー、ルーターがリクエストごとに節約するトークン数、更新があるかどうかが表示されます。

DSH を使っていない場合は、Context Guardian のプロキシ(python context_guardian.py。README を参照)は、OpenAI 互換の CLI ならどれでも動作します。Tool Guardian は、Claude Code、Cursor、または任意の MCP クライアント向けの通常の MCP サーバーとして動作します(README を参照)。

初めて使う方は、最初の5分間を参照してください。何もインストールされていない状態から、DSH または Claude Code でモデルがルーターを使用していることを確認するまでを説明します。

なぜこれが必要なのか

MCP ツールの定義は、モデルがそれらを使うかどうかにかかわらず、リクエストのたびに再送信されます。少数のサーバーでも、常時数万トークンに達することがよくあります。最初のユーザーメッセージを送る前に、小規模なローカルモデルのコンテキストウィンドウの大部分を占めてしまうこともあります。実際のあるセットアップでは、7つの MCP サーバーだけで**28,689トークン、32Kのコンテキストウィンドウの87.6%**を占め、ほかのすべての処理に使える容量を固定的に圧迫していました。

現在、この問題に対処する方法は2つありますが、どちらにもコストがあります。

方法コスト
MCP サーバーの読み込みを減らすその機能をまったく使えなくなります
そのまま使う入力を始める前に、コンテキストウィンドウの3分の2が失われます

Tool Guardian は、どちらのコストもかからない第3の方法です。すべてのサーバーの前段に配置され、モデルには少数の小さなツールと、サーバー名を並べた1行のカタログだけを見せます(背後に7つのサーバーがある場合で1,084トークン。下記で測定)。ツールの完全なスキーマは、モデルが要求したときだけ取得されます。

list_capabilities(server?)      one line per tool — names and purpose
describe_tool(server, tool)     the full argument schema for ONE tool
call_tool(server, tool, args)   invoke it, return the result

検索インデックスと同じ考え方です。簡潔なカタログは常に表示し、詳細は必要に応じて取得します。

すべてのツールに MCP アノテーション(title、readOnlyHint、destructiveHint、idempotentHint、openWorldHint)が付いています。call_tool 以外はすべて読み取り専用です。call_tool はバックエンドツールの処理をそのまま実行するため、破壊的かつオープンワールドとしてマークされています。DSH バンドルの tg__* 直接ツールにも同じ値が設定されています。

モデル要件(切り替える前にお読みください)

設計全体の前提となる動作は一つです。モデルはツールが必要になったとき、積極的に list_capabilities を呼び出し(続いて call_tool を呼び出す)必要があります。 高性能モデルやフロンティアモデルは、これを確実に実行します。小型のローカルモデルでは確実性が低く、以下の2つの測定結果が示すとおり、モデルが実行されるハーネスも、モデルと同じくらい重要です。

測定1 — 2026年8月、OpenClaude、MCP 上にマウントしたルーター。 実際の複数サーバー構成で、gpt-oss:20b と qwen3-30b-a3b は、通常のタスクでどちらもルーターを迂回しました。すべての結果に NEXT STEP の促しを含め、専用のルーターサブエージェントに事前準備させた場合でも同様でした。モデルはツール名をシェルコマンドとして扱ったり、スクリプトで回避したりしました。これはモデルだけでなく、ハーネスにも原因がありました。gpt-oss:20b は list_capabilities を正しく呼び出しましたが、一般的なタスク内でその発見結果を call_tool につなげられませんでした。また、一連の処理を完了した唯一の方法であるサブエージェントは、ルーターに到達する前に、ハーネス自体のエージェントツール引数の検証でブロックされました。

測定2 — 2026-09-19 と 2026-09-21、DeepSeek Harness、DSH バンドルによってルーターツールをネイティブ登録。 同じモデルファミリー(qwen3:30b-a3b-instruct-2507、32K コンテキストウィンドウ)は、迂回せずにルーターを使用しました。 09-19 は、プロンプトでツール名を指定した2つのセッションです。list_capabilities を1回、その後 list_capabilities ×8 → call_tool(ops-scripts, service_status)。09-21 は、ツール名を指定しない通常のプロンプトを3つ含む1つのセッションです(「現在、プロジェクトのどのサービスが稼働していますか?」「n8n ワークフローはいくつあり、最近編集されたものはどれですか?」「ブリッジのコミットで古いバイト列が書き込まれた前回の事例から、何が分かりましたか?」)。呼び出しログには、list_capabilities(jobs) → call_tool(jobs, pipeline_status)、続いて call_tool(n8n, list)(名前が誤っており、ok: false)→ call_tool(n8n, n8n_list_workflows)(ok: true、ラダーによって整形された12,963文字)が記録されています。モデルはルーターのエラーを受けて自己修正しました。また、bypass の行はゼロでした。3つ目のプロンプトには、コンパニオンプラグインの recall/search と組み込みの glob/read を使って回答しました。これは正しいツールの使用であり、迂回ではありません。これは3つのセッションと1つのモデルによる結果です。測定1がこれらのモデルについての最終評価ではないことを示すには十分ですが、お使いの環境でも同じ動作をすると保証するには不十分です。

つまり、--selftest で確認できるのは、節約効果とバックエンドが起動することです。お使いのハーネスでモデルがルーターを操作できることは確認できません。 導入を決める前に、実際のモデルとハーネスで、発見から呼び出しまでをテストしてください。推測ではなく、測定しましょう。すべてのルーター呼び出しは、セッション ID とともに ~/.tool-guardian/calls.jsonl(TOOL_GUARDIAN_CALL_LOG)に追記されます。DSH では、ルーターツールの仕事をするシェル呼び出しも kind: "bypass" として記録され(促したり拒否したりできます)。実際のタスクをいくつか試したら、/toolguardian bypass(DSH)または tool-guardian --bypass-summary(任意のクライアント)を実行すると、そのファイルからルーター呼び出し、あるべきだった call_tool(...) を正確に示すすべての迂回、そしてルーターに一度も触れなかったセッションを確認できます。プレーンな MCP については、迂回の検出とその限界をご覧ください。うまく動作しない場合は、モデルが決して開かないカタログの背後にすべてをルーティングするのではなく、厳選した見えるサーバーのサブセットを公開してください。

実行方法は2通りです

リポジトリは1つ、Python ルーターも1つです。使用するハーネスに合った入口を選んでください。どちらも引き続きサポートされます。

MCP サーバー(任意の MCP クライアント)ネイティブ DSH バンドル
対応環境Claude Code、OpenCode、Cline、Continue(テスト済みの設定)、Claude Desktop、OpenClaude、Cursor など、stdio 経由で MCP を話すものDeepSeek Harness
インストールpip install tool-guardiandsh plugin --profile <name> add dsh-tool-guardian
MCP スキーマを6つのルーターツールの背後に隠す(スキルを含めると8つ)はいはい、ネイティブに登録されます
結果に対する出力ラダーcall_tool の結果すべてのツールの結果(bash、grep、web_fetch など)
トークン価格付きのツールグループlist_groups_with_costsさらに activate_group があり、DSH 独自の組み込みツールもグループ化して非表示にできます
シェル呼び出しがルーターツールの役割を果たしていることを検知—ログ記録、通知、または拒否を行います
設定方法tool-guardian.json + TOOL_GUARDIAN_* 環境変数tool-guardian のパッチ行または DSH の設定。同じ環境変数が優先されます
flowchart LR
    A["Your agent<br/>(Claude Code, DSH, any MCP client)"] -->|"6 tools, ~1,700 tokens for 7 servers"| B["Tool Guardian"]
    B -->|"on demand"| C["filesystem"]
    B -->|"on demand"| D["git"]
    B -->|"on demand"| E["n8n, database, ..."]
    B -. "big result" .-> F[("archive<br/>retrieve_spill")]
    B -->|"shaped result"| A

ツール

ルーターは以下のツールを公開します。最初の4つは常に利用できます。list_groups_with_costs と retrieve_spill(出力ラダーが有効な場合)が、6つあるルーターツールの残り2つです。スキルフォルダーが設定されている場合にのみ、スキル用の2つのツールも表示されます。

ツール引数機能
list_capabilitiesserver(任意)1つのサーバー、またはすべてのサーバーについて、ツール名と1行の概要を表示します。まずはこちらを使ってください
search_capabilitiesquery、server、limit(任意)サーバーをまたいでキーワードからツールを検索します
describe_toolserver、tool、detail(任意)1つのツールの引数を表示します(detail=full:スキーマ全体)
call_toolserver、tool、args(任意)バックエンドツールを実行し、その(整形済みの)結果を返します。ユーザーの質問に答える手順です
list_groups_with_costsなしツールグループと、それぞれの読み込みにかかるコンテキストトークン数を表示します
retrieve_spillid、grep、start_line、max_lines(任意)短縮された結果の元の全文を読み戻します
list_skillsなしスキルごとに1行で表示します(TOOL_GUARDIAN_SKILLS が設定されている場合のみ)
read_skillname1つのスキルの全文を表示します(TOOL_GUARDIAN_SKILLS が設定されている場合のみ)

call_tool 以外のツールはすべて情報を報告するだけです(読み取り専用でネットワークアクセスはありません。カタログはメモリ上にあり、アーカイブとスキルはローカルディスク上にあります)。call_tool はバックエンドツールの動作を実行します。各ツールの説明では、その用途、同種の別ツールを使うほうがよい場合、アクセス対象、返される内容を、それぞれ簡潔に説明します。これは、テストによって tools/list のペイロードが1,100トークンに制限されているためです。各パラメーターは、独自の説明、enum、または名前を示すツールの説明のいずれかで、型と説明が一度だけ定義されています。tests/test_tool_definitions.py はこれを検証し、この表の引数名がサーバーから実際に一覧表示されるスキーマと一致することも確認します。

アーキテクチャ

flowchart LR
    client["MCP client<br/>(Claude Code, Claude Desktop, ...)"] -- "stdio JSON-RPC" --> serve
    dsh["DSH bundle<br/>index.js"] -- "line JSON<br/>tg_bridge.py" --> router
    subgraph tg["tool-guardian (Python, standard library only)"]
        serve["serve()<br/>MCP over stdio"] --> router["Router<br/>catalogue · search · arg checks · loop guard"]
        router --> ladder["tg_ladder<br/>shape result by type"]
        ladder -- "lossy?" --> spill[("tg_spill archive<br/>~/.tool-guardian/spill")]
        router --> groups["tg_groups<br/>token price per group"]
        router --> state[("calls.jsonl · state.json")]
    end
    router -- "stdio, started at launch,<br/>respawned once" --> b1["backend MCP server"]
    router --> b2["backend MCP server"]

クライアントの接続先を、唯一の MCP サーバーである Tool Guardian に設定し、クライアントに渡す予定だったものと同じ mcpServers 設定を渡します。起動時にすべてのバックエンドを子プロセスとして起動し、それぞれの tools/list を一度だけ読み込みます。それ以降は、list_capabilities、search_capabilities、describe_tool にメモリから応答します。バックエンドに到達するのは call_tool だけです。その引数は、まずバックエンドツールのスキーマに照らして検証され、結果はモデルに渡される前に出力ラダーを通ります。DSH バンドルは、MCP ではなく専用のブリッジプロセスを介して同じ Router に到達します。

主な設計上の決定と、それぞれのトレードオフ(詳細な記録は docs/adr/ にあります):

決定メリットコスト
汎用ルーターツールとオンデマンドのスキーマ(0001)リクエストごとのスキーマトークンを 91 % 削減(ベンチマーク)各機能の利用には 2~3 回の呼び出しが必要で、モデル自身が調べることを選ぶ必要があります
明示的な失敗と名前付きカタログ(0002)停止したサーバーが「そのようなツールはない」と誤認されませんサーバーごとに説明が数トークン増えます
説明の予算化と固定(0003)モデルが推測せずに選べるツールバックエンドがない状態で tools/list は 1,090 トークンとなり、テストによって ≤ 1,100 に固定されています
入力は寛容に受け付け、バックエンドでは厳密に検証(0004)小型モデルの惜しい呼び出しも動作し、実行不可能な呼び出しでは往復通信が発生しませんスキーマはルーターの実際の検証よりも緩やかです
標準ライブラリのみの Python コアを一つ使用し、DSH バンドルはブリッジにする(0005)どこにでもインストールでき、修正するルーターは一つだけですMCP stdio は手書きで、npm パッケージには Python が必要です
短縮する前にアーカイブ(0006)大きな結果がコンテキストを圧迫せず、何も失われません元のデータはローカルディスクに保存されます

インストール

pip install tool-guardian

標準ライブラリのみを使用するため、ほかにインストールするものはありません。

PyPI と npm では、現在同じコードを配布しています。MCP サーバーには pip install tool-guardian(0.3.0 以降)、DSH には npm の dsh-tool-guardian を使用してください。

それぞれのインストール対象は次のとおりです。Python パッケージ tool-guardian 0.8.0 は MCP サーバーです(pip install tool-guardian または uvx tool-guardian)。npm パッケージ dsh-tool-guardian 0.8.0-beta.1 は DeepSeek Harness バンドルです。バージョンは別々に管理されています。

設定

最も手早い方法: おそらく、別の MCP クライアントですでにサーバーを設定しているはずです。

tool-guardian-setup import    # copies them from Claude Desktop / Cursor / Windsurf / .mcp.json, asks before writing
tool-guardian-setup doctor    # checks the config, each server's command, your .env and ${VARS}, and says how to fix each problem

サーバーをもう 1 つ追加するのは 1 コマンドだけです。-- の後はすべて、入力する際とまったく同じサーバーのコマンドラインです。

tool-guardian-setup add git -- uvx mcp-server-git
tool-guardian-setup add github --env GITHUB_TOKEN='${GITHUB_TOKEN}' --description "issues and PRs" -- npx -y @modelcontextprotocol/server-github
tool-guardian-setup list      # name, command, and the group each server's tools land in
tool-guardian-setup remove git

add は doctor が検出するのと同じ設定ファイル(DSH の場合は $TOOL_GUARDIAN_CONFIG)に書き込みます。最初にバックアップを作成し、--replace を指定しない限り、既存の名前は拒否します。また、コマンドが PATH 上にあるか確認し、環境または .env で設定されていない ${VAR} があれば警告し、そのサーバーの doctor の結果を最後に表示します。読み込むには、MCP クライアントを再起動するか、DSH の新しいセッションを開始してください。 追加したサーバーはすべて、ルーターの 6 つのツールの配下に自動的に配置されます。groups の設定がない場合は、サーバー名をグループ名とする専用グループが作られます。カスタム groups 設定がある場合は、グループに追加するまで other に配置されます。

import は ~/.tool-guardian/mcp.json に書き込みます(既存のファイルがあればバックアップを作成します)。まだルーティングできないサーバー(HTTP/SSE サーバー)はスキップし、代わりにクライアントに追加するエントリを 1 つ表示します。DSH 内(npm install)では、同じ処理を python <plugin folder>/tg_setup.py doctor として実行します。

または、設定を手動で記述できます。

Tool Guardian は、標準の mcpServers ブロック(Claude Desktop/Claude Code やほとんどの MCP クライアントで使われるものと同じ形式)を読み込みます。

{
  "mcpServers": {
    "files": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
    },
    "git": {
      "command": "uvx",
      "args": ["mcp-server-git"],
      "description": "git status / diff / commit / log"
    }
  }
}

サーバーごとに任意の "description" を指定すると、モデルに表示されるカタログ情報が充実します。指定しない場合は、起動時にそのサーバー自身のツール名からヒントが生成されます。

設定ファイルは、--config PATH、$TOOL_GUARDIAN_CONFIG、~/.tool-guardian/mcp.json の順に検索されます。使用されるファイルと、その隣から読み込まれた .env は、起動時に標準エラー出力へ表示されます。

カレントディレクトリにある設定を使うには、明示的な指定が必要です。 ./mcp.json と ./.mcp.json は自動では読み込まれません。クローンしたリポジトリにこれらのファイルが含まれている場合があり、0.7.0 までは、そのディレクトリ内でルーターを起動すると、クライアントからの承認確認なしにサーバーが起動していました。設定ファイルが見つかってスキップされた場合は、ファイル名とオプトイン方法を示す行が標準エラー出力に 1 行だけ表示されます。使用するには、--config ./mcp.json を渡すか、TOOL_GUARDIAN_CONFIG=./mcp.json を設定するか、TOOL_GUARDIAN_PROJECT_CONFIG=1 を設定してください。

唯一のサーバーとして tool-guardian を指定した ./.mcp.json(下記の Claude Code のセットアップで、ルーターをクライアントに登録するために作成されるもの)は、ルーター自身の設定ファイルとしては扱われません。スキップされ、検索は ~/.tool-guardian/mcp.json へ続きます。

環境と .env

Tool Guardian は .env を自動で読み込み、バックエンドの 引数内の変数を展開するため、起動する側でシークレットを環境変数として エクスポートする必要はありません。

  • .env の自動読み込み。 起動時に、次の順序で .env を探します。明示的に指定されたパス、$TOOL_GUARDIAN_ENV、それから上方向への検索です。設定ファイルのディレクトリ(または cwd)から始めて、最大 5 階層上までたどり、最初に見つかった .env を読み込みます。これにより、.env をプロジェクトのルートに置いたまま、設定ファイルをネストしたフォルダーに配置できます。実際の環境変数はファイル内の値より優先され、TOOL_GUARDIAN_* 設定自体もファイルに記述できます。.env が見つからなくても何も起こらず、エラーにはなりません。

    例 — 設定ファイルがプロジェクト内にあり、.env がルートにある場合:

    myproject/
    ├── .env                 <- (3) ここで見つかり、読み込み後に検索終了
    └── config/
        └── dsh/
            └── mcp.json      <- $TOOL_GUARDIAN_CONFIG がこのファイルを指す
    

    検索は設定ファイルのディレクトリから上方向に進みます:

    1. myproject/config/dsh/.env    -> 見つからない
    2. myproject/config/.env        -> 見つからない
    3. myproject/.env               -> 見つかった(ここで終了)
    
  • 小ウィンドウ向けプリセット。 TOOL_GUARDIAN_PRESET=small は、コンテキスト長 8k のローカルモデル向けです。次の 2 つの値だけを設定します:

    設定デフォルトsmall説明
    TOOL_GUARDIAN_CATALOGUE_CHARS0(上限なし)1500list_capabilities 内の各サーバーのブロックをこの文字数で切り詰め、search_capabilities を案内する [+N more tools not shown ...] の行を追加します
    TOOL_GUARDIAN_DESC_WORDS126カタログと検索結果の行に表示する、ツール、サーバー、またはスキルの説明の単語数

    どちらの変数も設定すれば、プリセットを上書きできます。検索のランキングでは引き続き説明の 12 単語を評価し、出力の段階的な表示方法やその他すべての設定は変更されません。また、describe_tool はどちらの設定でも簡潔です。7 つのベンチマーク用カタログでは、サーバーごとの 7 つの一覧の合計は、small で 9,582 文字、設定なしでは 16,274 文字です。

  • 変数の展開。 環境変数から、各バックエンドの args および env の値に含まれる ${VAR}、$VAR、%VAR% を展開します。不明な変数はそのまま残るため、見落とした変数の名前はバックエンド自身のエラーで確認できます。シークレットは .env に保存し、バックエンドの引数から参照します:

    "args": ["-y", "mcp-remote", "https://app.openseo.so/mcp",
             "--header", "Authorization: Bearer ${OPENSEO_API_KEY}"]
    

    または、バックエンドの環境変数から参照します:

    "env": {"GITHUB_TOKEN": "${GITHUB_TOKEN}"}
    

    (0.7.0 までは展開されるのは args のみだったため、このような env の値はリテラル文字列 ${GITHUB_TOKEN} としてサーバーに渡されていました。)

実行

MCP クライアントが、単一の stdio サーバーとして Tool Guardian を使うように設定します:

{
  "mcpServers": {
    "tool-guardian": {
      "command": "tool-guardian",
      "args": ["--config", "/path/to/your/mcp.json"]
    }
  }
}

Claude Desktop(claude_desktop_config.json)では、事前のインストールなしで uv を使います:

{
  "mcpServers": {
    "tool-guardian": {
      "command": "uvx",
      "args": ["tool-guardian", "--config", "/path/to/your/mcp.json"]
    }
  }
}

Claude Code をターミナルから使う場合(ユーザースコープ、すべてのプロジェクト):

claude mcp add --scope user tool-guardian -- tool-guardian --config /path/to/your/mcp.json

コンテナーで実行する場合:docker build -t tool-guardian . を実行し、続けて docker run -i --rm -v "$HOME/.tool-guardian:/root/.tool-guardian" tool-guardian を実行します。 バックエンドもコンテナー内で実行されるため、イメージにはそれらの起動に必要なもの(npx、uvx など)が必要です。同梱の Dockerfile に含まれるのは Python のみです。

サーバーでできることはすべて引き続き利用できます。モデルは、最初にすべてを読み込むのではなく、2 段階(list_capabilities → call_tool)で機能を見つけます。

バックエンドが終了する、または応答を停止する場合

バックエンドのプロセスが終了した場合、または TOOL_GUARDIAN_CALL_TIMEOUT の時間内に呼び出しへの応答がない場合、ルーターはそのプロセスを停止し、次の呼び出しの前に再起動します。初回は 1 秒後、それ以降の再起動では 5 秒後に再起動し、initialize ハンドシェイクを再送信します。60 秒以内に 2 回目の障害が発生すると、バックエンドは DOWN としてマークされ、それ以降は再起動されません。回数はブリッジの stats にある backend_respawns と、/toolguardian の backend respawns: N since load 行で確認できます。

どれだけ節約できるか確認する

tool-guardian --selftest

設定済みのサーバーを起動して、カタログを表示し、ルーターのツールを使う場合のトークン数と、すべてのサーバーのツールを直接読み込む場合のトークン数を報告します。たとえば、ファイルシステムサーバーを1つ背後に配置した場合は、*「ルーターツールのコストは約840トークン、背後にあるツール一式は約3943トークン → リクエストごとに約3103トークンを削減(79%減)」*となります。小規模なサーバーが1つの場合、節約効果はほぼ相殺されますが、サーバーを追加するたびに節約量が増えます。合計の下にはサーバーごとに[server] N tools, ~M tokensの行が表示され、設定済みのサーバーの起動に失敗した場合は、ゼロ以外の終了コードで終了します。最後にupdate:の行が表示されます(新しいリリース、「none」、または「could not check」)。tool-guardian-setup doctorも同じ行を表示します。このチェックは1日に最大1回実行され、失敗しても何も表示せず、GUARDIAN_NO_UPDATE_CHECK=1で無効にできます。

トークン数のカウントには、tiktokenがインポート可能な場合はcl100k_baseを使用し、それ以外の場合は文字数を3.5で割った推定値を使用します。レポートには、たとえばtoken counts: estimate (chars / 3.5; tiktoken not available)のように、どちらを使ったかが表示されます。pip install tiktokenを実行すると、カウント方法が切り替わります。他にtiktokenを必要とするものはありません。

モデルがルーターを操作することを確認する
TOOL_GUARDIAN_PROVE_URL=http://127.0.0.1:11434/v1 tool-guardian --prove

ルーターツールを付けて、OpenAI互換のチャットエンドポイントに3つの同梱プロンプトを送信します。同梱のスタブサーバーを前段に置いた実際のルーター経由で、モデルのツール呼び出しを実行します。エンドポイントが報告したプロンプトトークン数と完了トークン数を含む、プロンプトごとのPASS/FAIL行を表示します。対象は、list_describe(一覧表示または検索の後にdescribe_tool)、nested_call(ネストされたフィルターを指定したcall_tool)、stalled(モデルが検索だけで終わらず、call_toolに到達する必要があります)です。すべて成功した場合は終了コード0、FAILがあれば1、TOOL_GUARDIAN_PROVE_URLが設定されていない場合は2で終了します。オプションとして、TOOL_GUARDIAN_PROVE_MODEL(デフォルト:GET <url>/modelsから取得した最初のID)と、TOOL_GUARDIAN_PROVE_KEY(ベアラートークン)を指定できます。mcp.jsonのサーバーは起動されず、呼び出しログやstate.jsonにも何も書き込まれません。

DSHでは、セッション中に**/toolguardian**と入力すると、実行中のプラグインから同じレポートを確認できます。各サーバーのステータスとツール数、リクエストごとに節約されるトークン数、アクティブなグループ、ラダーとバイパスのカウンター、更新情報の行が表示されます。

ツールの結果も小さく保つ(0.3.0)

定義は問題の半分にすぎません。もう半分は40 KBのビルドログです。すべてのcall_toolの結果は、モデルに渡される前に決定論的な出力ラダーを通るようになりました。

結果モデルに渡される内容
約1.2k文字未満、またはreadクラスのツールからの結果1バイトも変更せず、そのまま
300文字を超えるエラー先頭と末尾の要約
JSON配列/10k以上のCSVキー、最初と最後の項目、件数
8k以上のシェル形式の出力先頭、均等に配置したサンプル(行番号付き)、末尾 — [exit code: …]は常に保持
統合差分すべての変更と、そのすぐ隣のコンテキスト
その他の1.2k以上の結果情報を失わずに整理:ANSIコードの除去、連続する空行の圧縮、繰り返し行の件数表示

**何も通知せずに破棄されることはありません。情報を失う処理の前に、元の内容全体がアーカイブされ、結果にその旨が1行で表示されます。また、モデルはretrieve_spill(id, grep=…)**を呼び出して、元の内容を読み戻せます。アーカイブを書き込めない場合は、元の内容がそのまま返されます。同じ入力には常に同じ出力が返るため、プロバイダーのプロンプトキャッシュが引き続きヒットします。TOOL_GUARDIAN_LADDER=0で無効にできます。

list_groups_with_costsは各ツールグループのコンテキストトークン数を算出します。また、すべてのルーター呼び出しは~/.tool-guardian/calls.jsonlに記録されます(引数の値は記録されません)。これにより、モデルが実際にルーターを使っているかを測定できます。

スキル

オンデマンドでカタログを参照する同じ考え方を、SKILL.md ファイルに適用します。TOOL_GUARDIAN_SKILLS に 1 つ以上のフォルダーを指定してください(Linux/macOS では :、Windows では ; で区切ります)。ルートから 1 ~ 2 階層下にあるすべての SKILL.md がスキルとなり、フロントマターの name(またはフォルダー名)がスキル名になります。

  • list_skills — スキルごとに 1 行で、名前と目的を返します。説明にはすべてのスキル名が含まれるため、モデルはそれらの存在を把握できます。
  • read_skill(name) — 1 つのスキルの全文を返します。名前はあくまで名前であり、パスではありません。/、\、.. は拒否されます。

この 2 つのツールは、スキルが 1 つ以上見つかった場合にのみ表示されます。tool-guardian --skills-report を実行すると、すべてのスキルを最初から読み込む場合と比べて、カタログによってどれだけ節約できるかを確認できます。

実測した節約量

ご自身でも再現できます:python bench/bench_tokens.py(ネットワークもモデルも不要です。機械処理用の出力には --json を指定してください)。カタログは、7 つの公開 MCP サーバーから取得した実際の tools/list の応答を bench/fixtures/catalogs/ に記録したものです。

サーバーツール数ツールスキーマのトークン数
everything131,610
filesystem142,500
github265,094
memory91,347
n8n-mcp72,826
playwright255,443
sequential-thinking11,185
7 サーバーすべて9520,005

リクエストごと: tool-guardian を使わない場合、ツールスキーマに 20,005 トークンを使用しますが、使う場合は 1,084 トークンです(94.6 % 削減)。コンテキスト長 32K のローカルモデルでは、会話を始める前にウィンドウの 61 % が埋まっている状態と、3 % が埋まっている状態の違いになります。(2026-10-21 の説明文の書き換えで 1,790 まで増えていましたが、ADR 0003 に、元に戻すための予算を記録しています。)

代表的な 4 種類のツール結果における出力の削減例:

出力ツール削減前(文字数)削減後(文字数)方法
エラーが 1 件ある 3,000 行のビルドログbash115,9454,540先頭、サンプル、エラー行、末尾、終了コード
400 項目の JSON 配列http_get29,521730キー、先頭と末尾の項目、件数
3 ハンクの統一 diffgit_diff41,7641,363すべての変更と前後 2 行のコンテキスト
1,500 行の CSVquery44,882354ヘッダー、先頭と末尾の行、件数

短縮された結果はすべて、まずアーカイブに保存され、retrieve_spill で読み戻せます。トークン数は、tiktoken がインストールされている場合はそれを使用して計算し(cl100k)、それ以外の場合は文字数 ÷ 3.5 の保守的な推定値を使用します。上の表は推定値です。

タスクあたりのコスト(2026-10-01 に測定)

上記の削減量はリクエストあたりです。ルーターの背後にあるツールを呼び出すには、まず見つける必要があります。また、モデルへのリクエストが増えるたびに会話が再送されるため、タスク全体で重要なのはタスクあたりのリクエスト数です。実際の DeepSeek Harness セットアップで測定しました。qwen3-coder:30b(Ollama、64K ウィンドウ)、3 サーバー/26 ツール、読み取り専用タスク 8 件を 2 回ずつ実行し、タスクごとに新しいセッションを開始して、同じタスクをサーバーを直接マウントした場合と tool-guardian の背後に配置した場合で比較しました。

直接マウントtool-guardian 0.4.0tool-guardian 0.4.1
リクエストヘッダー内のツール数643333
リクエストあたりのヘッダートークン数17,83110,82510,825
タスクあたりのモデルリクエスト数1.94.82.8
タスクあたりの推定入力トークン数39,69273,60938,089
タスクあたりの実時間41 s75 s44 s
成功したタスク数(16 件中)687

0.4.0 は各リクエストを 39 % 削減しましたが、タスクあたりのコストは 85 % 増加しました。モデルが検索 -> 説明 -> 呼び出しと進み、呼び出し形式の NEXT STEP ヒントをプレーンテキストとしてコピーしたためです。0.4.1 では、各ヒットの引数を検索結果に含め(説明の往復を不要にし)、ヒントから呼び出し構文を削除しました。その結果、3 サーバーではタスクあたりのコストが直接マウントとほぼ同等になり、サーバーを追加するほど削減効果が大きくなります。このセットアップで損益分岐点となるのは、タスクあたり約 3.7 リクエストです。n = 16 での成功率の差は誤差の範囲内です。**効果がある場合:**多数のサーバー、長いセッション、検索 -> 呼び出しと進むモデル。**効果がない場合:**探索を行うモデルを使った、小規模なサーバー 1〜2 台。再現方法:ハーネスと生スコアは、CHANGELOG の 2026-10-01 の項目にあります。

小型モデルが犯すミスを検出します(0.4.0)

ローカルモデルのツール呼び出しはほぼ正しいものの、数値を "2" として送ったり、必須の引数を省略したり、存在しないオプションを指定したり、コンテキストが尽きるまで同じ失敗する呼び出しを繰り返したりします。ルーターが、こうしたミスを防ぐようになりました。

ミス処理
引用符で囲まれた数値、"true"/"false"、テキストが期待される箇所の数値自動的に修正してから送信します
必須引数の不足、型の誤り、許可されたリストにない値、閉じたスキーマでの不明なキー送信しません。モデルには各問題と、正しい呼び出し例が提示されます
同じ呼び出しが 3 回目に同じ結果を返した場合結果の先頭に「これを繰り返しても回答は変わりません」という 1 行を付けます
同じ呼び出しが 5 回目になった場合実行せず、モデルには手元の情報を使うか、何が妨げになっているかを伝えるよう指示します

ツールの検索も効率化されました。search_capabilities(query) は、カタログ全体の代わりに、一致する少数の server.tool: description 行を返します。切り替え設定:TG_VALIDATE_ARGS=0、TG_LOOP_GUARD=0。

ネストした引数

上記のチェックは、ネストしたオブジェクトや配列の項目(必須キー、型、列挙値、閉じたオブジェクト)も検査し、問題の箇所を完全なパスで示します。例:argument 'params.filters[2].op' must be one of: eq, lt, gt。

describe_tool の詳細

describe_tool(server, tool) は、デフォルトでコンパクト形式を返します。内容は server.tool: one-line description、required args: 行、そしてすべての引数を一覧にした NEXT STEP 行(? = 任意)です。detail="full" を指定すると、従来どおりツール定義全体を返します。ベンチマーク対象の 7 つのカタログ(95 ツール)では、コンパクト形式は 25,161 文字で、フル形式の 131,080 文字に対して 80.8 % 短くなります。TOOL_GUARDIAN_DESCRIBE_DETAIL=full を設定すると、フル形式がデフォルトになります。

ネイティブ DeepSeek Harness(DSH)バンドル

同じリポジトリは、インストール可能な DSH バンドル dsh-tool-guardian でもあります。Python ルーターに変更はありません。このバンドルはルーターを書き換えるものではなく、ルーターへのブリッジです。また、上記の MCP サーバーも引き続き動作します。

dsh plugin --profile <name> add dsh-tool-guardian        # or a path to a checkout (run `pnpm install` in it first)
dsh --profile <name> --dump-config                        # shows a "# == dsh-tool-guardian" layer

DSH 内では、(1)ルーターツールをネイティブに登録するため、バックエンドのグループを有効化(activeGroups、またはトークンコストを先に表示する activate_group ツール)しない限り、MCP バックエンドのスキーマがリクエストに含まれることはありません。(2)tools/post-execute を通じて、bash、grep、web_fetch など、すべてのツールの結果に出力ラダーを適用するため、dsh-trim を併設しないでください。(3)ルーターツールの役割を果たすシェル呼び出しを検知し、ログに記録して、(デフォルトでは)注意を促すか、拒否します(bypass.mode)。プロファイルの cordis.patch.yml で tool-guardian の行をオーバーライドするか、DSH 設定の名前空間 tool-guardian から設定できます。既存の TOOL_GUARDIAN_* 環境変数は、どちらの設定よりも優先されます。Python は $TOOL_GUARDIAN_PYTHON、次にパッケージと同じ場所にある .venv、最後に PATH 上の python/python3(3.9 以降、標準ライブラリのみ)の順で検出されます。

セットアップと確認。 プラグインフォルダーで npm run setup を実行すると、Python を検出し、すでに使用している MCP サーバーを(事前に確認したうえで)~/.tool-guardian/mcp.json にインポートし、doctor を実行します。ブリッジはこのファイルを自動で見つけるため、プリセットや YAML を編集する必要はありません。セッション中は、/toolguardian でセルフテストを実行し、/toolguardian bypass(または bypass last、bypass 24h)でバイパスの概要を確認し、/toolguardian restore で前回のセッションのグループを再読み込みできます。

セッションをまたぐグループ。 各 activate_group は ~/.tool-guardian/state.json(TOOL_GUARDIAN_STATE。空の場合は無効)に記憶されます。次のセッションでは、そのグループが提案されます(ログ行、list_groups_with_costs 内の行、restore_groups ツール、/toolguardian restore)。読み込まれたグループはリクエストごとにトークンを消費するため、自動で読み込まれることはありません。

設定。 DSH のウェブ UI に Tool Guardian のカードはありません(DSH はブラウザーバンドルを同梱するプラグインのみカードを表示します)。設定は ~/.dsh/settings.yaml の tool-guardian: の下にあり、即時反映されます。docs/dsh-settings.md では、各フィールドがカードとして示され、コードに基づいて行ごとに検証された正確な優先順位も説明されています。

結果整形の設計は dsh-trim(shuistama、MIT)に準拠しています。まず next() を呼び出し、エラー時は処理を続行し、データを失う処理の前にアーカイブします。

プレーンな MCP でのバイパス検知(およびその限界)

DSH では、プラグインがすべてのシェル呼び出しを実行前に確認するため、バイパスをログに記録し、注意を促すか、拒否できます。プレーンな MCP サーバーではそれができません。サーバーが確認できるのは自分宛ての呼び出しだけで、クライアント自身の Bash は確認できません。サーバー自身が検知できること、および tool-guardian --bypass-summary が報告する内容は次のとおりです。

  • ルーターを一度も呼び出さなかったセッション(クライアントの接続時に kind: "session" の行が記録されます)。
  • ツールを調べたものの、call_tool を呼び出さなかったセッション — Measurement 1 で記録されたパターンです。

Claude Code で実際にバイパスを検知するには、シェルコマンドをルーターに保存されたツール名と照合し、同じ呼び出しログに書き込む PreToolUse フックを追加します(名前のみを記録し、コマンドテキストは記録しません。エラーが発生した場合は「許可」として扱います)。

{ "hooks": { "PreToolUse": [ { "matcher": "Bash|PowerShell", "hooks": [ { "type": "command", "command": "tool-guardian --hook-pretooluse" } ] } ] } }

--mode deny/TOOL_GUARDIAN_HOOK_MODE=deny は呼び出しをブロックし、使用する call_tool(...) を Claude に伝えます。このフックは、Claude Code のドキュメントに記載された PreToolUse ペイロードと、実際の Claude Code 2.1.296 セッションから取得したペイロードを使ってテストされており、deny モードで呼び出しがブロックされました(動作確認済みを参照)。examples/claude-code/settings.json には同じブロックが記載されています。ツール実行前フックのない他のクライアントでは、サーバー側の 2 つのシグナルのみを利用できます。

Hook ルールファイル
tool-guardian --hook-pretooluse --rules ~/.tool-guardian/hook-rules.json
{"deny":  [{"tool": "Bash", "args": {"command": "\\brm\\s+-rf\\b"}, "reason": "no recursive deletes"},
           {"tool": "WebFetch"}],
 "allow": [{"tool": "Bash", "args": {"command": "^git (status|log|diff)"}}]}

ルールはデフォルトの動作より優先されます。tool はツール名全体に対して照合される正規表現です。各 args キーは、対応する tool_input フィールド内で検索される正規表現で、列挙されたキーはすべて一致する必要があります。拒否ルールが先にチェックされ、優先されます。一致すると、reason(またはルール名を示すデフォルトの理由)とともに呼び出しがブロックされます。許可ルールに一致すると、tool-guardian 独自のチェックはスキップされ、呼び出しは Claude Code の通常の権限フローに進みます。何も承認されるわけではありません。一致するルールがない場合、ファイルがない場合、またはファイルを読み取れない場合は、デフォルトの動作がそのまま適用されます。このフックが認識するのは、matcher によって送信されたツールのみです。そのため、他のツールにルールを適用するには、マッチャーの範囲を広げてください(例:Bash|PowerShell|WebFetch)。

トラブルシューティング

症状考えられる原因対処方法
モデルがルーターを一度も呼び出さず、記憶から回答するかシェルを使おうとする小型モデルは汎用ツールを使わないことがあります。これが最もよくある失敗です実際のタスクをいくつか実行してから、tool-guardian --bypass-summary を実行します。それでも回避が続く場合、そのモデルではルーターを動作させられません。モデル要件を参照してください
Windows:クライアントに command not found と表示される/サーバーが起動しないtool-guardian は、クライアントの PATH に含まれていない Scripts ディレクトリ内のスクリプトですwhere tool-guardian を実行し、完全なパスをクライアントの "command" に指定するか、"command": "uvx", "args": ["tool-guardian", ...] を使用します
list_capabilities でサーバーが UNKNOWN または DOWN と表示されるバックエンドの起動に失敗したか、停止しました行にバックエンド自身の直近の stderr の行(-- its stderr: ...)が表示されるようになりました。まずそれを確認してください。通常、不足しているトークンや誤ったパスが示されています。tool-guardian-setup doctor を実行すると、問題ごとに fix: の行が出力されます。DOWN のバックエンドは 60 秒後にもう一度再試行されます
stderr に config: ./mcp.json found but NOT loaded と表示される現在のディレクトリにある設定ファイルは、同意がない限り読み込まれません(0.8.0)使用する場合は、--config ./mcp.json または TOOL_GUARDIAN_PROJECT_CONFIG=1 を指定します。使用するつもりがなかった場合は、何も問題ありません。そのファイルは、たまたま現在いるディレクトリにあるものです
~/.tool-guardian/mcp.json にサーバーがあるのに、バックエンドが 0 件設定ファイルの検索で別の設定が優先されました起動ログに、読み込まれたファイルと、そのファイルと同じ場所にある .env が表示されます。--config または $TOOL_GUARDIAN_CONFIG を指定すると、両方が上書きされます
retrieve_spill failed: ... it may have been pruned (keep=500)アーカイブには最新の keep 件の結果が保持され、該当する ID は古くなって削除されましたcall_tool を使ってツールを再実行します。頻繁に発生する場合は、設定の toolGuardian ブロックで "spillKeep" の値を増やします
バックエンドに文字列 ${MY_TOKEN} がそのまま渡されるargs だけが展開されていた 0.7.0 以前のバージョンを使用しています0.8.0 に更新してください。このバージョンでは env の値も展開されます(未設定の変数は意図的にそのまま残ります)

互換性

テスト済み環境動作が期待される環境備考
DeepSeek Harness(バンドル版)0.1.2-alpha.2、Windows 11それ以降の0.1.xpeer @deepseek-ai/dsh-tools >=0.1.2-alpha.2、@deepseek-ai/cordis ^4.0.2。/toolguardian にはDSHのコマンドレジストリが必要です(0.1.2-alpha.2に搭載)
Node.js(バンドル版、npm run setup)Windows 11およびLinux上の22.x^22.19.0 または >=24engines フィールド
Python(ルーター、ブリッジ、セットアップ)Windows 11およびLinux上の3.11。CI:Ubuntu、Windows、macOS上の3.11、およびUbuntu上の3.9、3.10、3.12、3.133.9~3.13標準ライブラリのみを使用します。サポートされている各バージョンは現在CIで実行されます
オペレーティングシステムWindows 11(常用)、Ubuntu、Windows、macOS(CI:pytestとnodeのテスト)連携可能なクライアントで実行したクライアントはLinuxのみでした
DockerイメージCIでは、プッシュのたびにDockerfileをビルドし、stdio経由でMCP通信を行いますpython:3.12-slim。接続先のサーバーもコンテナ内で実行されるため、npx または uvx が必要なバックエンドには、それらを含むイメージが必要です
ルーター配下のMCPサーバーstdiostdioHTTP/SSE("url")サーバーは UNSUPPORTED として報告されます
クライアント(MCPサーバー経由)Claude Code 2.1.296、OpenCode 1.18.35、Cline CLI 3.0.70、Continue cn 1.5.47(Linux上で、それぞれスクリプト化されたモデルのターンを1回ずつ実行)、OpenClaudeClaude Desktop、Cursor、ClineおよびContinueのIDE拡張機能、stdio対応のMCPクライアント全般連携可能なクライアントを参照してください。Claude Codeのフックは実際のセッションで実行しました
モデルDSH上の qwen3:30b-a3b-instruct-2507 がルーターを使用(3セッション)ツールを呼び出す任意のモデルOpenClaudeではgpt-oss:20bとqwen3-30b-a3bがルーターを経由しませんでした。モデル要件を参照してください
モデルのバックエンドOllama(DSH / OpenClaude経由)任意ルーターはモデルのバックエンドと通信しません。重要なのはモデルのツール呼び出し動作のみです
コンパニオンdsh-context-guardian 0.1.0-alpha.52つはファイルを共有せず、個別にインストールします

「テスト済み環境」とは、このリポジトリのテストスイートに加え、開発に使用したマシンでの日常的な使用を意味します。「動作が期待される環境」はテスト済みではありません。動作しない場合は、イシューを作成してください。

設計上の注意点(重要な部分)

代替案やその結果を含む詳細版は、意思決定記録をご覧ください。

  • 意図的に、失敗を明確に示します。 ルーターは単一障害点です。ルーターがなければサーバーの障害による影響はそのサーバーだけですが、ルーターを介するとすべてのサーバーに影響する可能性があります。そのため、到達不能なバックエンドは、実際のエラーとともに UNKNOWN として報告し、空のツールリストとして返すことは決してありません。サーバーを指定したモデルに [] が返ると、その機能は存在しないと判断して、気づかれないように別の方法で対応します。この設計は、まさにその事態を防ぎます。
  • マシンだけでなく、モデルのために設計しています。 ツールの args はオブジェクトまたは JSON 文字列のどちらでも受け付け、モデルが実際に送ることのある類似した名前(query/name → server)も別名として扱います。また、各結果の末尾には、呼び出すべき具体的な 次のステップ を必ず示します。カタログを受け取っても指示がなければ、タスクを完了せずそこで止まりがちだからです。
  • カタログにはサーバー名を記載します。 名前のない汎用ツールが6つあっても、モデルにはその機能が存在すると信じる理由がなく、即興で対応してしまいます。ツールの説明にサーバー名を記載するにはトークンを少し使いますが、それによってモデルがカタログを開くか、6つのツールを無視するかが決まります。
  • SDKを使わず、プロトコルを手書きしています。 ルーターは、クライアントと背後のサーバーの両方と、stdioを介して MCP プロトコルリビジョン 2024-11-05 で通信します。実装は tool_guardian.py に手書きされており、MCP SDKもフレームワークもインストールが必要なものもありません(ADR 0005)。2つのパイプを介した約400行のJSON-RPCで構成されており、これが dependencies = [] を実現する理由であり、Python 3.9だけがインストールされたマシンでもスクリプト1つで動作する理由です。新しいクライアントリビジョンはこれに合わせてネゴシエーションし、未知のメソッドには適切な -32601 を返すため、クライアントはフォールバックできます。
  • トークン予算は目標ではなく、テストです。 ルーター自身の tools/list はリクエストのたびに再送されるため、tests/test_tools_list_budget.py によって1,100トークンに制限され、stdio経由でエンドツーエンドに測定されます。予算を超えて説明を改善することは、このプロジェクトが決して受け入れない唯一の変更です(ADR 0003)。

Guardian ファミリー

ローカルモデルを実行する人向けの、小規模なMITライセンスのツールが4つあります。それぞれが1つの役割を担い、コードを共有せず、単独でも役立ちます。

ツール解決する疑問
code-guardian自分が書いていないコードを実行またはマージする前に、安全か、動作するか、何か不足していないかを確認します。静的スキャンによる3つのスコアを提示し、Pythonを重点的に分析します。
context-guardianコンテキストウィンドウがいっぱいになる前に履歴を圧縮し、アーカイブした内容を検索するツールを備えたプロキシによって、ローカルモデルとの長時間のセッションをコンテキストウィンドウ内に収めます。
tool-guardian複数の MCP サーバーを少数のツール経由でルーティングし、サーバーの定義でコンテキストウィンドウが埋まらないようにします。
stack-guardian自宅のAIスタックが正常に動作しているかを確認します。Ollama/OpenAI互換ノード、NVIDIA GPU、Tailscaleピアを調べ、各検出結果に信頼度の評価を付けます。

現時点で対応していないこと

  • stdio サーバーのみです。 HTTP/SSE サーバー("url" エントリ)は UNSUPPORTED と報告されます。ここを介さず、直接読み込んでください。
  • ツールの統合や名前の変更は行いません。忠実にプロキシします。call_tool(server, tool, args) は実際のツールに変更を加えず到達します。
  • プレーンな MCP では、クライアントのシェル呼び出しを確認できません。バイパス検出とその限界を参照してください。
  • ルーター独自のツールは、tools/list で約 1,100 トークンを消費します(バックエンドなしの場合。サーバーのリストが長いほど増えます)。背後に小規模なサーバーが 1 つある場合は損益分岐点に近く、数台以上になると効果が出ます。説明を簡潔にするモードはまだありません。代わりに、説明は常に簡潔に保たれています。
  • トークン数は推定値です(文字数 ÷ 3.5)。tiktoken がインストールされている場合を除きます。
  • DSH のウェブ UI にカードはありません。~/.dsh/settings.yaml(docs/dsh-settings.md)で設定してください。

テスト方法

pip install -r requirements-dev.txt
python -m pytest -q                      # 212 tests; 3 skip until the npm install below has run
npm install --no-package-lock --legacy-peer-deps
npm run check && npm run test:node       # bundle syntax, text-call parser and repair
node tests/dsh_smoke.mjs && node tests/probe_dsh_mvp.mjs && node tests/probe_dsh_loopguard.mjs
node tests/probe_recall_line.mjs && node tests/probe_setup.mjs
pip install -e . && python tests/clients/run_clients.py   # real clients on PATH vs examples/ (optional)
  • 実際のプロトコルを使用します。 ツール定義、アノテーション、README の各テストでは、スタブバックエンドを用意して tool_guardian.py を stdio MCP サーバーとして起動し、tools/list が実際に返す内容を読み取ります。
  • コントラクトプローブ(tests/probes/)は、各モジュールが存在する前に、そのモジュールのコントラクトに基づいて作成されています。そのため、モジュール自身のセルフテストが甘いことで、検証できてしまう内容をチェックします。
  • 回帰テストは、まず失敗します。 CHANGELOG の各修正には、その修正前に main で失敗したテストが記載されています。
  • スナップショット。 tests/test_rank_identity.py は、記録済みのカタログに対する検索結果とカタログ出力を固定します。bench/bench_tokens.py は、この README に記載されたすべてのトークン数を bench/fixtures/catalogs/ から再測定します。
  • クライアント。 tests/clients/run_clients.py は、スクリプト化されたモデルを使って Claude Code、OpenCode、Cline、Continue を実行します(API キーやネットワークは不要です)。これは手動で実行し、CI では実行しません。
  • CI では、Ubuntu、Windows、macOS(Python 3.11)および Ubuntu(Python 3.9、3.10、3.12、3.13)で pytest を実行します。また、3 つすべてのオペレーティングシステムで node スイートを実行し、追跡対象のすべてのファイルをプライバシースキャンします。

謝辞

  • dsh-trim(shuistama、MIT)— 結果整形リスナーの設計を参考にしました。まず next() を呼び出し、失敗時は処理を続行し、情報が失われる処理を行う前にアーカイブします。Tool Guardian の段階的な仕組みは独自に実装した Python 版であり、dsh-trim のコードは含まれていません。
  • DeepSeek Harness — バンドル形式と、DSH 側の基盤となる tools/pre-execute / tools/post-execute の接続ポイントです。
  • Model Context Protocol — mcpServers の設定形式は同プロトコルのもので、既存の設定をそのまま使えるよう変更せずに使用しています。

ライセンス

MIT — LICENSE を参照してください。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。