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

七个 MCP 服务只需 1,084 个 token,而非 20,005 个;工具结果也不再塞满上下文窗口。

一个 MCP 服务,置于你所有其他服务之前。已在 Claude Code、OpenCode、Cline 和 Continue 中测试(每个客户端运行一轮脚本化模型调用,参见兼容情况);Claude Desktop、Cursor 和其他 stdio MCP 客户端:预计兼容,尚未测试。

测试 隐私 许可证:MIT Python 3.9+ 依赖:无 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 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 中的条目)

“完整轮次”的含义。 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 测试结果

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-guardiandsh 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_capabilitiesserver(可选)列出某个服务或所有服务的工具名称及单行摘要;从这里开始
search_capabilitiesquery、server 和 limit(可选)按关键词搜索各服务中的工具
describe_toolserver、tool、detail(可选)获取某个工具的参数(detail=full:完整架构)
call_toolserver、tool、args(可选)运行后端工具并返回(经过整形的)结果;这一步会给出用户所需的答案
list_groups_with_costs无列出工具组及加载每组所需的上下文 Token 费用
retrieve_spillid、grep、start_line、max_lines(可选)读取缩短结果的完整原文
list_skills无每项技能占一行(仅在设置了 TOOL_GUARDIAN_SKILLS 时可用)
read_skillname读取某项技能的完整文本(仅在设置了 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_capabilities
    TOOL_GUARDIAN_DESC_WORDS126在目录和搜索结果行中显示的工具、服务或技能描述字数

    设置任一变量即可覆盖预设。搜索排名仍按 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 数
everything131,610
filesystem142,500
github265,094
memory91,347
n8n-mcp72,826
playwright255,443
sequential-thinking11,185
全部七个9520,005

**每次请求:**不使用 tool-guardian 时,工具架构占 20,005 个 token;使用后为 1,084 个(减少 94.6 %)。对于 32K 上下文的本地模型来说,这意味着对话开始前,上下文窗口的占用率从 61 % 降至 3 %。(2026-10-21 的描述改写曾将其推高到 1,790;ADR 0003 记录了让它回到当前水平的预算。)

四种典型工具结果的输出阶梯:

输出工具之前(字符数)之后(字符数)方法
含一条错误的 3,000 行构建日志bash115,9454,540开头、样例、错误行、结尾、退出码
含 400 个项目的 JSON 数组http_get29,521730键、首尾项目、计数
含 3 个区块的统一差异git_diff41,7641,363每项变更及其前后各 2 行上下文
1,500 行 CSVquery44,882354标题行、首尾行、计数

每个缩短后的结果都会先归档,并可通过 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.0tool-guardian 0.4.1
请求头中的工具数643333
每次请求的头部 token 数17,83110,82510,825
每项任务的模型请求数1.94.82.8
每项任务的估算输入 token 数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 时的通过率差异处于噪声范围内。**适用场景:**服务较多、会话较长、模型会依次执行搜索 -> 调用。**不适用场景:**只有一两个小型服务,且模型会主动探索。复现方法:测试框架和原始分数见 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 或 >=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 服务stdiostdio"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);OpenClaudeClaude 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。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。