dsh-mcp-mini-manager
MCP manager for DeepSeek Harness Web: a Settings page beside Plugins that lists the MCP servers the running loader tree actually holds, each with its config summary, endpoint and live state, and disables or restores any of them through a paired-marker block in the profile's cordis.patch.yml. Env val
Install
dsh plugin --profile web add dsh-mcp-mini-manager
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-mcp-mini-manager
[中文](README.md) | English
MCP Mini Manager for DSH: one Settings page, at the same sidebar level as Plugins, that lists every MCP server this DSH actually loads — with each server's summary and live state — and takes any of them out of service.
- Plugin id:
dsh-mcp-mini-manager - Chinese name: MCP 极简管理
- English name: MCP Mini Manager
- Settings page id:
mcp-mini-manager(settings.section, order 16, right after Plugins)
What it fixes
DSH has no single place that shows which MCP servers are installed. Finding out means reading cordis.patch.yml, then the patch layers of every bundle, and deciding by hand which rows are MCP; removing one has no UI entry point at all. This page makes both a one-step operation.
How it can know which MCP servers exist
This is the one design point worth explaining.
In DSH an MCP server is exactly one row in the cordis loader tree, whose module is @deepseek-ai/dsh-mcp-client. That package's own type docs say so:
MCP client bridge plugin: connects to an external MCP server and registers its tools onctx.tools… Each plugin instance connects to one MCP server; load multiple instances incordis.ymlfor multiple servers.
So "which MCP servers are installed" is "which ctx.loader entries are dsh-mcp-client". This plugin reads two sources and reconciles them:
Source · Provides · Limitation
The live Loader (ctx.loader.entries()) · Authoritative config (serverName / transport / command / url …) and the real fiber state · Blind to disabled rows — a disabled row is not in the tree at all
The patch layers (each bundle's cordis.patch.yml, the profile's own, and ~/.dsh/cordis.patch.yml) · Disabled rows, and which package declared each one · Static config only, no runtime state
Merged, a server you removed yesterday still appears — with a "removed" state and its original config recovered from the removal record. The merge rule is: the live tree wins on anything it can see, and the layers supply exactly what the tree structurally cannot.
The page holds one boundary strictly: the browser never sends a path, and never sends YAML. Every request carries a single row id that this plugin's own catalog reported; the host re-confirms that the row really is an MCP client row before deriving which file to write and which block to write into it.
How removal works
"Remove" appends an id-targeted disable patch to the profile's own cordis.patch.yml:
# >>> dsh-mcp-mini-manager: disabled mcp row mcp-github # server: github # removed: <ISO timestamp> by dsh-mcp-mini-manager # restore this server from the MCP 极简管理 settings page, or delete # these lines to re-enable the row by hand. - id: mcp-github disabled: true # <<< dsh-mcp-mini-manager: disabled mcp row mcp-github
That route has three deliberate properties:
1. No package file is ever touched. The target row may come from dsh-base, dsh-web-app or a third-party bundle's patch layer, and those directories belong to the packages — a reinstall would undo any edit. The disable goes into the user layer, the one dsh-app-boot applies on every boot and the one the user edits.
2. No restart needed. DSH calls watchUserPatches on the profile and home layers at startup, so a write is reparsed and reapplied in process. After writing, the host re-reads the live tree and reports honestly whether the change is "already in effect" or "takes effect after a restart", instead of promising on its own authority.
3. Recoverable. Before the patch is written, the original row is recorded under <harness home>/.mcp-mini-manager/removed/<rowId>.json. "Restore" does exactly one thing: delete that disable block. Disable-then-restore is a byte-for-byte no-op — your own comments, !!js expressions and other plugins' patches are untouched, because the write is a line splice, not a YAML re-serialization.
Why not parse and re-serialize the YAML. That file is hand-written and shared by every plugin. Any "parse → modify → re-emit" round trip reorders or rewrites comments and !!js expressions, quietly breaking unrelated plugins. So this plugin only inserts and deletes whole marked line ranges, and every range carries its own begin/end markers.
Refusals, all deliberate
Situation · Behavior
The row is already disabled · Refused (already-disabled); the file is untouched
Unpaired markers (hand-edited, or an interrupted write) · Refused (marker-unpaired) — the block's extent is unknown, so neither a splice nor a delete can be bounded honestly
The patch file does not end with a newline · Refused (no-trailing-newline) — restore would stop being exact
A path-shaped row id (a/b, ../x, C:\x) · Refused by grammar (bad-row-id) before any filesystem use
A request from another origin (Origin ≠ Host) · Refused with 403
When the user layer already holds a patch for this row, the disable is written anyway. This is worth stating, because the instinct is that a duplicate id must be refused: applyEntryPatches applies every patch independently, key by key, so a patch carrying only id + disabled composes safely alongside one carrying name + config — that is exactly how DSH itself layers patches. It also means the most common shape of all, an MCP row declared through insert, stays removable; otherwise most rows on the page would become un-actionable.
"Clear removal records" is this plugin's only irreversible verb. It deletes record files only — it restores and removes nothing — and requires a second confirmation.
What else the page shows
- Summary: one line per server — the command line for stdio, the endpoint for streamable-http. Fields whose config holds a
!!jsexpression are flagged by name (**names only;
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::5,最后更新 2026-10-16