dsh-mcp-schema-drift
v0.3.0
Published
Read-only MCP tool-catalog drift checks for DeepSeek Harness
Readme
dsh-mcp-schema-drift
MCP 服务地址没有变,并不意味着它的工具和参数没有变。dsh-mcp-schema-drift 会在 DSH 实际可见的 MCP 工具注册表变化后自动检查,也可以获取显式选择的工具目录,生成 DSH 公共工具名以及 TypeScript/Python 参数投影,再进行对比。
显式探测只进行 MCP 初始化和分页 tools/list,不发送 tools/call。自动检查复用 DSH 已注册的 MCP 输入 schema,不会另建连接,也不会调用 MCP 工具。
目录
它是什么
插件提供两个入口,使用相同的快照和对比实现:
| 需求 | DSH slash 命令 | 独立 CLI |
| --- | --- | --- |
| 选择目标 | 管理员配置的 profile 名称 | 显式指定的 JSON 探测配置 |
| 手动采集 | 当前快照与命名保存的基线 | JSON 快照文件或命名基线库 |
| 选择基线 | 命名的持久基线,或同一 Agent 的上一次成功探测 | 显式指定文件或命名基线库 |
| 查看结果 | 命令返回各级别的数量 | JSON 和 Markdown 包含逐条发现 |
| 保留历史 | 自动状态与命名手动基线会跨重启私有保存 | 文件保留到你修改或删除为止 |
| 发布检查 | 手动调用 | --fail-on breaking 检测到破坏性变化时退出码为 2 |
slash 命令通过 dsh-commands 注册,并设置 recordInput: false。它是供用户执行的命令,不是暴露给模型调用的 MCP 工具。
为什么需要它
升级 MCP 服务、更换部署、审查新工具目录,或者排查“以前能用的参数为什么失效了”,都适合先比较快照。例如,search 工具原来只要求 query,升级后新增必填的 locale,旧调用就可能被拒绝。
DSH 适配还引入另一层变化:原始名称会映射为公共工具名,参数结构也可能在 TypeScript/Python 渲染时丢失。插件把这些派生信息一起记录下来。
当前实现只覆盖部分兼容性规则。没有发现问题,不代表完整兼容,也不代表服务运行行为正确。
使用示例
配置好名为 inventory 的目标后,先执行:
/mcp-drift profiles
/mcp-drift probe inventory先把经过审查的状态保存一次(名称由你决定):
/mcp-drift save inventory before-upgrade升级服务或改变目录后,可在任意后续 Agent 上下文中执行:
/mcp-drift probe inventory
/mcp-drift diff inventory before-upgrade如果变化是新增一个必填参数,并禁止额外参数,结果示例如下:
Drift for inventory: 2 breaking, 0 unknown, 0 adaptation-loss, 0 informational finding(s).手动对比发生在执行 diff 时。除此之外,DSH 的工具注册表更新会自动触发检查,覆盖首次同步、notifications/tools/list_changed、重连和工具 generation 替换等常规路径。自动检查比较的是 DSH 实际暴露的 mcp__... 工具输入契约,因此不会声称识别出了某个远端服务的具体版本。
第一次看到非空目录时只建立基线,不提醒。后续变化会在当前活跃根会话显示持久化的 DSH 命令卡片。工具短暂消失会默认等待 5 秒,避免 generation 切换过程中的误报删除。
安装
安装到 DSH
需要 Node.js >=22、提供 Cordis 和 commands 服务的 DSH profile,以及下例使用的 pnpm。该包刻意不声明任何 DSH 运行时依赖,始终使用宿主提供的服务。
如果使用默认主目录下的 web profile:
cd ~/.dsh/profiles/web
pnpm add [email protected]如果你的目录不同,请使用实际 profile 路径。随后在该目录的 cordis.patch.yml 中添加以下条目。如果文件只有空数组 [],用下面的列表替换它;已有其他配置时保留它们,并确保 ID 不重复。
- insert:
- id: mcp-schema-drift
name: dsh-mcp-schema-drift
config:
profiles: {}重启使用这个 profile 的 DSH 宿主,或者使用宿主支持的补丁重载机制。自定义启动器可能只在启动时读取配置。执行 /mcp-drift profiles 验证;空配置会提示管理员添加目标,具体见配置。
独立 CLI
npm install --global [email protected]命令名为 mcp-schema-drift,不需要运行中的 DSH 会话。仅全局安装 CLI,不会在 DSH 中注册 slash 命令。
更新与卸载
在 profile 目录中执行 pnpm add dsh-mcp-schema-drift@latest 更新,然后重载或重启宿主。只有可选的会话内上一份快照历史会清空;命名基线会保留。
卸载时,移除补丁文件中的插件条目,在 profile 中执行 pnpm remove dsh-mcp-schema-drift,再重载或重启。全局 CLI 可用 npm uninstall --global dsh-mcp-schema-drift 卸载。
命令
| 命令 | 行为 |
| --- | --- |
| /mcp-drift profiles | 列出名称、可选标签、传输类型和启用状态 |
| /mcp-drift | profiles 的简写 |
| /mcp-drift status | 显示自动检查是否就绪及此会话最近结果 |
| /mcp-drift probe <profile> | 获取当前快照,保留在本会话中 |
| /mcp-drift save <profile> <baseline> | 将当前快照以安全的命名基线持久保存 |
| /mcp-drift baselines <profile> | 列出已保存的基线名、工具数、摘要前缀和保存时间 |
| /mcp-drift diff <profile> [baseline] | 对比当前快照与命名基线;不传名称时对比会话内上一次快照 |
| /mcp-drift delete <profile> <baseline> | 删除该指定命名基线 |
profile 列表末尾会引导执行 probe、保存命名基线、之后再次 probe 和 diff。如果没有 profile,则提示先添加配置。列表不会展示 URL、命令参数、环境变量或请求头。
失败或取消的 probe 不更新历史。已保存的基线不可覆盖:同名保存会失败,避免静默替换经过审查的契约。diff 不清空历史。省略基线名称时仍是 Agent 会话隔离的旧行为;命名基线可被其他 Agent 和重启后的 DSH 使用。delete 必须指定准确 profile 和基线名。
自动检查默认开启,不需要在 profiles 中配置目标。使用 /mcp-drift status 可确认宿主已连接到 tools 和 agents 服务。
配置
这里有两种 profile:DSH profile 是宿主配置目录,例如 ~/.dsh/profiles/web;MCP drift profile 是本插件 config.profiles 下的一项命名目标。插件不会自动导入 DSH 已配置的 MCP 服务。
Streamable HTTP 服务
将安装示例中的空 profiles 替换为:
profiles:
inventory:
label: Inventory service
enabled: true
probe:
serverName: inventory
transport: streamable-http
url: https://example.test/mcp
limits:
totalTimeoutMs: 20000
requestTimeoutMs: 5000
maxPages: 100
maxTools: 1000
maxCatalogBytes: 4194304
maxSchemaBytes: 131072将 URL 换成真实地址。需要鉴权时,可在 probe 中添加字符串映射 headers,例如 Authorization: Bearer YOUR_TOKEN。真实凭据应由受保护的部署配置提供;插件本身不会对这些字符串做环境变量插值。
本地 stdio 服务
profiles:
local_inventory:
label: Local inventory MCP
probe:
serverName: inventory
transport: stdio
command: /absolute/path/to/node
args:
- /absolute/path/to/server.mjs
cwd: /absolute/path/to/server-directory
env:
NODE_ENV: production路径必须能被 DSH 宿主进程访问。command 是可执行程序,参数通过独立的 args 数组传入,不是整条 shell 命令。虽然不调用工具,启动服务本身仍可能产生副作用;请使用可信服务和合适的测试或只读账号。
配置参考
自动检查默认启用。大多数宿主不需要改动;如需显式配置,可使用:
automatic:
enabled: true
# stateDir: /private/path/for/this-host
debounceMs: 1000
removalGraceMs: 5000| 字段 | 必填 / 默认 | 含义 |
| --- | --- | --- |
| profiles | 必填映射,允许 {} | 可选择的探测目标 |
| automatic.enabled | true | 设为 false 关闭注册表驱动的自动检查 |
| automatic.stateDir | DSH 主目录下的私有位置 | 可选的宿主专属持久基线目录 |
| automatic.debounceMs | 1000 | 连续注册表事件的合并延迟,范围 10–60000 |
| automatic.removalGraceMs | 5000 | 工具持续缺失多久才报告删除,范围 10–60000,且不得小于 debounce |
| baselineDir | $DSH_HOME/mcp-schema-drift/baselines | 可选的命名手动基线私有目录 |
| profile 键名 | 必填,匹配 [a-z][a-z0-9_-]* | slash 命令使用的名称 |
| label | 可选 | 展示标签,不要放入秘密 |
| enabled | 除非为 false,否则启用 | 禁用后仍列出,但不允许 probe 和 diff |
| probe | 必填 | 连接配置 |
| probe.serverName | 必填,匹配 [A-Za-z0-9_-]{1,32} | 用于公共工具名的稳定本地命名空间 |
| probe.transport | 必填 | stdio 或 streamable-http |
| probe.url | HTTP 必填 | http: 或 https: MCP 地址 |
| probe.headers | HTTP 可选 | 字符串到字符串的请求头映射 |
| probe.command | stdio 必填 | 可执行程序名称或路径 |
| probe.args | stdio 可选 | 字符串数组 |
| probe.cwd | stdio 可选 | 子进程工作目录 |
| probe.env | stdio 可选 | 传给 SDK transport 的字符串环境变量映射 |
| probe.limits | 可选 | 部分覆盖默认探测限制 |
serverName 不是经过验证的远程服务身份。对比时请保持一致:修改它可能改变公共工具名,即使远程目录没有变化。
CLI 与 CI
将服务目录保存为文件
创建 local-probe.json,内容是 profile 内部 probe 的字段,不需要外层 profile 映射:
{
"serverName": "inventory",
"transport": "streamable-http",
"url": "https://example.test/mcp"
}mcp-schema-drift probe --config local-probe.json --out reports/baseline.json
# 服务目录发生变化后:
mcp-schema-drift probe --config local-probe.json --out reports/candidate.json
mcp-schema-drift diff \
--baseline reports/baseline.json \
--candidate reports/candidate.json \
--json reports/drift.json \
--markdown reports/drift.md \
--fail-on breakingbaseline 与 candidate 请使用不同路径。命令成功时会覆盖输出文件,并按需创建父目录。失败的 probe 不会把已有快照替换为部分采集结果。
CI 命名基线库
同一种可移植 JSON 快照格式也支持小型命名基线库,适合把经过审查的 CI 基线放在源码目录以外:
mcp-schema-drift baseline save --dir .mcp-baselines --profile inventory --name before-upgrade --snapshot reports/baseline.json
mcp-schema-drift baseline list --dir .mcp-baselines --profile inventory
mcp-schema-drift baseline diff --dir .mcp-baselines --profile inventory --name before-upgrade \
--candidate reports/candidate.json --json reports/drift.json --markdown reports/drift.md --fail-on breaking同名保存会失败。若需更新,请先复制并审查 JSON,再用新的名称保存,避免无意间弱化发布门禁。
离线演示
仓库内置了两份示例,无需连接 MCP 服务即可运行:
git clone https://github.com/wrc093/dsh-mcp-schema-drift.git
cd dsh-mcp-schema-drift
npm ci
npm run snapshot -- --input fixtures/baseline-catalog.json --out reports/baseline.json
npm run snapshot -- --input fixtures/breaking-catalog.json --out reports/candidate.json
npm run diff -- \
--baseline reports/baseline.json \
--candidate reports/candidate.json \
--json reports/drift.json \
--markdown reports/drift.md \
--fail-on breaking最后一条命令预期退出码为 2:新目录要求 locale 且禁止额外属性,得到两项 breaking。原始目录需要 serverName 和 tools 数组,每个工具需要 name 与对象形式的 inputSchema。参见基线样例。
CLI 参数与退出码
| 子命令 | 必填参数 | 可选参数 |
| --- | --- | --- |
| probe | --config、--out | 无 |
| snapshot | --input、--out | 无 |
| diff | --baseline、--candidate、--json、--markdown | --fail-on breaking |
| baseline list | --dir、--profile | 无 |
| baseline save | --dir、--profile、--name、--snapshot | 无 |
| baseline diff | --dir、--profile、--name、--candidate、--json、--markdown | --fail-on breaking |
| 退出码 | 含义 |
| --- | --- |
| 0 | 执行成功;不加失败策略时,即使报告包含 breaking 也返回成功 |
| 1 | 输入、连接、探测、文件或命令用法错误 |
| 2 | 设置 --fail-on breaking 且发现 breaking;退出前已经写出报告 |
接入 CI 时,保留经过审查的基线,从目标部署采集候选快照,执行 diff,并在检查失败时仍保留两种报告作为产物。只有 unknown 或 adaptation-loss 不会让该策略失败;如果你的发布策略也要阻止它们,需要自行检查 JSON findings。目前失败策略只支持 breaking。
对比如何工作
- 初始化独立 MCP 连接并读完分页
tools/list,或者加载指定的原始目录。 - 派生 DSH 公共名称、清理输入 schema,并运行 TypeScript/Python 参数渲染。
- 对工具和对象键排序,计算 SHA-256 摘要,记录 adapter 身份与投影状态、摘要。
- 按原始工具名匹配,对实现覆盖到的变化进行分类。
| 级别 | 当前覆盖的例子 |
| --- | --- |
| breaking | 删除工具、公共名称变化、新增必填属性、number 收窄为 integer、已有 enum/const 集合中删除允许值、禁止额外属性、从封闭对象删除属性 |
| informational | 新增工具、扩展已有 enum/const 允许值 |
| adaptation-loss | 参数投影在结构化输出与 unknown/Any 之间变化,也包括结构恢复 |
| unknown | 不支持的词汇或形状、其他类型变化、从开放对象删除属性 |
0.3.0 的能力边界:
- 语义检查针对输入 schema。
outputSchema会计入探测大小限制,但不会进入派生快照或参与比较。 - 数组、联合类型、引用和 enum/const 转换的覆盖并不完整。一些变化可能没有告警,例如
items、oneOf内部变化,或者新添加 enum 限制。不支持的词汇可能让该 schema 的更深层分析停止。 - 保存 PTC 输出摘要,但目前 diff 检查的是投影状态变化,不是所有渲染文本变化。
- 保存 adapter 身份,但不会自动拒绝不同版本的 adapter。应使用相同 adapter、稳定命名空间生成待比较快照。
- 参数投影的状态与摘要来自插件内置的保守、无依赖渲染器。不支持的 JSON Schema 结构会主动降级为
unknown/Any;插件不会把任何 DSH 核心运行时包导入宿主 profile。 - 自动检查只覆盖当前 DSH 可见的 MCP 输入契约;它不是远端版本识别、输出兼容性证明、OAuth 检查或工具执行。独立 probe 也无法证明自己和 DSH 发给模型的工具来自同一代连接。
可行性探测记录 是带日期的上游证据与后续建议;其中的规划不等于已经发布的功能。
数据处理与探测限制
快照不保存连接配置与工具描述文本。schema 清理会递归移除 description、default、examples 这类注解;字面名称就是 description 的合法输入属性会保留。其他保留的名称、标题、enum、const 仍可能包含敏感信息。因此,在分享快照或把它当作权威基线之前,请检查内容。
自动检查状态与命名基线文件都使用原子写入和仅所有者可读写的权限,每个文件上限为 8 MB;每个 MCP profile 最多 100 个命名基线。它们只保存已经清理的 schema,不保存 probe URL、请求头、命令参数、工具调用参数或结果。
PTC 渲染器接收原始 schema,但快照只保存输出摘要和结构状态。不采集 prompt、工具调用参数或工具返回结果。底层连接错误会被返回,可能包含服务详情,错误文本没有保证完整脱敏。
| 限制 | 默认值 | 含义 |
| --- | ---: | --- |
| totalTimeoutMs | 20000 | 初始化和目录采集的总时间预算 |
| requestTimeoutMs | 5000 | 单次请求超时,不超过剩余总预算 |
| maxPages | 100 | 最多页数 |
| maxTools | 1000 | 最多工具数 |
| maxCatalogBytes | 4194304 | 所有规范化输入/输出 schema 对的字节数总和 |
| maxSchemaBytes | 131072 | 单个工具规范化 schema 对的最大字节数 |
限制必须是正的安全整数;单次请求超时不能大于总超时。重复工具名、重复分页 cursor、预算耗尽、schema 过大都会中止采集。字节检查针对解析后的 schema,不是完整网络响应、描述文本或 transport 的内存用量;这些限制也不是 stdio 服务的沙箱。
常见问题
| 现象 | 检查方向 |
| --- | --- |
| slash 被当成普通聊天 | 是否安装到实际使用的 DSH profile、补丁是否生效、是否存在 commands 服务、是否重载或重启 |
| 没有 profile | 添加 config.profiles;已有 DSH MCP 连接不会自动导入 |
| profile 不存在或禁用 | 运行 /mcp-drift profiles,使用准确且启用的名称 |
| 没有当前快照 | 在 save 或 diff 前执行 /mcp-drift probe <profile> |
| 命名基线不存在 | 执行 /mcp-drift baselines <profile>;名称只能用小写字母、数字、_、- |
| 还没有自动提示卡片 | 第一次观察只建立基线;运行 /mcp-drift status,并在真实 MCP 目录更新或重连后查看 |
| 意外报告工具删除 | 确认工具缺失是否已超过 automatic.removalGraceMs;generation 切换慢时可适当增大它 |
| 找不到 stdio 程序 | 检查绝对路径、宿主环境和工作目录 |
| HTTP 鉴权失败 | 检查地址与受保护的请求头配置;不支持交互式 OAuth |
| 超限或重复 cursor | 先检查服务分页和大小,再酌情调整限制 |
| schema 变了但没有发现 | 核对上面的规则覆盖范围,并直接审查快照 schema |
开发
npm ci
npm run build
npx tsc --noEmit
npm test
npm pack --dry-runbuild 将 JavaScript 和类型声明输出到 dist/。测试覆盖快照、diff 规则、DSH 渲染、本地 MCP fixture 的受限分页采集,以及 slash 命令行为,不需要生产 MCP 服务。
| 文件 | 职责 |
| --- | --- |
| src/dsh-plugin.ts、src/baseline-store.ts | 命令、配置目标与私有命名基线库 |
| src/probe.ts | 配置校验与受限发现 |
| src/snapshot.ts、src/canonical.ts | 快照、清理、稳定 JSON 与摘要 |
| src/dsh-adapter.ts | 命名与 PTC adapter |
| src/diff.ts | 变化判定规则 |
| src/cli.ts、src/report.ts | CLI 文件、退出策略、Markdown 报告 |
| test/、fixtures/ | 回归检查与离线示例 |
本地 DSH 开发时,先构建,再把源码目录安装到开发 profile,修改后重载或重启宿主。不要把探测凭据和生成的报告提交到源码仓库。
贡献
欢迎提交 Issue,附上最小化且脱敏的 baseline/candidate 与预期发现。规则修改请增加对应回归测试并运行上述检查;文档需要区分已经实现与尚未支持的情况。
许可证
本项目采用 MIT 许可证。
