Tools & Capabilities

dsh-mcp-manager

fishlikewater/dsh-mcp-manager · ★ 2

fishlikewater

MCP server manager: adds a Settings → MCP Servers page to DSH to configure MCP servers in the UI — saved means live: the host mounts/unmounts @deepseek-ai/dsh-mcp-client instances and the model gains or loses mcp tools immediately.

README body is shown in Chinese.

License
MIT
Added
2026-08-23
Maintenance
active
Install kind
github
npm
@fishlikewater/dsh-mcp-manager

No manifest check is available; listing does not imply compatibility or security review.

安装

dsh plugin --profile web add github:fishlikewater/dsh-mcp-manager

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: zh · Read Chinese README

Table of contents
  1. 特性
  2. 安装
  3. 桌面版
  4. CLI profile
  5. 使用
  6. 密钥引用
  7. 配置写在哪一层
  8. 版本兼容
  9. 工作原理
  10. 故障排查
  11. 开发
  12. 发布
  13. License

在 DeepSeek Harness 的设置页里管理 MCP 服务器。

DSH 原生要在 profile 的 cordis.patch.yml 里手写 @deepseek-ai/dsh-mcp-client 条目。这个插件把它搬到界面上:填表单、保存,实例随之挂载或卸载,不用改文件、不用重启。模型立刻拿到或失去 mcp__<名称>__<工具名> 形式的工具。

支持 DSH 0.1.7-rc.2 到 0.2.x,包含桌面版。0.3.0 起配置不再写进 settings.yaml,而是本插件 Loader 条目的 config.servers。

特性

  • 在「设置 → MCP 服务器」里增删改查,stdio 与 streamable-http 都支持
  • 每台服务器单独开关。关闭只是不挂载,配置保留
  • 保存即生效。servers 是 volatile 字段,走就地更新,不重载条目
  • 密钥不落盘。env / headers 的值可以写成 env:NAME / cred:NAME,挂载时才解析
  • 宿主侧不依赖 webServer,headless profile 一样能用,只是没有界面

安装

插件要同时出现在 profile 的依赖和 dsh.profile.bundles 里。本包声明了 dsh.bundle,所以 dsh plugin add 会一次做完两件事。

桌面版

桌面版用应用自带的 profile,路径是 $DSH_HOME/profiles/desktop($DSH_HOME 默认是 ~/.dsh,Windows 上是 C:\Users\<你>\.dsh)。它由应用直接加载,不在 CLI 的 profile 列表里,但 dsh plugin --profile desktop 照样认得 —— 那个 --profile 就是指向 $DSH_HOME/profiles/<名字>。

在侧栏的「插件」页里装(推荐)。打开「插件」→「添加插件」,填包名、tgz 的绝对路径或者 Git 地址,点「安装」,装完选「立即启用」。对话框里说的「包名」就是下面命令里 add 后面那一段;安装失败时页面会给出原因和重试。

或者命令行走一遍:

dsh plugin --profile desktop add @fishlikewater/dsh-mcp-manager
dsh plugin --profile desktop add D:\downloads\fishlikewater-dsh-mcp-manager-0.4.0.tgz

两条路做完的是同样两件事:在 profile 目录里跑 pnpm,再按已装状态把声明了 dsh.bundle 的包补进 dsh.profile.bundles。装完都要重启桌面版,bundle 层只在启动时组合。重启后打开「设置 → MCP 服务器」;条目没出现就是被版本门禁拦下了,见「版本兼容」。

PATH 上的 dsh 可能比桌面版自带的核心旧。检查一下:

dsh --version

如果低于桌面版的版本,改用应用目录里那个:

"D:\Program Files\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd" --version

两个都能装插件(都是往 profile 目录转发 pnpm),但只有和桌面版同版本的那个能拿 --dump-config 校验组合结果,也才能把版本门禁的判定还原成启动时的真实情况。

用 link: 指向本仓库时,Node 会从插件目录往上找 @deepseek-ai/*,可能命中仓库 node_modules 里的旧版本。用 tgz 或 npm 包安装可以避开。

CLI profile

dsh plugin --profile web add @fishlikewater/dsh-mcp-manager
dsh plugin --profile web add ./path/to/dsh-mcp-manager   # 或本地目录
dsh --profile web --dump-config                          # 能看到 mcp-manager 即可

卸载:dsh plugin --profile web remove @fishlikewater/dsh-mcp-manager。

使用

表格里的字段都能在设置页里填,只有 toolCallTimeoutMs 例外:它是条目级的,不是 volatile 字段,只能写进 patch。

字段 适用 说明
serverName 全部 [A-Za-z0-9_-]{1,32},全局唯一,决定工具名前缀
enabled 全部 默认 true
transport 全部 stdio 或 streamable-http,默认 stdio
command stdio 可执行文件,建议绝对路径
args stdio 数组,每行一个
env stdio 每行 KEY=VALUE,值可以是数字或布尔,也可以写成 env:NAME / cred:NAME
cwd stdio 可选
url http 例如 http://localhost:3000/mcp
headers http 每行 KEY: VALUE,值同样支持 env:NAME / cred:NAME
toolCallTimeoutMs 条目 单次工具调用超时,默认 60000

serverName 全局唯一是 mcp-client 的要求。重名时本插件只挂第一台,日志里记一条。

改任意一台都会重建全部实例,其余 MCP 连接会重连一次。设置页在可见时每 5 秒对一次 revision,外部改了 patch 会自动反映到界面上。

密钥引用

env 和 headers 的值可以指向别处存着的密钥,配置里只留引用文本:

写法 取值来源
env:NAME 宿主进程的环境变量
cred:NAME DSH 的 credentials 服务,即 resolve(NAME)

引用名限 [A-Za-z_][A-Za-z0-9_]*,带连字符的名字不会被当成引用。解析发生在挂载前,交给 mcp-client 的配置里是真实值,而 patch 文件和设置页里始终只有引用文本。解析不出来时保留字面值,并在宿主日志里记一条;credentials 服务不在时同样如此,不影响其它服务器挂载。

examples/mcp-servers.example.yaml 是一份可以直接抄的配置,stdio 和 streamable-http 各一。

配置写在哪一层

mcp-manager 条目由插件自带的 bundle patch 插入。要保存服务器清单,就在 profile 的 cordis.patch.yml 里对它做 id 定向覆盖:

- id: mcp-manager
  config:
    servers:
      - serverName: demo
        transport: stdio
        command: node

两个不要:

  • 不要用 insert:。bundle 里已经有同名条目,会撞 id。
  • 不要写进 $DSH_HOME/cordis.patch.yml。home 级 patch 在 profile patch 之后应用、优先级更高,而 DSH 会拒绝任何会被更外层覆盖的表单写入 —— 清单一旦落在 home 层,设置页就变成只读。

代价是每个 profile 各存一份清单,改的时候要改多处。这是「设置写入落在当前 profile 的 patch」带来的结果。

版本兼容

DSH 在导入插件前会拿运行中的版本核对它的要求。判定规则在 0.2.0 前后换过一次:

  • 0.2.0 起:manifest 声明了 engines.dsh,就只看这一个范围,peerDependencies 里 @deepseek-ai/dsh-* 的部分一律忽略
  • 0.1.x:没有 engines.dsh,逐个核对 peerDependencies 里 @deepseek-ai/dsh 和 @deepseek-ai/dsh-* 的范围

本包两边都声明,且指向同一个真实支持区间:

"engines": { "dsh": ">=0.1.7-rc.2 <0.3.0" },
"peerDependencies": {
  "@deepseek-ai/cordis": "^4.0.2",
  "@deepseek-ai/dsh-mcp-client": ">=0.1.0-rc.6 <0.3.0"
}

下界 0.1.7-rc.2 是硬要求:settings 命名空间从这一版才有,再早的 DSH 里插件能加载,设置页却是空的。上界 0.3.0 留给下一个可能改配置契约的大版本。

@deepseek-ai/dsh-mcp-client 单独放宽到 >=0.1.0-rc.6 <0.3.0。它跟着 DSH 在 0.2.0 跳到 0.2.1-rc.2,但 Config 契约没变 —— serverName、transport、command、args、env、cwd、url、headers、failOnStartupError 都还在,所以一条范围能盖住两条线。

别写版本枚举,也别用 >=0.1.0-rc.6 <0.2.0。DSH 用 includePrerelease 匹配,枚举会漏掉没列出的版本,写死的上界则会把 0.2.0-rc.1 这类预发布放行。

被门禁拦下的表现是插件根本不出现在组合里:桌面版启动日志把它列进 skippedBundles,dsh --profile <名字> --dump-config 的输出里也找不到 mcp-manager 这一行。可以在 profiles/<profile>/compatibility.json 里为「包名@版本 × 运行时版本」写一条豁免,由插件管理器的授权流程管理;豁免按精确版本记录,插件或 DSH 升级都不继承。

工作原理

设置页 client.js(注册在 settings.section 插槽)
  │  ctx.remote.settings.describe() / mutate(ns, ops, expectedRevision)
  ▼
mcp-manager 条目的 config.servers  ←→  profiles/<profile>/cordis.patch.yml
  │  servers 是 volatile 字段,就地更新引用并发 loader/volatile-update
  ▼
宿主半 index.js,逐台 ctx.plugin(mcp-client),工具注册到 ctx.tools

命名空间就是 Loader 条目 id。 DSH 0.1.7 起用户设置统一成「Loader 条目的 config」,$DSH_HOME/settings.yaml 只剩一次性导入的作用,插件自注册的命名空间也不再保证对浏览器可见。所以这里既没有 ctx.settings.register(),也没有自建的 HTTP 路由。

servers 必须是 volatile 字段。 settings 服务只把 schema 里带 volatile 字段的活跃条目投影成命名空间:describe() 对每个条目求一次 volatileForm(schema),返回 undefined 就整条跳过。没有 volatile 字段的插件,命名空间永远不存在。写入也只接受落在 volatile 子树内的路径。

volatile 只能标在固定对象路径上,不能嵌套,所以要整列标在 servers 上。标进 ServerSchema 内部会被 schemastery 拒绝:

volatile fields require a fixed object path without an enclosing volatile field

volatile 变更不会重载条目。 Loader 只把新快照提交进运行中的引用,然后发 loader/volatile-update,apply 不重跑。宿主半自己记录已挂载的实例,收到事件先 dispose() 再按新值重新挂载。值是 cosmokit 的 Volatile<T>,读取要走 .get()。

值按 any 接收。 args、env、headers 的值在挂载前统一字符串化。早期版本把 env 的值约束成字符串,写一个 PORT: 8080 就会让整份 config 校验失败,所有服务器一起挂不上。现在单台写错只影响那一台,日志里记一条 [dsh-mcp-manager]。

mcp-client 惰性加载。 首次需要挂载时才解析模块:先试裸导入,profile 进程里的 runtime 解析拦截会把插件的裸导入路由到宿主自带的那份包;失败则用 ctx.pluginPackages.packageOf() 定位包目录,再按绝对路径导入。两条路都落在宿主那一份,避免出现第二个实例抢占 serverName。配置为空时完全不加载。

@deepseek-ai/schemastery 是普通依赖而不是 peer,静态导入,保证 schema 一定能加载。最低 ^3.18.4:.volatile() 在 3.18.1 还不存在,装到旧版本会在加载时抛 Schema.array(...).default(...).volatile is not a function。

故障排查

现象 排查方向
设置里没有「MCP 服务器」 插件不在该 profile 的 bundles 里,或被版本门禁拒绝。看侧栏「插件」页和启动时的 stderr
「没有找到 mcp-manager 配置命名空间」 条目 id 不是 mcp-manager、条目没被加载,或宿主半的 Config 里没有 volatile 字段(0.4.0 之前)
保存被拒绝,页面像只读 config.servers 被更外层的 patch 覆盖,通常是写在了 $DSH_HOME/cordis.patch.yml
「配置已被其他写入方改动」 revision 冲突。页面每 5 秒会自己刷新,重试即可
换了新版但行为没变 index.js 和 client.js 的改动要重启 DSH;config 和 patch 的改动会热生效
工具列表里没有 mcp__* 该台 enabled: false、config 校验失败,或 MCP 服务器自己连不上。在宿主日志里搜 [dsh-mcp-manager] 和 mcp-client(
提示无法加载 @deepseek-ai/dsh-mcp-client 该 profile 的 runtime 解析拦截没生效,或这个包不在运行时里
保存后 patch 文件被重排、注释消失 设置页按 schema 重写文件,属正常行为
服务器连不上但配置看着没错 值可能写成了 env:NAME / cred:NAME,而对应的环境变量或凭据没配。宿主日志里搜「引用保留字面值」
安装命令跑不通 手工兜底:在 profile 的 package.json 里加依赖、把包名加进 dsh.profile.bundles,再 pnpm install

开发

package.json            dsh.bundle.patch 与 dsh.client 双 manifest
cordis.patch.yml        bundle 层,插入 mcp-manager 条目
index.js                宿主半:Config schema,逐台挂载 mcp-client
client.js               客户端 bundle,手写维护,不需要打包器
examples/               配置示例
scripts/check-pack.mjs  npm pack 白名单校验
test/                   自测与迁移校验
npm test                               # 宿主半 + 客户端半自测
npm run check:pack                     # 发布内容白名单
python test/verify-profile-patch.py    # 校验两个 profile 的 patch,要能读到 $DSH_HOME

client.js 是给浏览器模块加载器用的产物,格式是 window.__ModuleLoader__.load({ id, factory }),id 必须是完整包名 @fishlikewater/dsh-mcp-manager:client-modules 按包名匹配 Loader 条目。

改完代码必须重启 DSH。Node 的 ESM 模块缓存不随文件变化失效,浏览器侧的 bundle 同理。

发布

npm publish

prepublishOnly 会先跑 npm test 和 npm run check:pack:自测不过,或者 files 白名单漏了 examples/、混进了 test/,发布都会中止。

包名用 scope 版 @fishlikewater/dsh-mcp-manager,无 scope 的 dsh-mcp-manager 已被他人占用。发布者账号必须是 fishlikewater。publishConfig.access: public 已经配好,不用带 --access public。

改包名要同步三处:cordis.patch.yml 的 name、client.js 的 id,以及各 profile 的 package.json。

License

MIT