dsh-mcp-manager
English | 简体中文
为 DeepSeek Harness (DSH) 提供 MCP(Model Context Protocol)服务器的图形化管理界面:列出、添加、删除、 启用停用、测试连接,并可从本机已有的 MCP 配置中扫描导入。
本插件以独立 bundle 形式装入 DSH profile,不修改官方任何源码。
目录
问题背景
官方 base bundle 已包含 MCP 客户端 @deepseek-ai/dsh-mcp-client,但没有配套的管理界面。每台 MCP
服务器需要作为一条 insert 条目手工写入 profile 用户层的 cordis.patch.yml:
- insert:
- id: mcp-filesystem
name: "@deepseek-ai/dsh-mcp-client"
config:
serverName: filesystem
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "D:\\work"]
问题在于该文件同时是 boot 阶段的输入:
- 顶层必须是一个 YAML 数组,每个元素是一个 patch;
- 解析由官方
dsh-app-boot使用 js-yaml 加自定义 schema 完成,解析失败属于致命错误 —— 应用直接无法启动,而界面上没有任何修复入口; - 同一个文件还是用户自己的 patch 层,手工编辑时容易破坏其它内容。
因此需要一个带校验的图形化编辑入口:把配置当作结构化数据读写,写入前先验证,并且只修改自己负责的那一段。
环境要求
| 项 | 要求 |
|---|---|
| Node.js | ^22.19.0 || >=24 |
| DeepSeek Harness | 官方桌面版(Windows)。本插件依赖其 dsh-desktop-host 的 process.argv 布局与随包 Node / pnpm |
| 官方 base bundle | 需包含 @deepseek-ai/dsh-mcp-client(官方 base bundle 已自带) |
| 可选 | 运行时 node_modules 中存在 @modelcontextprotocol/sdk,「测试连接」功能需要它 |
安装
安装分两步。第二步不可省略 —— 只执行 pnpm add 不会把插件加入 bundle 层栈。
第一步:把包装进 profile
# 1. 进入目标 profile 目录(官方桌面版使用 desktop profile)
cd ~/.dsh/profiles/desktop
# 2. 用官方桌面版随包的 pnpm 安装
# 随包 pnpm 位于 <安装目录>/resources/runtime/pnpm/bin/pnpm.cjs,
# 可用随包 node 执行:node "<该路径>" add ...
pnpm add "file:<插件目录绝对路径>"
也可以直接从 GitHub 安装:
pnpm add "github:VCPr0j3k7/dsh-mcp-manager"
第二步:登记进 dsh.profile.bundles
编辑 profile 目录下的 package.json,把包名加入 dsh.profile.bundles 数组:
{
"name": "dsh-profile-desktop",
"private": true,
"dependencies": {
"dsh-mcp-manager": "file:<插件目录绝对路径>"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-mcp-manager"
]
}
}
}
dsh.profile.bundles 是 profile 的 bundle 层栈:DSH 只从这里列出的 bundle 装配插件树。包已安装但未登记时,
插件不会加载,界面上也不会出现任何提示 —— 现象只是「侧边栏里没有 MCP 连接器」。这是本插件最常见的安装问题。
第三步:重启
配置装配发生在启动阶段。修改 package.json 或 cordis.patch.yml 之后必须重启 DSH 桌面版,
运行中的实例不会热更新。重启后侧边栏应出现「MCP 连接器」入口。
卸载
cd ~/.dsh/profiles/desktop
pnpm remove dsh-mcp-manager
随后从 dsh.profile.bundles 中删除 dsh-mcp-manager 这一项,并重启 DSH。
卸载不会删除已经写入 cordis.patch.yml 的 MCP 服务器条目 —— 它们位于标记区块内,会继续由官方
@deepseek-ai/dsh-mcp-client 使用。如需一并清除,手工删除该区块(含起止标记行)即可。
工作原理
两个半边
| 半边 | 运行位置 | 装载方式 |
|---|---|---|
宿主半边 index.js + host/ |
Node.js(官方宿主进程) | Cordis 插件:导出 name / inject / apply(ctx) |
客户端半边 client.js |
浏览器(桌面外壳内的 SPA) | window.__ModuleLoader__.load({ id, factory }) |
宿主半边在 ctx.webServer 上注册一条 kind: 'prefix' 路由 /dsh-mcp-manager/api,
前缀内部的派发由 host/router.mjs 完成。这样注册点只有一处,卸载时由框架统一清理。
请求与响应
所有接口统一返回信封:
{ "ok": true, "data": { /* ... */ } }
{ "ok": false, "error": "可读的错误信息" }
业务失败同样返回 HTTP 200,状态码只用于表达传输层事实(未知路由返回 404)。请求体上限 8 MB。
基址探测
客户端页面由官方外壳提供,其来源(origin)可能是宿主的 HTTP 地址、外壳的自定义 scheme,或是不透明来源。
因此客户端在首次请求时按顺序尝试候选基址(相对路径 → http://dsh.internal),并以 /info 返回的
data.plugin 是否等于本插件 id 作为判定依据 —— 仅返回 200 不足以判定,SPA 的兜底路由会把未知路径也
返回 200 + HTML。选定后记录,后续请求直接复用。
事件通道
宿主事件(如 MCP 配置更新日志)通过 GET /events?since=<seq> 拉取,客户端以 300ms 间隔轮询。
不使用 SSE,是因为外壳对自定义 scheme 的响应体转发是否会缓冲无法确认,轮询没有这个不确定性。
配置写入
真正的 MCP 连接由官方 @deepseek-ai/dsh-mcp-client 完成,本插件只负责把条目写进 profile 的用户层 patch。
profile 的 patchReload 为 startup,因此所有写操作都返回 needsRestart: true,由界面给出重启提示条。
YAML 库的来源
写入前的校验必须使用官方同一套库,否则可能出现「本插件认为合法、boot 认为非法」的情况。因此
host/yaml-libs.mjs 按绝对路径从以下位置依次查找 js-yaml(require 目录入口,不看 exports):
- 运行时目录
<runtimeDir>/node_modules process.argv[2]给出的运行时目录下的node_modules- profile 目录下的
node_modules <DSH_HOME>/profiles/node_modules
运行时中的那一份优先,profile 中的那一份(即本包声明的 js-yaml 依赖)作为兜底。
界面
侧边栏「MCP 连接器」入口,页面分三个标签页:
| 标签页 | 内容 |
|---|---|
| 已接入 | 当前 profile 中的服务器卡片:传输方式、命令或地址、条目 id、连接状态、工具数量、原始配置。支持测试连接、启用停用、删除 |
| 发现 | 分组展示可一键接入的服务器:其它 AI 客户端配置、其它 DSH 家目录、内置常用目录。点击「接入」会把已填好的参数带入添加表单 |
| 添加 | 粘贴导入(mcpServers JSON 片段或一行 npx / uvx 启动命令)与手动表单(stdio / streamable-http) |
连接状态来自宿主而非本地推断:只有 mcp__<serverName>__* 的工具确实出现在宿主的工具注册表中,
才显示为「已连接」;配置已写入但尚未重启时显示为「待重启生效」。
「添加」页支持先测试再写入,避免出现「重启后才发现连不上」。
宿主接口
全部挂在 /dsh-mcp-manager/api 下。
公共路由(由 host/router.mjs 提供):
| 路由 | 说明 |
|---|---|
GET /info |
插件与宿主信息;data.plugin 用于客户端校验基址 |
GET /config |
读取共享配置 |
POST /config |
深合并写入共享配置 |
POST /open-external |
用系统默认程序打开网址(仅放行 http / https) |
POST /pick-directory |
弹出系统目录选择器 |
GET /restart-pending |
profile 关键文件的修改时间是否晚于宿主就绪时刻 |
POST /restart-host |
请求重启宿主。插件无法重启宿主进程,该路由始终返回错误,由界面提示手动重启 |
GET /events?since=<seq> |
拉取宿主事件(客户端 300ms 轮询) |
本插件路由(由 index.js 注册):
| 路由 | 说明 |
|---|---|
GET /mcp/list |
列出当前 profile 中的服务器,并附带宿主侧真实状态 |
POST /mcp/add |
新增服务器;同名时需显式传入 overwrite: true |
POST /mcp/remove |
按条目 id 删除服务器 |
POST /mcp/toggle |
启用 / 停用(写入 disabled 字段,不删除配置) |
GET /mcp/scan |
扫描本机可供接入的 MCP 服务器 |
POST /mcp/test |
实际发起一次 MCP 握手并取回工具列表 |
POST /mcp/parse |
解析粘贴的 JSON 片段或一行启动命令 |
配置的落地方式
本插件只修改 cordis.patch.yml 中由成对标记注释围起的区块:
# >>> dsh-desktop:mcp —— 以下内容由桌面版维护,请勿手工编辑
- insert:
- id: mcp-filesystem
name: "@deepseek-ai/dsh-mcp-client"
config: { /* ... */ }
# <<< dsh-desktop:mcp
几条硬性约定:
- 区块内容是完整的 patch 列表项(
- insert: [...]),而不是片段。cordis.patch.yml的顶层必须是 一个 YAML 数组,把insert:平铺进文件会导致解析失败。 - 写入前先验证:生成的候选全文会用官方同一套 js-yaml 解析一遍,通过后才落盘。
- 原子写入:先写临时文件再
rename,任何时刻磁盘上要么是旧版、要么是新版,不会留下半截文件。 - 幂等:同一份配置反复写入得到同一份字节,因此每次启动的同步不会反复改写 profile。
- 区块之外不修改任何字节:文件中手工编写的 MCP 条目会在界面上以「手工条目」列出,只读展示,不可通过 本插件修改或删除。
内置服务器目录
「发现」页附带一份内置目录,只收录官方或广泛使用、且用 npx 一条命令即可启动的服务器:
| 名称 | 说明 |
|---|---|
| 文件系统 | 让模型读写指定目录下的文件(必须指定一个目录,否则会被拒绝启动) |
| 长期记忆 | 基于知识图谱的记忆服务器:把事实写进去,之后跨会话取回 |
| 顺序思考 | 把复杂问题拆成可回溯的思考步骤,适合需要多步推理的任务 |
| Everything | 官方演示服务器:提供一组示例工具,用于确认 MCP 通路是否打通 |
| Playwright 浏览器 | 让模型真正打开一个浏览器:点击页面、填写表单、截图、抓取内容 |
| Context7 文档 | 按库名获取最新的官方文档与代码示例,减少模型记错 API 的情况 |
首次启动这些服务器需要联网由 npx 拉取包。带参数的条目会预先填好表单字段,由用户确认后再写入。
启用条件
| 半边 | 条件 |
|---|---|
| 宿主半边 | 声明 inject = ['webServer'],需要宿主组合中存在 webServer 服务(官方 base bundle 已提供) |
| 客户端半边 | 声明 dsh.client.inject = ['@deepseek-ai/dsh-client-ui-slots'],dsh.client.platform = 'web' |
| 两者共同 | 包名必须登记在 profile package.json 的 dsh.profile.bundles 数组中 |
另外,读写 MCP 配置需要同时拿到运行时目录与 profile 目录(来自 process.argv[2] 与 process.argv[3])。
两者缺任一时,GET /mcp/list 会返回可读的错误信息,而不是抛出 ENOENT。
测试
npm test
test/check.mjs 为纯 Node 自检(37 项),不依赖 DSH 运行时,覆盖:
package.json的声明与入口文件是否真实存在(dsh.bundle.patch、exports['./client']、dsh.client.platform);- 宿主半边的导出契约(
name/inject/apply)与apply()是否正常注册前缀路由; - 路由对齐:从
client.js中提取客户端调用的全部路由,逐条确认在宿主路由表中有实现; /info的data.plugin、未知路由回 404、ctx.effect登记了清理函数;- 共享配置的默认值包含
githubToken字段。
自检会把 DSH_HOME 与 profile 目录指向本次运行创建的临时目录,因此不会读写真实的 ~/.dsh。
端到端验证:重启桌面版后,侧边栏应出现「MCP 连接器」;在「发现」页接入一台服务器并重启, 「已接入」页应显示「已连接 · N 个工具」。
已知限制
- 修改必须重启宿主才生效。 profile 的
patchReload为startup,这是官方组合的装载时序,不是本插件的 取舍。界面会给出重启提示条。 - 插件无法重启宿主进程。
POST /restart-host始终返回错误(结束宿主进程会连带丢失用户会话), 因此界面上的「立即重启」会收到明确错误提示,需要手动退出并重新打开 DSH。 - 手工编写的条目只读。 不在标记区块内的 MCP 条目会在界面上列出,但无法通过本插件修改或删除, 以免覆盖用户自己的内容。
serverName有格式限制。 只允许[A-Za-z0-9_-]{1,32},与官方dsh-mcp-client的校验一致。 从其它客户端导入时会按该规则做字符替换与截断。- 「测试连接」依赖运行时中的 MCP SDK。 需要运行时
node_modules下存在@modelcontextprotocol/sdk/dist/esm/client;缺失时该功能返回明确错误,其余功能不受影响。 - 内置目录需要联网。 服务器包由
npx在首次使用时拉取,离线环境无法启动这些服务器。 - 扫描只覆盖各客户端的默认配置路径。 Claude Desktop / Claude Code / Cursor / VS Code / Windsurf /
Cline / Roo Code 以及
~/.dsh之外的配置文件不会被发现。 - 写入需要 profile 目录可写。 目录只读或运行时目录缺失时,读写类接口会返回可读的错误信息。
No comments yet. Be the first to write one.