给 DeepSeek Harness(dsh)添加 MCP 服务器:从 opencode.json 迁移实战
给 DeepSeek Harness(dsh)添加 MCP 服务器:从 opencode.json 迁移实战
DeepSeek Harness(CLI 命令 dsh,npm 包 @deepseek-ai/dsh)是 DeepSeek 官方在 2026 年 8 月开源的 Agent 框架,定位对标 Claude Code。其设计哲学是 "一切皆插件"——基于 Cordis 框架,所有能力(包括 MCP 客户端)都是插件。
本文承接上一篇《OpenCode 中让 MiniMax 两个 MCP 共存:Token Plan + 完整版》,把其中那两个 MCP 服务迁移到 dsh,并记录完整的踩坑过程。

一、为什么不能直接用 opencode.json 的写法
opencode 的 MCP 配置是 JSON-键值对:
{
"mcp": {
"MiniMax": {
"type": "local",
"command": ["uvx", "--from", "minimax-mcp", "--with", "mcp>=1.6.0,<2.0.0", "minimax-mcp"],
"environment": { "MINIMAX_API_KEY": "..." },
"enabled": true
}
}
}
dsh 的配置是 YAML patch 列表,每条 MCP 服务器是一个独立的插件实例:
- id: mcp-minimax
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: minimax
transport: stdio
command: uvx
args: [...]
env:
MINIMAX_API_KEY: "..."
核心差异:
| 维度 | opencode | dsh |
|---|---|---|
| 配置格式 | JSON | YAML |
| MCP 客户端 | 内嵌在 mcp.<name> 字段 |
独立插件 @deepseek-ai/dsh-mcp-client |
| 配置文件 | ~/.config/opencode/opencode.json |
$DSH_HOME/cordis.patch.yml 等 |
| 工具命名 | <server-name> 直接暴露 |
mcp__<serverName>__<toolName>(Claude Code / Codex 同款) |
| 热重载 | 需重启 | 监听 patch 文件,保存即重连 |
二、前置条件
- Node.js ≥ 16——
npx直接用,无需全局安装 - uvx 已安装(
uv tool install uv或 astral-sh/uv) - 操作系统:Windows / macOS / Linux 均可
$DSH_HOME 默认值:
- Windows:
C:\Users\<YOU>\.dsh\ - macOS / Linux:
~/.dsh/
也可通过环境变量覆盖:$env:DSH_HOME = "D:\custom\path"。
三、第一步:触发首次初始化
dsh 的 web / headless profile 在首次使用时自动从模板初始化。如果不想打开 Web UI,可以用 --dump-default-config 安全地创建目录并打印 Bundle 层:
npx -y @deepseek-ai/dsh --profile web --dump-default-config
执行后 C:\Users\zengt\.dsh\ 会出现:
.dsh/
├── profiles/
│ ├── web/
│ │ ├── cordis.yml # base 模板(不可手改)
│ │ ├── cordis.patch.yml # profile 级 patch(空)
│ │ ├── package.json
│ │ └── pnpm-workspace.yaml
│ └── node_modules/
│ └── @deepseek-ai/dsh-mcp-client/ # MCP 客户端插件已就绪
└── sessions/
此时 @deepseek-ai/dsh-mcp-client 已经下载完毕,无需 pnpm add。
四、第二步:写 cordis.patch.yml
在 home 级(C:\Users\zengt\.dsh\cordis.patch.yml)写入如下内容。这样所有 profile 都会共享这些 MCP 服务器。
# DSH home-level MCP overrides.
# Layer precedence (lowest to highest):
# bundles -> profile cordis.patch.yml -> THIS file -> --patch CLI overlay
# Edit & save; HMR reconnects without process restart.
- insert:
- id: mcp-minimax
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: minimax
transport: stdio
command: uvx
args:
- --from
- minimax-mcp
- --with
- mcp>=1.6.0,<2.0.0
- minimax-mcp
env:
MINIMAX_API_KEY: "<YOUR_KEY>"
MINIMAX_API_HOST: "https://api.minimaxi.com"
MINIMAX_MCP_BASE_PATH: 'C:\Users\zengt\MiniMax-mcp-output'
MINIMAX_API_RESOURCE_MODE: "local"
- id: mcp-minimax-search
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: minimax-search
transport: stdio
command: uvx
args:
- --from
- minimax-coding-plan-mcp
- --with
- mcp>=1.6.0,<2.0.0
- minimax-coding-plan-mcp
- -y
env:
MINIMAX_API_KEY: "<YOUR_KEY>"
MINIMAX_API_HOST: "https://api.minimaxi.com"
关键字段说明
- insert:——dsh 的 patch 语法,必须用insert/replace/remove显式声明操作。直接写- id: mcp-minimax会被当作"查找已存在条目",从而报patch: entry not found。serverName——MCP 工具命名前缀,[A-Za-z0-9_-]{1,32},在同一 dsh 实例内必须唯一。模型最终看到的工具名是mcp__minimax__text_to_image/mcp__minimax-search__web_search。transport: stdio——dsh 当前支持stdio和streamable-http两种 transport。MINIMAX_MCP_BASE_PATH——完整版 MCP 必填,所有生成的多媒体文件会落盘到此目录(YAML 单引号保留 Windows 反斜杠)。MINIMAX_API_RESOURCE_MODE: local——生成的文件返回本地路径而非 URL。
五、第三步:验证合并结果
npx -y @deepseek-ai/dsh --profile web --dump-config | Select-String -Pattern "mcp-minimax|dsh-mcp-client" -Context 0,12
应输出两条完整的 MCP 条目,包含 serverName、transport、command、args、env 等字段。
六、第四步:启动 dsh
npx @deepseek-ai/dsh web
默认监听 http://127.0.0.1:3080。打开浏览器后新建会话,模型会自动把新发现的工具列出来:
mcp__minimax__text_to_audio
mcp__minimax__list_voices
mcp__minimax__voice_clone
mcp__minimax__music_generation
mcp__minimax__generate_video
mcp__minimax__text_to_image
mcp__minimax__search_understand_image
mcp__minimax__play_audio
mcp__minimax__voice_design
mcp__minimax__list_mcp_resources
mcp__minimax-search__web_search
mcp__minimax-search__understand_image
试着调用一下,确认 Python 隔离环境被正确激活:
调用
mcp__minimax__text_to_image,prompt 写 "a cute cat"
七、三层 patch 优先级
dsh 的配置像叠千层糕,按从低到高的优先级合并:
┌─────────────────────────────────────────────┐
│ --patch C:\temp\extra.yml (CLI overlay) │ ← 临时调试最高
├─────────────────────────────────────────────┤
│ $DSH_HOME/cordis.patch.yml (home 级) │ ← 机器所有 profile 共享
├─────────────────────────────────────────────┤
│ $DSH_HOME/profiles/<name>/cordis.patch.yml │ ← per-profile 用户覆盖
├─────────────────────────────────────────────┤
│ profile manifest 的 bundles[] (内置) │ ← 默认 base
└─────────────────────────────────────────────┘
后层覆盖前层同名条目。mcp-minimax 写在 home 级会让所有 profile 都启用它;如果只想要 web profile 用,写在 profiles/web/cordis.patch.yml 即可。
八、进阶用法
8.1 HMR 热重载
cordis.patch.yml 被监听。编辑保存即触发重连,无需重启 dsh:
- 改
serverName——新工具名立即生效 - 改
env.MINIMAX_API_KEY——下次调用即用新 Key - 删一条 MCP 整块——该服务所有工具从模型面板消失
8.2 临时覆盖(不落盘)
npx @deepseek-ai/dsh web --patch C:\Users\zengt\Desktop\debug-mcp.yml
debug-mcp.yml 写法与上面相同,常用于临时换一组 Key 测试。
8.3 禁用 MCP 服务
直接删掉对应 - id: 块。下次 dump 即可看到该条目消失,没有 enabled: false 这种开关。
8.4 查 MCP 客户端源码
dsh 把 MCP 客户端实现放在 C:\Users\zengt\.dsh\profiles\node_modules\@deepseek-ai\dsh-mcp-client,想看重新连接策略、超时、命名规范化逻辑的话直接读。
九、故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
patch: entry "mcp-minimax" not found |
用了 - id: mcp-x 而不是 - insert: |
最外层必须包裹 - insert: |
| 编辑后无反应 | 缩进错了 | YAML 用 2 空格缩进,参考上面例子 |
MINIMAX_MCP_BASE_PATH 报"目录不存在" |
目录未创建 | New-Item -ItemType Directory -Path "C:\Users\zengt\MiniMax-mcp-output" |
spawn uvx ENOENT |
PATH 找不到 uvx | 在 command 里写绝对路径,如 "C:\Users\zengt\.local\bin\uvx.exe" |
ModuleNotFoundError: No module named 'mcp.server.fastmcp' |
mcp>=2.0 破坏 API |
保留 --with "mcp>=1.6.0,<2.0.0"(上文已加) |
工具名变成 mcp__minimax__t_e_x_t__t_o__a_u_d_i_o 之类怪名 |
serverName 含非法字符 |
严格满足 [A-Za-z0-9_-]{1,32} |
| dsh 启动但 Web UI 打不开 | 首次启动在拉前端构建产物 | 等待 1-2 分钟,或确认 5080 / 443 端口未被占用 |
十、写在最后
dsh 仍是 Developer Preview(npm 版本 0.1.0-rc.x),官方明确说会有破坏性变更。本文的插件 ID 和 YAML schema 是基于 0.1.0-rc.6 编写,等正式版发布时建议先看 CHANGELOG 再升级。
密钥管理跟 opencode 那篇一样——不要在仓库里提交明文 Key。建议:
- 把
cordis.patch.yml加进.gitignore(.dsh/目录整体忽略最简单) - 或者把
MINIMAX_API_KEY放到用户级环境变量,YAML 里写!!js process.env.MINIMAX_API_KEY(Cordis 支持!!js表达式)
下一篇会写《在 dsh 中开发自己的 Cordis 插件》——从 apply(ctx: Context) 开始,把上面的 MCP 客户端当模板。