容器环境会让 MCP 描述哈希误报
作者发现,同版本 Desktop Commander 在两次运行中因工具描述包含容器主机名,产生不同哈希,而工具结构及其他描述保持一致。监控 tools/list 变化时,应统一运行环境,或在哈希前清理主机名、路径等动态字段。
AI 深度解读
同一版本的 MCP server,连续跑两次,工具描述的哈希却变了。AgentShortlist 团队最初以为工具发生了变化,检查后发现:描述里混进了 Docker 容器 ID。这次误报暴露了一个容易忽略的问题:工具清单既受代码影响,也可能受运行环境和客户端身份影响。
作者在 Docker 中启动了 10 个 MCP server,记录安装、握手和工具清单。对接 MCP 的程序员可以从中拿走一套基础检查方法,但要先认清边界:启动成功、清单一致,都不能证明工具可用或安全。
同一版本的工具描述,为什么会变
问题出在 Desktop Commander 0.3.1。两次运行之间,只有 start_process 的描述不同;4180 个字符的描述中,第 1348 个字符处出现了不同的容器 ID:一次是 fd1cb95d1f61,另一次是 6f6d4f1f57de。工具 schema 和其余 24 条描述完全一致。
服务把容器 hostname 写进了工具描述。重新创建容器,hostname 变化,文本哈希自然跟着变。作者当天修正了记录,并明确指出:这是环境造成的差异,不是漏洞,也不是风险信号。
哈希能证明内容不同,不能解释为什么不同。
作者的探测脚本会发送 initialize 和 tools/list,将清单按工具名、对象键排序后计算 sha256,分别保存描述、schema,以及两者合并后的哈希。排序可以消除顺序差异,却消除不了描述中的动态内容。
所以,用 tools/list 做变更监控时,应在相同环境下比较,或者在计算哈希前去掉 hostname、路径等环境字段。否则,一次普通的容器重建,就可能被误报成工具变更。
10 个全部通过,究竟通过了什么
测试发生在 2026 年 10 月 25 日,环境是 Windows、WSL2 和 Docker,没有提供凭据,只使用合成数据。7 个服务运行于 node:22-slim;excel、fetch、git 使用装有 uv 和 Python 3.12 的镜像。git 还需要额外安装 git,首次尝试就因缺少它而未能启动。
作者所说的「通过」只有三个条件:包能安装并启动,握手返回有效协议版本,能够返回工具清单。
第一次测试没有调用任何工具。当天后续测试才对 memory、everything、git、fetch 各增加了一次使用合成数据的无害调用。原文没有展开这些调用的参数和具体结果,因此不能据此推断其他功能。
重复性证据也有区别。chrome-devtools、codebase-memory、Context7、excel、memory、everything、git、fetch 的两次哈希一致;Desktop Commander 因容器 ID 不一致;Playwright 的记录里没有第二次运行。即便两次相同,也只是有限的稳定性证据。
「能启动并返回清单」是一项接入检查,不是功能验收。
工具数量同样需要附带条件。Desktop Commander 原来的卡片写着 24 个工具,测试客户端却收到 25 个。作者检查 main 分支上的 src/server.ts,其 package.json 标注为 0.3.1,源码确实定义了 25 个不同工具。
差异来自客户端名称:连接方叫 desktop-commander-app 时,服务会隐藏 give_feedback_to_desktop_commander,清单于是变成 24 个。Playwright 也有类似的统计前提:测试只启用默认工具集,没有开启 README 中的可选 --caps 分组。
工具名字能帮你筛查,不能替你验证权限
作者按名称给工具分类,例如把 start_process 归入运行命令,把 write_file 归入写文件。这种办法成本低,适合接入前快速看一眼能力范围。
Desktop Commander 有 7 个与进程有关的名称:start_process、interact_with_process、kill_process、force_terminate、list_processes、read_process_output、list_sessions,另有 5 个写文件或目录的名称,以及 2 个读取或设置自身配置的名称。
Playwright 的 browser_evaluate、browser_run_code_unsafe 被归为执行代码;Chrome DevTools MCP 的 32 个工具中,evaluate_script 被规则归入这一类。名称中的 unsafe 值得进一步查看,但作者没有验证其行为。
mcp-server-git 有 5 个被归为改变仓库状态的工具:git_add、git_commit、git_checkout、git_create_branch、git_reset,另有 7 个只读名称。server-memory 的 9 个名称中,6 个涉及创建、添加或删除存储数据。
server-everything 的 get-env 名称表示返回环境变量,但作者没有调用它;该项目本身是测试客户端用的参考服务。mcp-server-fetch 只有一个 fetch 工具,名称显示它涉及网络访问。
这套规则也有明显盲区:codebase-memory 的 17 个工具中有 16 个无法分类,Context7 的 2 个工具都无法分类。这只能说明规则覆盖不到。反过来,叫作 read_file 的工具也可能限制可读路径,名称无法告诉你限制是否存在、是否有效。
我的判断
我认可作者把观察结果和结论边界分开,尤其是主动纠正容器 ID 引起的误报。对维护 MCP 接入清单的团队,这比给所有服务贴一个「通过」标签更有用。
但这还不是完整评测。10 个服务、同一天、同一台机器,能支持的是特定环境下的安装和协议兼容性判断。真实数据处理、权限边界、项目宣称的限制,都缺少验证。
与逐个执行工具相比,清单探测更适合做第一层检查;与阅读源码相比,按名称分类更快,却容易漏判,也可能把受严格限制的能力归得过宽。原文没有给出误报率、漏报率或效率对照,这部分应视为经验方法。
对描述做归一化也要保留原文。我的取舍是同时保存原始响应和用于比较的版本:告警可以少一点,追查差异时仍有证据可看。
能拿来做什么
- 给日常 AI 编程环境增加 MCP 接入检查,记录服务版本、运行镜像、客户端名称,以及
initialize、tools/list的响应,方便复现清单差异。 - 在 Node 探测脚本中分开计算描述和 schema 的哈希;发现变化后先查看具体字段,再判断是否需要调整接入配置。
- 接入浏览器或 Git 类 MCP 时,先核对执行代码、提交、切分支、重置等工具,再用合成数据验证实际需要的操作,分别记录清单检查与调用结果。
- 比较团队成员的工具数量时,先对齐客户端身份和
--caps配置,再排查版本与安装问题。