DeepSeek Harness Hub
← 返回列表

sduwall/dsh-wall-mcp-manager

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

@sduwall/dsh-wall-mcp-manager

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/29 · 已提供中文文档
综合分
27.9
GitHub 分
27.9
用户评分
★ Stars
0
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add sduwall/dsh-wall-mcp-manager
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-settings@deepseek-ai/cordis-plugin-loader@deepseek-ai/dsh-mcp-client@deepseek-ai/dsh-tools
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

@sduwall/dsh-wall-mcp-manager

DSH(DeepSeek Harness)插件:集中管理 MCP 服务配置,并展示每个服务提供的工具、
调用参数与返回契约。

- 插件类型:Cordis Bundle(npm 包)
- 依赖:@deepseek-ai/cordis、@deepseek-ai/cordis-plugin-loader、
@deepseek-ai/dsh-mcp-client、@deepseek-ai/dsh-settings、@deepseek-ai/dsh-tools
(peer,由 DSH 运行时提供)、@deepseek-ai/schemastery
- 运行环境:Node.js ≥ 20(DSH 内置 Node ≥ 22)
- 硬依赖服务:loader、tools(inject: ['loader', 'tools'])
- 可选服务:settings(运行时改配置并热生效;缺失时按组合层配置工作)、
webServer(设置界面的配置桥;缺失时仍照常挂载 MCP,只是没有界面)

它做什么

本插件自己不实现 MCP 协议,只做两件事:

1. 对账:把配置里的服务清单收敛成一组 @deepseek-ai/dsh-mcp-client 实例
(一个实例 = 一个 MCP 服务)——新增则挂载、改了则更新、删了则卸载、没变则一动不动;
2. 呈现:通过回环配置桥把「配置 + 挂载状态 + 工具契约」交给设置界面。

为什么是对账而不是「全拆重建」:重建会让所有 MCP 重连一次。用户改一个服务的
备注文字,不该把另外五个的 stdio 子进程全杀掉重启。判定依据是实例配置指纹
(toMcpConfig 的输出序列化),因此只改 description 这类非 mcp-client 字段不触发重连。

工具命名与 serverName

MCP 工具在 DSH 里的公开名是 mcp____。因此:

- servers 用字典而非数组,键即 serverName,让「重名」在数据结构上就不可能
(serverName 在所有活动 mcp-client 实例之间必须唯一,重复会在挂载阶段抛错);
- serverName 受 [A-Za-z0-9_-]{1,32} 约束(与 mcp-client 内部常量一致),
非法名称在插件侧就被拦下并在界面显示原因,而不是只留一行日志;
- 改名等于换一个服务:工具名会整体变化,模型侧的引用也随之改变。
对账时先卸载不再需要的 serverName、再挂载新的,避免改名瞬间在唯一性检查上撞车。

界面按 mcp____ 前缀归组工具(最长前缀优先,正确区分 fs 与 fs_ro),
不反解析工具名——publicToolName 在需要字符替换或截断时会追加哈希,其源码明确
"the public name is never parsed to recover it",但 serverName 段不会被改写。

配置

配置命名空间为 dsh-wall-mcp-manager(settings 命名空间只允许小写 kebab-case,
不带 npm scope),形如:

dsh-wall-mcp-manager:
servers:
filesystem:
enabled: true
transport: stdio
description: 本地文件访问
command: npx
args:
- -y
- '@modelcontextprotocol/server-filesystem'
- D:/workspace
env:
LANG: zh_CN
secretEnv:             # 凭据类环境变量;没有凭据就整段省略,不要写成 {}
API_TOKEN: xxx
cwd: ''
toolCallTimeoutMs: 60000
failOnStartupError: false
weather:
transport: streamable-http
url: https://mcp.example.com/mcp
headers:
X-Trace: 'on'
secretHeaders:         # 凭据类请求头
Authorization: Bearer xxx

| 字段 | 默认值 | 说明 |
| --- | --- | --- |
| enabled | true | 停用则不挂载该服务(保留配置) |
| transport | stdio | stdio(本地子进程)或 streamable-http(远程 HTTP) |
| description | '' | 备注,仅本界面展示,不传给 mcp-client |
| command | '' | stdio 必填:启动命令 |
| args | [] | stdio:命令参数,逐项填写(不会按空格拆分) |
| env | {} | stdio:环境变量(明文,可在界面查看) |
| secretEnv | 无 | stdio:凭据类环境变量,role('secret'),永不出网 |
| cwd | '' | stdio:工作目录,留空继承 DSH 进程 |
| url | '' | streamable-http 必填:服务地址 |
| headers | {} | streamable-http:请求头(明文,可在界面查看) |
| secretHeaders | 无 | streamable-http:凭据类请求头,role('secret'),永不出网 |
| toolCallTimeoutMs | 60000 | 工具调用超时(毫秒,≥1000) |
| failOnStartupError | false | 启动失败是否让该实例整体失败 |

secretEnv / secretHeaders 在挂载时被合并进 env / headers(同名以凭据为准)——
对 mcp-client 而言它们和普通环境变量、普通请求头没有区别,拆成两个字段只是因为
Plaintext items must be viewable and editable in the UI, while credential items must be write-only and never readable; the two require different roles.

The two credential fields have no default value (explicitly .default(undefined)): the UI determines "configured / not configured" based on the set flag in the redacted view, and that flag is decided by "whether the value is undefined"; schemastery unconditionally adds a {} default value to every dict, so if it is not pushed back to undefined, credential slots that have never been filled in would also show as "configured". Therefore, when hand-writing yaml, do not write secretEnv: {}; if there are no credentials, omit the entire section.

Why not copy mcp-client's Schema

mcp-client's own Config is z.union([stdio, streamable-http]), but settings' redactSecrets only penetrates the three container types object / dict / array, and when it encounters a union it returns the entire subtree as-is (its source comment explicitly states "a secret buried inside a union branch is not reachable and must not be modeled that way"). If modeled as a union, credentials would go out over the network verbatim along with describe.

Therefore, a flat object + transport discriminator field is used here, and at mount time toMcpConfig reassembles a valid instance config by branch—outputting only the fields that branch should have; carrying even one extra field from another branch would be directly rejected by the union.

Half-finished work does not affect other services

"A half-filled service" is the norm in the configuration UI. Validation failure (missing command, invalid name, unsupported transport) only causes that item to be skipped and its reason marked in the UI; it does not invalidate the entire config or other services. A single service failing to mount (command does not exist, remote unreachable) is likewise only recorded as a failure of that item, and never spills over into unloading the entire plugin.

Settings UI

The plugin registers a top-level settings.section page "MCP Services". The UI is rendered by the browser-side bundle (lib/client.js), reading and writing through a host-built loopback configuration bridge:

| Route (all POST) | Purpose |
| --- | --- |
| /api/dsh-wall-mcp-manager/describe | Redacted config view (including revision and schema) |
| /api/dsh-wall-mcp-manager/mutate | Precisely modify fields via { ops, expectedRevision } |
| /api/dsh-wall-mcp-manager/servers | Mount status of each service (fiber phase / skip reason / failure reason) |
| /api/dsh-wall-mcp-manager/tools | Tool list grouped by service, including parameters and unwrapped return schema |

- Only accepts local loopback origins (127.0.0.1 / ::1 / ::ffff:127.*). This bridge can write MCP startup commands and credentials, and its privilege is equivalent to "executing arbitrary processes on the host"; DSH's webserver allows binding to 0.0.0.0, and in such deployments, not performing origin validation is equivalent to exposing a remote command execution interface to the entire network segment.
- Credentials never go out over the network: describe uses redactSecrets: true, marking the entire dictionary as secret so that not even key names are returned; the UI can only show "configured / not configured". For credential boxes, saving while left blank means no modification; this is the only semantics that supports modification without returning the original text; to clear, click "Clear Credentials".
- Deletion requires secondary confirmation: clicking "Delete" only sets a pending-confirmation flag, and the UI expands a confirmation bar in place, explaining the connections that will be disconnected, the number of tools to be removed, and that credentials will be cleared along with it and cannot be recovered; only after clicking "Confirm Delete" is unset actually written. Credentials are never echoed back, and an accidental deletion means you can only go get the key again; such irreversible operations must not be completed with one click.
- Writes use per-field set / unset (not whole replacement), with revision optimistic locking. Whole replacement would also erase secretEnv / secretHeaders that the browser has never seen; optimistic locking ensures that manual yaml edits and concurrent UI modifications do not overwrite each other. Validation failures and revision conflicts return ok:false, and the UI keeps the draft for correction.
- /servers and /tools read runtime facts and do not depend on the settings service; they can also be used for read-only display outside headless. When no data source hook is provided, these two routes are simply not registered (better a 404 than fake data).
- Tool cards display invocation parameters and return contracts. mcp-client wraps each tool's return schema in a fixed wrapper { content, structuredContent }, and the UI unwraps it: tools that declare outputSchema display their real structured fields, while tools that only return text blocks are explicitly explained. Forms that cannot be expanded field by field (union / $ref) all fall back to "View Raw Schema", without guessing or fabricating fields.
- The "MCP tools from other sources" section lists tools with the mcp__ prefix that do not belong to this plugin's configuration (for example, mcp-client instances mounted directly in the deployment config); their contracts can only be viewed, and their configuration cannot be modified here.

Limitation: the UI depends on a local browser accessing DSH's web service (loopback validation). When accessing the GUI via a remote address, configuration cannot be read or written; use $DSH_HOME/settings.yaml instead.

DSH's official "Plugin Configuration" page only renders namespaces within the WEB_SETTINGS_NAMESPACES whitelist in dsh-host-apiproxy; that whitelist is a hardcoded constant inside the package, and plugins outside the repository cannot hook into it; this plugin therefore uses a self-built route bridge and performs the same Schema validation and hot application in the settings page.

Hot application

After saving the configuration, reconciliation happens immediately, with no need to restart DSH: additions connect immediately, modifications reconnect that service, and deletions disconnect. Consecutive saves are serialized to avoid create / remove interleaving.

Note that the timing of configuration and runtime differs: configuration is fixed as soon as it is saved, while MCP connections and tool registration must wait for the child process or remote handshake to complete. After saving, the UI briefly polls the runtime status (about 8 seconds), advancing the message from "Connecting…" to "Connected, N tools registered" or the specific failure reason, with no need to manually refresh.

After the message reaches a terminal state, it disappears automatically after 10 seconds, with timing starting from the moment the connection result is obtained, not from the moment Save is clicked—the handshake may take several seconds, and timing from the click would make the message disappear before the result appears. In-progress messages are neither timed nor closable; otherwise the conclusion of this connection would never be visible again. Multiple messages are merged and queued by "service / operation" and coexist: polling text for the same service is rewritten in place (it will not pile up a dozen messages in 8 seconds), and messages from different sources each occupy their own line.

Status badges are divided into two levels, because fiber's "running" does not mean MCP is connected: mcp-client is in
failOnStartupError: false(本插件默认)下,握手失败也不会让实例失败,而是在后台按
指数退避重连。所以徽章只在该服务注册出工具时才显示「已连接」,实例活着但握手
未成时显示「未连接」。此时反复点「刷新状态」会一直得到同样的结果——那不是刷新失效,
而是连接确实没建立,失败原因见 DSH 日志。

配置分三层解析(后者覆盖前者):Schema 默认值 → cordis 组合层(cordis.patch.yml)→
settings.yaml 用户层。组合层适合部署级默认值,用户层适合随时可能变动的服务与凭据。

安装

dsh plugin --profile  add @sduwall/dsh-wall-mcp-manager

本地源码目录安装亦可:dsh plugin --profile  add ./dsh-wall-mcp-manager。

出厂默认 servers 为空,此时插件正常加载但不挂载任何 MCP,因此安装本身不会启动任何
子进程或对外请求。

部署级默认值请写在 profile 自己的 cordis.patch.yml
($DSH_HOME/profiles//cordis.patch.yml),而不是本包内的
cordis.patch.yml(后者属于 npm 包,升级会被覆盖):

- id: dsh-wall-mcp-manager
config:
servers:
filesystem:
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-filesystem', 'D:/workspace']

注意:patch 会整体替换被命中行的 config,覆盖时要把需要的键全部写出。

运行时挂载 mcp-client 时引用的包名是一个字符串,静态配置检查(verify-cordis-config)
看不见它。插件因此导出 MCP_PACKAGE 常量,便于组合方把它声明为依赖。

开发与测试

npm test
等价于
node tests/inventory.test.mjs && node tests/mcp-bridge.test.mjs \
&& node tests/client-bundle.test.mjs

测试直接运行文件(避免 node --test 在受限沙箱下的子进程限制)。
本地需让 @deepseek-ai/cordis、@deepseek-ai/schemastery、@deepseek-ai/dsh-settings
可解析(可将 node_modules/@deepseek-ai 目录联结到 DSH 安装自带的运行时)。

测试的意图:

- 纯函数测试(inventory.test.mjs):配置校验、mcp-client 配置翻译(联合分支多带
字段会被拒绝)、挂载计划与指纹(算错会让每次保存都重连 MCP)、返回 schema 拆包、
工具前缀归组(错了会把工具挂到别的服务名下)。
- 配置桥测试(mcp-bridge.test.mjs):凭据原文与键名都不出网、逐字段写入不抹掉
界面从未见过的凭据、回环来源限制、多级 path、修订冲突拒绝、只读存储与 Schema 约束
在桥上同样生效、settings 缺失时只读路由照常工作。用真实 settings 实现 + 内存路由表
驱动,不绑定端口(被测对象是路由逻辑,listen 只会引入沙箱网络权限依赖)。
- 客户端 bundle 测试(client-bundle.test.mjs):在 VM 沙箱里执行浏览器 bundle,
验证 __ModuleLoader__ 握手 id、inject(缺了 slots,bundle 会在真实运行时 FAILED)、
槽位注册,以及 draftToOps —— 那是界面改动翻成 mutate 操作的唯一出口,一旦它在
凭据框留空时多写一个 secretEnv,宿主侧再怎么脱敏也救不回来。

目录结构

dsh-wall-mcp-manager/
├── package.json          # 插件包声明(dsh.bundle.patch → cordis.patch.yml,dsh.client → 浏览器 bundle)
├── cordis.patch.yml      # Bundle 配置入口(插件条目与默认配置,出厂 servers 为空)
├── lib/
│   └── client.js         # 浏览器端 bundle:设置界面「MCP 服务」页面(settings.section)
├── src/
│   ├── index.js          # Cordis 插件入口:Config Schema + 服务清单对账(挂载/更新/卸载)
│   ├── inventory.js      # 纯函数层:配置校验与翻译、挂载计划与指纹、返回 schema 拆包、工具归组
│   └── mcp-bridge.js     # 回环配置桥路由:describe / mutate / servers / tools
└── tests/                # 纯函数测试 + 配置桥测试(真实 settings)+ 客户端 bundle 契约测试

发布

本包以 scoped 包 @sduwall/dsh-wall-mcp-manager 发布到 npm 公共 registry。

npm config set registry https://registry.npmjs.org   # 镜像 registry 不能发布
npm login                                            # 需具备 @sduwall scope 的发布权限
npm pack --dry-run                                   # 核对 tarball 文件清单
npm publish                                          # publishConfig.access=public,scoped 包才不会被当作私有包

prepublishOnly 会在发布前自动跑一遍 npm test,测试不通过则中止发布。

注意包内不带 scope 的三处标识是有意为之,改动会破坏兼容:settings 命名空间
dsh-wall-mcp-manager(只允许小写 kebab-case)、cordis.patch.yml 的 id、
配置桥路由前缀 /api/dsh-wall-mcp-manager。而
cordis.patch.yml 的 name 与 lib/client.js 的 __ModuleLoader__.load({ id })
必须与包名逐字符一致(含 scope),否则 patch 被静默跳过、bundle 注册失败。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群