关于 Tool Guardian
Tool Guardian 是面向多 MCP 服务的路由器,按需提供后端工具定义,并可归档过长结果,以减少上下文占用。
主要功能
- 01按需发现工具
- 02搜索并描述后端工具
- 03路由调用后端工具
- 04测算工具组令牌成本
- 05归档过长结果
适用场景
- 01减少多个 MCP 服务的定义令牌
- 02按关键词查找相关工具
- 03避免长命令结果占满上下文
MCP 相关
它是置于其他 MCP 服务前的 MCP 服务。客户端使用其路由工具,按需列出、搜索、描述和调用后端工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
6收录的工具列表 · 更新于 5 天前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| server | string | 否 | 服务器名称;省略则表示全部服务器 |
详细介绍
Tool Guardian
七个 MCP 服务只需 1,084 个 token,而非 20,005 个;工具结果也不再塞满上下文窗口。
一个 MCP 服务,置于你所有其他服务之前。已在 Claude Code、OpenCode、Cline 和 Continue 中测试(每个客户端运行一轮脚本化模型调用,参见兼容情况);Claude Desktop、Cursor 和其他 stdio MCP 客户端:预计兼容,尚未测试。
mcp-name: io.github.LuminariSoftwares/tool-guardian
标题中的数字来自本仓库的基准测试,测试使用了七个公开 MCP 服务记录下来的 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 分钟 · 缘由 · 两种运行方式 · 架构 · 安装 · 测量节省量 · 输出阶梯 · DSH 套件 · 兼容性 · 更新日志
一个位于其他 MCP 服务之前的 MCP 服务,提供 6 个路由工具(搭配 skills 时为 8 个),取代几十个具体工具:四个通用工具用于列出、搜索、描述和调用后端服务中的工具,另有两个用于查看工具组成本和归档结果(参见工具)——其余工具则可按需发现。这样,模型还没读到一个字,工具定义就不会先占满上下文窗口。
路由器从不与模型后端通信,因此模型由 Ollama、LM Studio、vLLM 还是 llama.cpp 提供都无关紧要;关键在于客户端调用模型工具(已使用 Ollama 运行;其他后端预计可用,尚未测试)。
这是 Context Guardian 的配套工具:Context Guardian 会在上下文窗口填满前压缩对话;Tool Guardian 则防止工具先把窗口填满。 二者解决的是同一个问题的两个方面。
| 不使用 Tool Guardian | 使用后 | |
|---|---|---|
| 32K 模型上的 7 个 MCP 服务(基准测试) | 每次请求都携带 20,005 个 token 的架构定义(占窗口的 61 %) | 1,084 个 token(3 %);仅在模型请求时才获取架构定义 |
| 包含一处错误的 3,000 行构建日志(基准测试) | 115,945 个字符进入对话 | 4,540 个字符;完整原文已归档,一次调用即可获取 |
| 启动失败的后端 | 空工具列表,模型会悄悄绕过问题继续工作 | 显示状态 UNKNOWN 和实际错误(例如 could not start: …),绝不返回空列表 |
快速开始(60 秒)
无需安装:uvx 可直接从 PyPI 运行它。下面的示例服务是 MCP 文件系统服务,它通过 npx 运行,因此这行命令需要 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。
实际使用效果
以下是一个与两个服务(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.
整个过程中,模型的上下文里只有路由器自己的六个工具;26 个后端工具从未发送给模型。
何时不该使用路由器
有两种情况不适合使用它;提前说明这一点,比事后发现问题省事:
- 客户端已经会为你搜索工具。 有些客户端(以及一些模型 API)会按需自行加载工具定义。在这种客户端中加入路由器,只会多一跳,并增加第二个目录,没有任何节省。
- 你只运行少量小型服务。 路由器自身的工具在
tools/list中约占 1,100 个令牌;其后只接一个小型服务时,节省几乎为零(决定使用前,--selftest会根据你的服务打印实际的计算结果)。每增加一个服务,节省就会增加;在最需要它的场景中,节省最大——例如运行多个服务的 32K 本地模型。
如果你的模型本身就无法可靠地调用工具,路由器也帮不上忙:请参阅 模型要求。
兼容
pip install tool-guardian,然后在客户端中添加一条配置。以下每个示例都使用相同的命令 tool-guardian,
该命令会读取 ~/.tool-guardian/mcp.json:tool-guardian-setup import 会从你已在使用的服务中填充该文件。
| 客户端 | 示例 | 配置位置 | 已测试 |
|---|---|---|---|
| Claude Code | project.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:完整轮次;实时会话中的钩子 |
| OpenCode | opencode.json | 项目中的 opencode.json,或 ~/.config/opencode/opencode.json | 1.18.35:完整轮次 |
| Cline | cline_mcp_settings.json | VS Code:Cline → MCP Servers → Configure MCP Servers;CLI:~/.cline/data/settings/cline_mcp_settings.json | CLI 3.0.70:完整轮次 |
| Continue | tool-guardian.yaml | 工作区中的 .continue/mcpServers/(IDE),或 config.yaml 中的 mcpServers 条目 | cn 1.5.47:完整轮次(config.yaml 中的条目) |
“完整轮次”的含义。 2026-10-10,在 Linux 上,每个客户端都使用了未经修改的示例 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 发送的负载现已成为测试夹具
(tests/test_claude_code_hook.py),并与 Claude Code 钩子文档中的示例放在一起。
尚未测试:Cline VS Code 扩展(文件名和格式相同;已通过 Cline CLI 测试)、Continue 的
.continue/mcpServers/ 文件夹(由 IDE 扩展读取;cn 只读取 config.yaml),以及在 Windows 或
macOS 上运行客户端(单元测试会在 CI 中的这三个平台上运行)。在 Windows 上,如果客户端找不到 tool-guardian,请将
where tool-guardian 返回的完整路径填入 command。
DeepSeek Harness 测试结果
Tool Guardian 也以原生 DeepSeek Harness 捆绑包 dsh-tool-guardian 的形式提供(它新增了什么)。以下两项数据仅来自 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 个字符 — 单个 DSH 环境,由作者测量,脚本尚未发布。
同时安装两者(DeepSeek Harness,约 5 分钟)
Context Guardian 和 Tool Guardian 是解决同一问题的两个部分:Tool Guardian 可避免工具架构和工具结果填满上下文窗口,Context Guardian 则会在对话填满窗口前对其进行压缩,并保留重要内容。两者不共享文件,需分别安装。你需要 DSH 0.1.2-alpha.2 或更高版本、Node.js ^22.19 或 >=24,以及 PATH 中的 Python 3.9+,供 Tool Guardian 的路由器使用。
# 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 可作为普通 MCP 服务,供 Claude Code、Cursor 或任何 MCP 客户端使用(参见其 README)。
刚开始使用?前 5 分钟将带你从零开始安装,并验证模型确实在 DSH 或 Claude Code 上使用了路由器。
为什么要做这个
每次请求都会重新发送 MCP 工具定义,无论模型是否使用这些工具。几个服务的定义通常就会占用数万个令牌——在首次发送用户消息前,往往已占去小型本地模型上下文窗口的大部分。在一个实际环境中,七个 MCP 服务占用了 28,689 个令牌,占 32K 窗口的 87.6%,成为其他所有内容都要面对的固定开销。
目前有两种应对方式,但都要付出代价:
| 方式 | 代价 |
|---|---|
| 加载较少的 MCP 服务 | 你会完全失去相应功能 |
| 忍受这种情况 | 你还没开始输入,上下文窗口就已少了三分之二 |
Tool Guardian 提供了第三种选择,而且两者的代价都不必承担。它代理所有服务,只向模型展示少量小型工具,以及一行服务器名称目录(背后有七个服务时为 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)。能力强的前沿模型都能可靠做到。较小的本地模型则不一定——正如下方两次测量所示,模型运行所用的运行环境与模型本身同样重要。
测量 1 — 2026 年 8 月,OpenClaude,MCP 上挂载的路由器。 在真实的多服务端堆栈中,gpt-oss:20b 和 qwen3-30b-a3b 在普通任务中都绕过了路由器——即使每个结果中都有 NEXT STEP 提示,并且还有专门的路由器子 Agent 为它们预先提供引导。它们把工具名称当作 shell 命令,或通过编写脚本绕过工具。部分原因在于运行环境,而非模型:gpt-oss:20b 确实正确调用了 list_capabilities,但在一般任务中无法将发现结果用于 call_tool;而唯一完成整个调用序列的方式(子 Agent)在到达路由器之前,就被运行环境自身的 Agent 工具参数验证拦截了。
测量 2 — 2026-09-19 和 2026-09-21,DeepSeek Harness,路由器工具由 DSH bundle 原生注册。 同一模型系列(qwen3:30b-a3b-instruct-2507,32K 窗口)使用了路由器,完全没有绕过行为。 09-19,两次会话的提示词中都指定了工具:调用一次 list_capabilities,然后调用 list_capabilities ×8 → call_tool(ops-scripts, service_status)。09-21,一次会话,三个普通提示词都没有指定工具(“Which of the project's services are running right now?”、“How many n8n workflows do we have, and which were edited most recently?”、“What did we learn last time a bridge commit wrote stale bytes?”):调用日志显示 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 记录;第三个提示词则通过配套插件的 recall/search 加内置 glob/read 作答,这才是正确的工具,并非绕过行为。这是三个会话、一个模型:足以说明测量 1 不能代表这些模型的最终表现,但不足以保证你的模型也会如此。
因此:--selftest 能证明节省效果,也能证明后端已启动——但它不能证明你的模型会在你的运行环境中调用路由器。 提交前,请用实际模型和运行环境测试发现→调用流程,并通过测量而不是猜测来验证:每次路由器调用都会追加到 ~/.tool-guardian/calls.jsonl(TOOL_GUARDIAN_CALL_LOG),其中带有会话 ID;在 DSH 下,若 shell 调用完成了某个路由器工具的任务,也会以 kind: "bypass" 记录在该文件中(并可对此进行提示或拒绝)。完成几项真实任务后,/toolguardian bypass(DSH)或 tool-guardian --bypass-summary(任何客户端)会读取该文件并为你汇总:路由器调用、每项绕过行为及其本应对应的确切 call_tool(...),以及从未调用过路由器的会话。通过纯 MCP 使用时,请参阅纯 MCP 下的绕过检测及其局限。如果它做不到,就公开一小部分经过筛选且可见的服务,而不是把所有服务都藏在一个模型从不打开的目录后面。
两种运行方式
它是一个代码仓库,也是一个 Python 路由器。选择适合你所用框架的入口——两种方式都将继续支持。
| MCP 服务(任意 MCP 客户端) | 原生 DSH 插件包 | |
|---|---|---|
| 适用于 | Claude Code、OpenCode、Cline、Continue(已测试的配置)、Claude Desktop、OpenClaude、Cursor,以及任何通过 stdio 使用 MCP 的工具 | DeepSeek Harness |
| 安装 | pip install tool-guardian | dsh plugin --profile <name> add dsh-tool-guardian |
| 将 MCP 架构隐藏在 6 个路由器工具之后(启用技能后为 8 个) | 是 | 是,以原生方式注册 |
| 结果的输出阶梯 | call_tool 的结果 | 每个工具的结果(bash、grep、web_fetch,……) |
| 带 Token 价格的工具组 | list_groups_with_costs | 另有 activate_group,而且 DSH 自带的工具也可以分组并隐藏 |
| 检测 shell 调用是否在执行某个路由器工具的工作 | — | 记录日志、提醒或拒绝 |
| 配置方式 | 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
工具
路由器会公开以下工具。前四个始终可用;list_groups_with_costs 和 retrieve_spill(启用输出阶梯时)是另外两个路由器工具,构成 6 个工具;只有在配置了技能文件夹时,才会出现两个技能工具。
| 工具 | 参数 | 功能 |
|---|---|---|
list_capabilities | server(可选) | 列出某个服务或所有服务的工具名称及单行摘要;从这里开始 |
search_capabilities | query、server 和 limit(可选) | 按关键词搜索各服务中的工具 |
describe_tool | server、tool、detail(可选) | 获取某个工具的参数(detail=full:完整架构) |
call_tool | server、tool、args(可选) | 运行后端工具并返回(经过整形的)结果;这一步会给出用户所需的答案 |
list_groups_with_costs | 无 | 列出工具组及加载每组所需的上下文 Token 费用 |
retrieve_spill | id、grep、start_line、max_lines(可选) | 读取缩短结果的完整原文 |
list_skills | 无 | 每项技能占一行(仅在设置了 TOOL_GUARDIAN_SKILLS 时可用) |
read_skill | name | 读取某项技能的完整文本(仅在设置了 TOOL_GUARDIAN_SKILLS 时可用) |
除 call_tool 外,其他工具都只提供信息(只读,不访问网络:目录在内存中,归档和技能文件在本地磁盘上);call_tool 则会执行后端工具的操作。每个工具的描述都会简要说明其用途、何时应改用同级工具、会触及什么内容以及会返回什么——每项约用一个分句,因为测试要求整个 tools/list 负载不得超过 1,100 个 Token。每个参数只会在其自身描述、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 包通过专用桥接进程访问同一个 Router,而非通过 MCP。
关键决策及其权衡(完整记录见 docs/adr/):
| 决策 | 带来的好处 | 付出的代价 |
|---|---|---|
| 通用路由器工具,按需提供架构(0001) | 每次请求的架构 token 减少 91%(基准测试) | 每项能力都需要 2–3 次调用;模型必须主动选择查看 |
| 明确报告故障,使用命名目录(0002) | 服务宕机绝不会被误认为“没有此工具” | 每个服务的描述会多占几个 token |
| 为描述设置预算并固定上限(0003) | 模型可以在工具之间进行选择,而无需猜测 | 没有后端时,tools/list 占用 1,090 个 token,并由测试固定在 ≤ 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+),以及 npm 上的 DSH 包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
只需一条命令即可再添加一个服务器——-- 后的所有内容都是服务器的命令行,与你实际输入的完全一致:
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 个工具:如果没有配置分组,它会获得一个以服务器命名的独立分组;如果使用自定义分组配置,则会被放入 other,直到你将它加入某个分组。
import 会写入 ~/.tool-guardian/mcp.json(并备份已有文件),跳过暂时无法路由的服务器(HTTP/SSE 服务器),并输出一条供你添加到客户端的条目。在 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 文件都会打印到 stderr。
当前目录中的配置需要你明确授权。./mcp.json 和 ./.mcp.json 不会自动加载。你克隆的代码仓库可能包含此类文件;在 0.7.0 及更早版本中,在该目录内启动路由器会启动其中的服务器——客户端不会显示任何审批提示。找到并跳过此类文件时,stderr 会输出一行,说明文件名和启用方式;如需使用,请传入 --config ./mcp.json、设置 TOOL_GUARDIAN_CONFIG=./mcp.json,或设置 TOOL_GUARDIAN_PROJECT_CONFIG=1。
如果 ./.mcp.json 中唯一的服务器是 tool-guardian 本身(即下方 Claude Code 设置写入的、用于向客户端注册路由器的配置),它绝不会被当作路由器自身的配置:该文件会被跳过,搜索会继续进行到 ~/.tool-guardian/mcp.json。
环境和 .env
Tool Guardian 会自行加载 .env,并在后端参数中展开变量,因此启动它的程序无需将密钥导出到环境中。
-
自动加载
.env。 启动时,它会按以下顺序查找.env:显式指定的路径、$TOOL_GUARDIAN_ENV,然后是向上搜索——从配置文件所在目录(或当前工作目录)开始,向上遍历最多 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 的本地模型。它只会设置两个值:设置 默认值 small作用 TOOL_GUARDIAN_CATALOGUE_CHARS0(不限制) 1500 每个服务在 list_capabilities中的区块会截断到此字符数,并显示[+N more tools not shown ...]行,指向search_capabilitiesTOOL_GUARDIAN_DESC_WORDS12 6 在目录和搜索结果行中显示的工具、服务或技能描述字数 设置任一变量即可覆盖预设。搜索排名仍按 12 个描述词评分,输出阶梯和其他所有设置均保持不变;两种预设下的
describe_tool都是精简格式。在七个基准目录中,启用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 客户端指向 Tool Guardian,把它作为单个 stdio 服务:
{
"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。
你仍然可以使用服务器能执行的所有功能——模型只需分两步发现这些功能(list_capabilities → call_tool),而不必一开始就为全部功能付费。
后端退出或停止响应时
后端进程退出,或调用在 TOOL_GUARDIAN_CALL_TIMEOUT 时间内没有收到回复时,路由器会停止该后端,并在下次调用前重新启动:首次等待 1 s,之后每次重新启动等待 5 s,并重新发送 initialize 握手。如果 60 s 内第二次发生故障,该后端会被标记为 DOWN,之后不会再重新启动。重启次数显示在桥接器的 stats 中,/toolguardian 的 backend respawns: N since load 行也会显示此次数。
查看节省效果
tool-guardian --selftest
启动已配置的服务,打印目录,并报告路由器工具的 token 开销与直接加载所有服务工具的开销之间的差异——例如,路由器后面只有一个文件系统服务时:"路由器工具约耗费 840 个 token,而其后完整工具集约耗费 3943 个 token -> 每次请求节省约 3103 个 token(减少 79%)"。一个小型服务的开销接近盈亏平衡;添加的服务越多,节省越明显。总计下方会为每个服务打印一行 [server] N tools, ~M tokens;如果任何已配置的服务启动失败,程序会以非零状态退出。最后会打印一行 update:(显示有较新版本、“none”或“could not check”);tool-guardian-setup doctor 也会以同样的行结束。此检查每天最多运行一次,失败时不显示任何信息;设置 GUARDIAN_NO_UPDATE_CHECK=1 可关闭检查。
token 数量优先使用 tiktoken(cl100k_base)计算;若无法导入,则使用字符数 ÷ 3.5 估算。报告会注明所用方式,例如 token counts: estimate (chars / 3.5; tiktoken not available)。执行 pip install tiktoken 即可启用精确计算;其他功能无需依赖它。
验证你的模型能否驱动路由器
TOOL_GUARDIAN_PROVE_URL=http://127.0.0.1:11434/v1 tool-guardian --prove
向兼容 OpenAI 的聊天端点发送三个内置提示词,并附上路由器工具;通过内置的存根服务前的真实路由器运行模型的工具调用;针对每个提示词打印一行 PASS/FAIL,并显示端点报告的提示词和补全 token 数: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(bearer token)。不会启动 mcp.json 中的服务,也不会向调用日志或 state.json 写入任何内容。
在 DSH 中,于会话内输入 /toolguardian,即可从正在运行的插件查看相同报告:每个服务的状态和工具数量、每次请求节省的 token 数、活跃组、梯级和绕过计数,以及更新提示行。
工具结果也会保持精简(0.3.0)
定义只是问题的一半;另一半是 40 KB 的构建日志。现在,每个 call_tool 结果都会先经过确定性的输出梯级处理,再交给模型:
| 结果 | 模型收到的内容 |
|---|---|
少于约 1.2k 个字符,或来自 read 类工具 | 原样保留,逐字节不变 |
| 超过 300 个字符的错误 | 开头 + 结尾摘要 |
| ≥ 10k 的 JSON 数组 / CSV | 键、首尾项及计数 |
| ≥ 8k 的 shell 风格输出 | 开头、均匀抽样(带行号)、结尾——始终保留 [exit code: …] |
| 统一差异 | 每项变更,以及紧邻其旁的上下文 |
| 其他 ≥ 1.2k 的内容 | 无损清理:移除 ANSI 转义序列、合并连续空行、统计重复行 |
不会在无提示的情况下丢失任何内容。 在进行任何有损处理之前,都会归档完整原文,并在结果中用一行说明;模型可以调用 retrieve_spill(id, grep=…) 将其读回。如果无法写入归档,则会返回原文。相同输入始终得到相同输出,因此提供商的提示词缓存可以持续命中。设置 TOOL_GUARDIAN_LADDER=0 可关闭此功能。
list_groups_with_costs 会按上下文 token 数为每个工具组计算开销;每次路由器调用都会记录到 ~/.tool-guardian/calls.jsonl(绝不会记录参数值),便于你衡量模型是否真的在使用路由器。
技能
将按需加载目录的理念应用于 SKILL.md 文件。将 TOOL_GUARDIAN_SKILLS 指向一个或多个文件夹(Linux/macOS 上用 : 分隔,Windows 上用 ; 分隔);根目录下方一到两级的每个 SKILL.md 都是一项技能,其名称取自 front matter 中的 name(或其文件夹名称)。
list_skills— 每项技能占一行:名称和用途。其描述会列出每项技能的名称,让模型知道它们的存在。read_skill(name)— 某项技能的完整文本。名称就是名称,绝不是路径:拒绝/、\和..。
仅当找到至少一项技能时,才会显示这两个工具。tool-guardian --skills-report 会打印目录相较于预先加载所有技能所节省的内容。
实测节省量
你可以自行复现:python bench/bench_tokens.py(无需网络或模型;使用 --json 输出机器可读结果)。这些目录是七个公开 MCP 服务实际返回的 tools/list 结果,记录在 bench/fixtures/catalogs/ 中。
| 服务 | 工具数 | 工具架构的 token 数 |
|---|---|---|
| everything | 13 | 1,610 |
| filesystem | 14 | 2,500 |
| github | 26 | 5,094 |
| memory | 9 | 1,347 |
| n8n-mcp | 7 | 2,826 |
| playwright | 25 | 5,443 |
| sequential-thinking | 1 | 1,185 |
| 全部七个 | 95 | 20,005 |
**每次请求:**不使用 tool-guardian 时,工具架构占 20,005 个 token;使用后为 1,084 个(减少 94.6 %)。对于 32K 上下文的本地模型来说,这意味着对话开始前,上下文窗口的占用率从 61 % 降至 3 %。(2026-10-21 的描述改写曾将其推高到 1,790;ADR 0003 记录了让它回到当前水平的预算。)
四种典型工具结果的输出阶梯:
| 输出 | 工具 | 之前(字符数) | 之后(字符数) | 方法 |
|---|---|---|---|---|
| 含一条错误的 3,000 行构建日志 | bash | 115,945 | 4,540 | 开头、样例、错误行、结尾、退出码 |
| 含 400 个项目的 JSON 数组 | http_get | 29,521 | 730 | 键、首尾项目、计数 |
| 含 3 个区块的统一差异 | git_diff | 41,764 | 1,363 | 每项变更及其前后各 2 行上下文 |
| 1,500 行 CSV | query | 44,882 | 354 | 标题行、首尾行、计数 |
每个缩短后的结果都会先归档,并可通过 retrieve_spill 读取。若已安装 tiktoken,token 数使用其 cl100k 编码计算;否则采用较为保守的字符数 ÷ 3.5 估算。上表使用的是估算值。
每项任务的成本(测量日期:2026-10-01)
上述节省是每次请求的节省。路由器后面的工具必须先被找到才能调用,而且每次额外的模型请求都会重新发送整个对话,因此衡量整个任务时,关键指标是每项任务的请求数。以下数据来自真实的 DeepSeek Harness 配置:qwen3-coder:30b(Ollama,64K 窗口),3 个服务/26 个工具,8 项只读任务各重复 2 次,每项任务使用一个全新会话;对比直接挂载服务与通过 tool-guardian 挂载相同服务:
| 直接挂载 | tool-guardian 0.4.0 | tool-guardian 0.4.1 | |
|---|---|---|---|
| 请求头中的工具数 | 64 | 33 | 33 |
| 每次请求的头部 token 数 | 17,831 | 10,825 | 10,825 |
| 每项任务的模型请求数 | 1.9 | 4.8 | 2.8 |
| 每项任务的估算输入 token 数 | 39,692 | 73,609 | 38,089 |
| 每项任务的耗时 | 41 s | 75 s | 44 s |
| 通过的任务数(共 16 项) | 6 | 8 | 7 |
0.4.0 将每次请求的成本削减了 39 %,但每项任务的成本反而增加了 85 %:模型会依次执行搜索 -> 描述 -> 调用,并将类似调用的 NEXT STEP 提示原样复制为纯文本。0.4.1 会将每个命中项的参数放入搜索结果(无需往返查询描述),并从提示中移除调用语法;在 3 个服务的情况下,每项任务的成本现在与直接挂载基本持平,而且随着添加的服务增多,节省也会增加。在此配置下,盈亏平衡点约为每项任务 3.7 次请求。n = 16 时的通过率差异处于噪声范围内。**适用场景:**服务较多、会话较长、模型会依次执行搜索 -> 调用。**不适用场景:**只有一两个小型服务,且模型会主动探索。复现方法:测试框架和原始分数见 CHANGELOG 中 2026-10-01 的条目。
捕获小型模型常见的错误(0.4.0)
本地模型的工具调用几乎正确:将计数作为 "2" 发送、遗漏必需参数、编造选项,或重复同一个失败调用,直到上下文耗尽。现在,路由器会拦截这些错误:
| 错误 | 处理方式 |
|---|---|
带引号的数字、"true"/"false"、预期为文本却传入数字 | 自动修正后发送 |
| 缺少必需参数、类型错误、值不在允许列表中、封闭模式中出现未知键 | 不发送;向模型指出每个问题,并提供正确的调用示例 |
| 第 3 次返回相同结果的相同调用 | 在结果前添加一行提示:“重复此操作不会改变答案” |
| 第 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" 会像之前一样返回完整工具定义。在七个基准目录(95 个工具)中,精简形式为 25,161 个字符,完整形式为 131,080 个字符,缩短了 80.8 %。设置 TOOL_GUARDIAN_DESCRIBE_DETAIL=full 可将完整形式设为默认值。
Native DeepSeek Harness (DSH) bundle
同一代码库也是可安装的 DSH bundle:dsh-tool-guardian。Python 路由器保持不变——该 bundle 是它的桥接层,而非重写版本;上文的 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) 原生注册路由器工具,因此 MCP 后端的架构不会进入请求,除非你激活其组(activeGroups,或 activate_group 工具;该工具会先报出令牌成本);(2) 通过 tools/post-execute 对每个工具的结果运行输出阶梯——包括 bash、grep、web_fetch 等所有工具,因此不要在旁边挂载 dsh-trim;(3) 发现执行路由器工具功能的 shell 调用,并记录、提醒(默认)或拒绝它们(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 Web UI 中没有 Tool Guardian 卡片(DSH 只会为附带浏览器 bundle 的插件绘制卡片);设置位于 ~/.dsh/settings.yaml 的 tool-guardian: 下,并会实时生效。docs/dsh-settings.md 将所有字段以卡片形式展示,并逐行从代码中验证了确切的优先级顺序。
结果整形设计遵循 dsh-trim(shuistama,MIT):先调用 next(),出错时放行,在进行任何有损处理前先归档。
通过普通 MCP 进行绕过检测(及其局限)
在 DSH 下,插件会在每次 shell 调用执行前检查它,因此会记录、提醒或拒绝绕过行为。普通 MCP 服务无法做到这一点:它只能看到发给自己的调用,看不到客户端自己的 Bash 调用。服务本身能看到的内容,以及 tool-guardian --bypass-summary 会报告的内容:
- 从未调用路由器的会话(客户端连接时会写入一条
kind: "session"记录); - 查找过工具但从未调用
call_tool的会话——这是 Measurement 1 记录到的模式。
如需在 Claude Code 中真正检测绕过行为,请添加其 PreToolUse 钩子。该钩子会将 shell 命令与路由器保存的工具名称进行匹配,并写入同一调用日志(只记录名称,绝不记录命令文本;任何错误都表示“放行”):
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|PowerShell", "hooks": [ { "type": "command", "command": "tool-guardian --hook-pretooluse" } ] } ] } }
--mode deny / TOOL_GUARDIAN_HOOK_MODE=deny 会阻止调用,并告知 Claude 应使用哪个 call_tool(...)。该钩子已针对 Claude Code 文档中的 PreToolUse 负载,以及从实时 Claude Code 2.1.296 会话捕获的负载进行了测试;在后者中,拒绝模式成功阻止了调用(参见支持情况);examples/claude-code/settings.json 是相同的配置块。其他没有工具调用前钩子的客户端只能获得两种服务端信号。
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 的正常权限流程;这并不代表批准了任何内容。如果没有匹配项、文件不存在或文件无法读取,则保持默认行为不变。Hook 只能看到其 matcher 传递给它的工具,因此要为其他工具设置规则,就需要扩大匹配范围(例如 Bash|PowerShell|WebFetch)。
故障排除
| 症状 | 可能原因 | 处理方法 |
|---|---|---|
| 模型从不调用路由器——它根据记忆作答或转而使用 shell | 小型模型会绕过通用工具;这是最常见的故障 | 运行几个实际任务,然后执行 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 s 后再重试一次 |
stderr 中出现 config: ./mcp.json found but NOT loaded | 未经同意,不会加载当前目录中的配置(0.8.0) | 如果你本来打算使用它:运行 --config ./mcp.json,或设置 TOOL_GUARDIAN_PROJECT_CONFIG=1。如果不是,则没有问题——该文件属于你当前所在的目录 |
~/.tool-guardian/mcp.json 中有服务,但后端数量为零 | 搜索时优先找到了其他配置 | 启动日志会指出已加载的文件及其旁边的 .env;--config 或 $TOOL_GUARDIAN_CONFIG 可以覆盖两者 |
retrieve_spill failed: ... it may have been pruned (keep=500) | 归档仅保留最新的 keep 个结果,该 ID 已过期 | 使用 call_tool 重新运行工具;如果经常发生,请提高配置中 toolGuardian 区块里的 "spillKeep" |
后端收到字面文本 ${MY_TOKEN} | 你使用的是 0.7.0 或更早版本,这些版本只会展开 args | 更新到 0.8.0,该版本也会展开 env 值(未设置的变量仍会按设计保留为字面文本) |
兼容性
| 测试环境 | 预期可用 | 备注 | |
|---|---|---|---|
| DeepSeek Harness(捆绑版) | 0.1.2-alpha.2、Windows 11 | 后续的 0.1.x 版本 | 同级依赖 @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 或 >=24 | engines 字段 |
| Python(路由器、桥接程序、安装程序) | Windows 11 和 Linux 上的 3.11;CI:Ubuntu、Windows 和 macOS 上的 3.11,以及 Ubuntu 上的 3.9、3.10、3.12、3.13 | 3.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 服务 | stdio | stdio | "url" HTTP/SSE 服务会报告为 UNSUPPORTED |
| 客户端(MCP 服务路径) | Claude Code 2.1.296、OpenCode 1.18.35、Cline CLI 3.0.70、Continue cn 1.5.47(各运行一次脚本化模型回合,Linux);OpenClaude | Claude Desktop、Cursor、Cline 和 Continue IDE 扩展、任何 stdio MCP 客户端 | 请参阅适用范围;Claude Code 钩子在实时会话中运行 |
| 模型 | DSH 下的 qwen3:30b-a3b-instruct-2507 驱动了路由器(3 个会话) | 任何会调用工具的模型 | gpt-oss:20b 和 qwen3-30b-a3b 在 OpenClaude 下绕过了路由器——请参阅模型要求(切换前请阅读) |
| 模型后端 | Ollama(通过 DSH / OpenClaude) | 任意 | 路由器不会与模型后端通信;只有模型调用工具的行为会产生影响 |
| 配套组件 | dsh-context-guardian 0.1.0-alpha.5 | 两者不共享文件,需分别安装 |
“测试环境”表示在此仓库的测试套件中通过测试,并在开发所用的机器上日常使用。“预期可用”表示未经测试——如果无法使用,请提交 issue。
设计说明(重要部分)
更完整的版本,以及备选方案和后果,请参阅决策记录。
- 故障会明确报告,这是有意为之。 路由器是单点故障:没有路由器时,一个故障服务只会让你失去该服务;有路由器时,故障可能让你失去所有服务。因此,无法访问的后端会连同其真实错误报告为
UNKNOWN,绝不会显示为空工具列表。如果模型请求某个服务,却收到[],就会认定该能力不存在,并悄悄绕过它——这正是我们要避免的故障。 - 为模型而构建,而不只是为机器。 它接受对象或 JSON 字符串形式的工具
args,会将模型实际发送的近似参数名映射到对应参数(query/name→server),并在每个结果末尾给出具体的 NEXT STEP 调用指引——因为模型收到目录却没有指示时,往往会就此停下,而不是完成任务。 - 目录会列出你的服务名称。 六个没有名称的通用工具无法让模型相信任何能力确实存在,因此模型会自行发挥。在工具描述中写出服务名称只需几个 token,却能让目录从模型会打开的内容变成模型会忽略的六个工具。
- 手写协议,不用 SDK。 路由器通过 stdio 使用 MCP 协议修订版
2024-11-05,既与客户端通信,也与其后端服务通信;所有实现均手写在tool_guardian.py中——不需要 MCP SDK、框架或任何安装步骤(ADR 0005)。它是在两条管道上运行的约 400 行 JSON-RPC,这也是dependencies = []的原因,也让脚本的单个副本在只有 Python 3.9 的机器上仍能运行。较新的客户端修订版会协商降级到此版本;遇到未知方法时会正确返回-32601,以便客户端回退。 - token 预算是测试要求,不是目标意向。 路由器自身的
tools/list会在每次请求中重新发送,因此tests/test_tools_list_budget.py会将其限制在 1,100 个 token,并通过 stdio 进行端到端测量。为了让描述更好看而超出预算,是本项目绝不会接受的改动(ADR 0003)。
Guardian 系列
为运行本地模型的用户打造的四款小型 MIT 许可工具。每款工具只做一件事,它们不共享代码,而且每款都能独立发挥作用。
| 工具 | 能回答的问题 |
|---|---|
| code-guardian | 运行或合并你没有编写的代码之前:它安全吗?能正常运行吗?有没有遗漏?通过静态扫描给出三个评分,并对 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 上,它无法看到客户端的 shell 调用;请参阅绕过检测及其局限。
- 路由器自身的工具约占用 1,100 个 token 的
tools/list(无后端时;服务器列表较长时会更多)。后面接一个小型服务时,成本大致持平;从接入几个服务起就能有所收益。目前还没有精简描述的模式——我们选择让所有人的描述都保持简洁。 - 除非安装了
tiktoken,否则 token 数量是估算值(字符数 ÷ 3.5)。 - DSH Web 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 中的每项 token 数量,数据来自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,在三种操作系统上运行 node 测试套件,并对所有已跟踪文件进行隐私扫描。
致谢
- dsh-trim(shuistama,MIT)——结果整形监听器的设计思路:先调用
next(),出错时放行,并在任何有损操作之前归档。Tool Guardian 的阶梯逻辑是独立的 Python 实现;其中不包含 dsh-trim 代码。 - DeepSeek Harness——Tool Guardian 所依赖的打包格式,以及 DSH 侧所基于的
tools/pre-execute/tools/post-execute接口。 - Model Context Protocol——
mcpServers配置格式由其定义,并在此原样使用,因此你现有的配置可以继续使用。
许可证
MIT——请参阅 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。