@aiwayds/dsh-mcp-adapter
v0.4.2
Published
dsh plugin: token-efficient prompt-side shim for the official MCP client — folds mcp__* tool schemas out of every assembled prompt and exposes two constant meta-tools (mcp_list / mcp_call) instead
Maintainers
Readme
dsh-mcp-adapter
English | 简体中文
面向 DeepSeek Harness (dsh) 的省 token MCP 适配器——一个受 pi-mcp-adapter 启发的 prompt-side shim(提示词侧垫片)。
要求 dsh >= 0.1.5-rc.2 — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。不再支持 alpha 线。
问题
官方 @deepseek-ai/dsh-mcp-client 插件把发现的每个 MCP 工具都原生注册进工具表(mcp__<server>__<tool>),于是每次请求都要为每个 MCP 工具的完整 JSON Schema 付费——上游 README 原话:"Data-dependent schema cost is paid on every request while the tools are registered." 几个 server、几十个工具下来,就是每条消息烧掉数千 token,无论模型是否真的调用它们。
思路
本插件完全保留官方 dsh-mcp-client 作为连接层(transport、自动重连、tools/list_changed 重同步——全是上游现成的),只在提示词装配这一处介入:
- 每个匹配的
mcp__*工具 schema 被折叠出装配后的 prompt(system-prompt/assemblewaterfall); - 原位换上两个恒定 meta-tool,常驻 prompt 成本对 server/工具数量而言是 O(1):
mcp_list— 紧凑目录(工具名 + 截断描述,不含 schema);传tool按需展开单个工具的完整 schema,传server过滤,传verbose全量内联;mcp_call— 按{ tool, arguments }把调用分发到仍然注册着的定义上,运行上下文原样透传。
工具本身仍注册在 ctx.tools 里,所以 TUI 渲染、tools.restrict() 掩蔽照常工作——变的只是 prompt 载荷。折叠后恒定的工具列表也比上游"每次重同步就换代"的模式更利于 KV 前缀缓存。
一个管线细节:按子工具名(mcp__server__tool)匹配的 pre-execute / guard / post-execute 阶段不会在折叠调用上触发——注册表只会看到外层的 mcp_call。要管控 MCP 使用(审批、策略),请 guard mcp_call 本身。
图片结果保持原生行为:mcp_call 把 output.render 委托给被分发的子工具,并以同一个执行对象转发子工具的 finalizeContent——带图 MCP 结果仍会投影为持久附件引用,而不是把 base64 内联进上下文。
故障放行(fail-open): 若两个 meta-tool 未成功注册(重名冲突、启动中断),本插件不动装配结果——退回官方全量直通,绝不会让 MCP 工具变得不可发现。
Code Mode: 在 mode: 'code' 下线上本来就折叠为 run_code,本插件天然 no-op。
加载位置: 经宿主组合加载(即下方 cordis.patch.yml 的 insert 行)时全局生效——所有 agent 的装配都会被折叠;若经某个 agent 的 scoped context 加载,则只对该 agent 生效。
安装
保留(或新增)你的 @deepseek-ai/dsh-mcp-client 配置行,然后把本插件加在同处:
- insert:
- id: dsh-mcp-adapter
name: '@aiwayds/dsh-mcp-adapter'
config: {}dsh plugin --profile <name> add @aiwayds/dsh-mcp-adapter卸载
dsh plugin --profile <name> remove @aiwayds/dsh-mcp-adapter宿主会自动完成清理:dsh.profile.bundles 里对应的条目被拼接移除,插件的 patch 层随之失效。
有一份状态被刻意保留:~/.dsh/settings.yaml 里的 mcp-adapter: 小节——stable server id(1..99)和 disabled 门闩。它按设计永不回收:重装本插件后,每个 server 仍沿用之前的 id。
连这份状态也想清掉的话,请自行删除 settings.yaml 里的 mcp-adapter: 小节;重装时 id 会重新分配。
配置
| 键 | 默认值 | 含义 |
|---|---|---|
| prefix | "mcp__" | 要折叠的工具名前缀 |
| keep | [] | 保持原生进 prompt 的名字模式(* 通配)——对应 pi-mcp-adapter 的 direct 模式,适合高频、值得占一等座 schema 的工具 |
| servers | [] | server 白名单:非空时只有这些 server 的工具会被折叠 / 进目录 / 可分发(三处共用同一份名单) |
| descriptionLimit | 200 | mcp_list 目录里每条工具描述的最大字符数 |
config:
keep:
- mcp__fs__read_file
- mcp__github__*
servers:
- fs
- github信任边界: 默认所有匹配 prefix 的工具都会被折叠——前缀只是命名约定而非安全边界,第三方插件恰好用 mcp__* 注册的工具同样会折叠。若只信任官方 client 的 server,请在 servers 里显式列出;其余保持原生(仍可直调,只是不走 meta-tool)。
备注
mcp_call只接受匹配prefix的工具(配置了servers时还须在白名单内)——它不可能被用来绕过其它工具自己的 pre-execute 管线。- 已知边界(waterfall 次序):若某 listener 注册早于本插件、并在自己的
next()之后补插mcp__*schema,该 schema 会逃过折叠——本插件折叠的是它运行时装配结果里的内容。当前上游不存在这样的 listener。 - 目录里的 server 名是启发式提取:前缀后第一段
__分隔段(server 名规范为[A-Za-z0-9_-]{1,32},不会含字面__,故不会错分组)。 - 与 ben7am1n/dsh-mcp-proxy 可共存(它是 connection-side 代理、自带连接管理,工具名互不冲突)。该项目同样致谢 pi-mcp-adapter;本仓库是独立的 prompt-side 实现:复用官方 client 而不是重造连接层。
- 权衡(与 pi-mcp-adapter 相同):首次调用多一次发现往返;模型展开过的 schema 会占据后续上下文。
命令
本插件在平台 commands 服务上注册一条斜杠命令——该依赖是软性的:宿主若没有命令服务,折叠与两个 meta-tool 照常工作(只记一条日志警告,代价是没有 /mcp)。/mcp 展示状态;v0.2.0 起它同时也是整个 MCP server 进/出适配器的唯一控制面:
| 形态 | 输出 |
|---|---|
| /mcp 或 /mcp list | 树形总览——每行 server 带 stable [<id>] 前缀;disabled 的标 ⏸ disabled 且不列工具;尾部附折叠健康行 |
| /mcp list <name> | <name> 匹配某 server → 该 server 全部工具(disabled 的附加 ⏸ 说明行);匹配完整工具名 → 完整描述 + 完整 input schema |
| /mcp config | 当前生效的 prefix / keep / servers / descriptionLimit 及各自命中清单,外加持久 enable/disable 台账 |
| /mcp disable <id> | 把一个 server 整体闩上:工具强制折叠出 prompt(keep 与 servers 豁免一并覆盖)、从 mcp_list 目录消失、mcp_call 拒绝并给 /mcp enable <id> 指引 |
| /mcp enable <id> | 用同一个 stable id 复原 |
其余形态一律回复用法说明。/mcp 观测到的每个 server 都会分到一个稳定数字 id(1..99,最小空闲优先),经 dsh settings 服务持久化到配置文件的 mcp-adapter: 小节(默认安装即 ~/.dsh/settings.yaml)。id 跨重启、跨 re-sync 空窗保持不变,且永不回收——一个 id 永远指同一个 server;99 个用尽时由 /mcp 视图明确标注(id space exhausted (99/99): N server(s) beyond the cap cannot be gated,受影响的分组行会带标记)。
disable 是门闩式开关,不是真断连:官方 client 不提供断连 API,工具仍留在注册表里、连接照常运行——门闩只把它请出 prompt、目录与分发。三层门闩共用同一个判定函数,彼此之间以及与 /mcp 的展示永远不会口径不一。enable/disable 经 settings 服务持久化;没有 settings 服务时其余功能照常,只有 toggle 会回一条说明性报错。
一条真实边界:门闩生效在 prompt 侧(目录/分发层);记得完整工具名的模型仍可能原生直调 mcp__server__tool 成功——需要硬性拦截时,请配合管线 guard 或 tools.restrict()。
状态是二态语义:server 出现在列表里 = 它的工具在本 scope 可见——不可见不代表未启用(可能正在重连退避);官方 client 不暴露连接状态。健康行形如 meta-tools: mcp_list/mcp_call live · folding ACTIVE — folded N, kept M · ~X chars of schema out of prompt(meta-tools 存活且至少折叠一个工具时),否则降级为 fail-open(或无可折叠)提示。X 是被折叠 schema 的 JSON 字符数,刻意标注为字符而非 token。输出超过 400 行会被截断,并提示用 /mcp list <server> 收窄。
致谢
特别致敬 pi-mcp-adapter 及其作者 @nicobailon:本插件的核心思想——把无界的 MCP 工具面折叠为恒定的 meta-tool、schema 按需展开,从而无论挂多少 server 常驻 prompt 成本都是 O(1)——完全来自该项目;正是它重新定义了 MCP 集成应有的代价。本仓库是这个理念在 DeepSeek Harness 上的移植,机制上刻意走了不同的路线(复用官方连接层的 prompt-side shim),但灵感与理念的功劳归于原作。如果你用的是 pi,请直接用原版。
同样值得提及:ben7am1n/dsh-mcp-proxy 独立验证了 dsh 侧对这类方案的真实需求。
开发
npm install && npm run check && npm test@deepseek-ai/* 类型由 scripts/link-dsh-closure.mjs 从全局 dsh 闭包软链解析(precheck 自动执行)——它们被刻意排除在 package.json 之外,以保证类型图中只存在一份 cordis 实例。完整设计依据与上游参考见 DESIGN.md。
