MCP & Skill manager: enable/disable MCP servers and skills from a Settings panel to free context in real time; optional AI middle layer (mcp_search/mcp_call) calls disabled servers on demand, with state-based visibility filtering that keeps user-enabled servers visible.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:lilyblessing/dsh-mcp-skill-panel
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
✨ What it is
A settings-page panel that turns your MCP servers and Skill catalog into an actionable list: one toggle per entry — disabling releases context usage instantly, enabling works without a restart. It also ships an optional AI middle layer (autoManage): disabled MCP servers stay hidden from the model and are called on demand, while servers you enabled stay visible — your toggles decide exactly what the model context pays for.

🎯 Core features
| Feature | Description |
|---|---|
| 🟢 Real-time MCP toggle | Disable → loader entry is disposed (connection closed + all mcp__<server>__* tools unregistered), tools disappear from the model catalog immediately and their schema tokens are freed; enable → reconnect + tools restored, no restart |
| 🧠 Skill toggle | Injects/removes disable-model-invocation: true in SKILL.md frontmatter; the model catalog updates in real time |
| 📊 Backfill while disabled | Disabled MCP cards still show "N tools / ~N tokens in catalog" (last-good snapshot from the private catalog), so you can decide whether re-enabling is worth the context cost |
| 🤖 AI middle layer (optional switch) | With autoManage on: disabled MCP servers are hidden from the model and used on demand via mcp_search (top-K catalog search with exact schemas) and mcp_call (keep-alive enable → in-plugin execute → idle 30s auto reaping); servers you enabled stay visible (e.g. memory for high-sensitivity recall, filesystem for direct IO); AI-temporarily-enabled servers never pollute context |
| 🔒 Your toggles are never overridden by the model | The reaper only reclaims servers that AI enabled from a disabled state; servers you manually enabled are never auto-disabled (toggle clears AI marks) |
| 💾 Survives restarts | MCP state is materialized into the preset composition file via the plugin state file (~/.dsh/dsh-mcp-skill-panel/state.json); catalog snapshots persist (catalog.json) and backfill after restart |
| ⚡ Fast | Toggles flip instantly (optimistic UI + server confirmation); domain caches with event-driven invalidation (tools/change / skills/change); the MCP tab never triggers skill discovery |
| 🌐 Bilingual UI | All copy zh/en, follows the DSH UI language; light/dark theme aware |
| 🪶 Zero context footprint | The plugin itself registers no model tools and consumes no injection surface (with the switch off it behaves like it isn't installed) |
🏗️ Two modes (the "AI Middle Layer" switch in the panel)
stateDiagram-v2
[*] --> Mode1Direct: autoManage off
[*] --> Mode2Middle: autoManage on
Mode1Direct --> Mode2Middle: panel switch / POST /config
Mode2Middle --> Mode1Direct: panel switch / POST /config
state Mode1Direct {
direction LR
M1: Model uses native tools of every enabled MCP directly (mcp__*)
M1a: Enable/disable only via the panel
}
state Mode2Middle {
direction LR
M2: Disabled MCP servers hidden from model
M2a: Model calls them on demand via mcp_search / mcp_call
M2b: Servers you enabled stay visible
M2c: AI-temporarily-enabled servers never pollute context
}
Assembly filtering in mode 2 (evaluated every turn):
flowchart TD
A[system-prompt/assemble] --> B{name starts with mcp__?}
B -- no --> K[keep: enters model context]
B -- yes --> C{parse server}
C -- fail --> K
C -- ok --> D{server state?}
D -- user-enabled disabled=false and not AI-enabled --> K
D -- user-disabled disabled=true --> F[filter out: hidden from model]
D -- AI-temporary mcp_call keep-alive --> F
F --> G[when needed: mcp_search / mcp_call on demand]
📦 Install
dsh plugin --profile web add "github:lilyblessing/dsh-mcp-skill-panel#main"
Prebuilt artifacts are committed (lib/), so the git-source one-liner installs without a build step. Restart dsh web after installing (bundles are composed at startup; hot reload does not apply), then open Settings → MCP & Skill Manager.
🚀 Usage
- Settings → MCP & Skill Manager
- MCP Servers tab: each card shows server name, status badge (Active / Disabled / No tools / Failed), a model-visibility badge (in middle-layer mode: user-enabled = visible, disabled / AI-temporary = hidden), tool count and estimated token usage; toggle with the button on the right
- Skills tab: each card shows name, source, description, model-visibility badge; toggle on the right
- AI Middle Layer switch: on → disabled MCP servers are used on demand by the model (see modes above); off → classic direct mode
- Manual management (optional): edit the preset composition file (
disabled: truerows) or SKILL.md frontmatter (disable-model-invocation: true) directly — takes effect on next restart/change
Badge meanings: 🟢 Active (has tools) / ⚪ Disabled / 🟡 No tools (process running but empty tool list — usually a failed server start or empty implementation) / 🔴 Failed (neither running nor disabled).
🔌 HTTP API
| Method | Path | Description |
|---|---|---|
| GET | /api/mcp-skill-panel/state?session=<id>&part=<mcp|skills|all> |
Catalog snapshot; part scopes the fetch (the UI lazy-loads per tab), defaults to all; without session, the first root agent is used |
| POST | /api/mcp-skill-panel/mcp/toggle |
{ entryId, disabled } |
| POST | /api/mcp-skill-panel/skill/toggle |
{ name, disabled } |
| GET | /api/mcp-skill-panel/config |
Read the AI middle layer switch state |
| POST | /api/mcp-skill-panel/config |
{ autoManage: boolean } toggle the AI middle layer (persisted to state.json) |
| GET | /api/mcp-skill-panel/debug |
Catalog collection diagnostics (event counters / snapshot telemetry / in-memory catalog summary) |
| POST | /api/mcp-skill-panel/debug/collect |
Trigger one catalog snapshot manually |
The legacy prefix
/api/runtime-inventory/*(≤0.3.1) is still registered for compatibility. Domain caches (60s TTL fallback) are invalidated precisely by events:tools/change/loader/partial-dispose→ MCP domain;skills/change→ Skill domain.
⚙️ How it works
flowchart LR
subgraph Host["Host (Node, cordis plugin)"]
R[webServer routes<br/>/api/mcp-skill-panel/*]
C[catalog collector<br/>tools/change incremental + last-good persistence]
L[loader toggle<br/>resolve + update disabled]
F[assembly filter<br/>system-prompt/assemble]
T[mcp_search / mcp_call<br/>keep-alive enable + idle reaping]
R --> L
C --> R
F --> C
T --> C
T --> L
end
subgraph Browser["Browser (client bundle)"]
P[Two-tab panel<br/>toggles + visibility badges + autoManage switch]
end
R <--fetch--> P
MCP toggling: each MCP row is a loader entry in the agent preset composition (agent.cordis.yml, @deepseek-ai/dsh-mcp-client, full id like include:agent-presets:mcp-cheatengine). loader.resolve(id).update({ disabled }) disposes/restarts the entry in real time.
Why persistence takes two steps: the preset tree's write() is an explicit no-op, and dsh-agent-presets detects preset-file changes via a {mtimeMs, size} stamp — writing that file at runtime triggers a standing remount without disposing old instances (serverName conflicts, session creation failures — a 0.1.0 incident). So toggles only write the plugin state file, and the intent is materialized into the preset file during apply (early startup, before the standing mount).
Middle-layer call chain (mcp_call against a disabled server):
sequenceDiagram
participant M as Model
participant P as Plugin (mcp_call)
participant L as loader
participant S as MCP server
M->>P: mcp_call(server, tool, args)
P->>L: entry.update({disabled:false}) (record AI owner)
L->>S: spawn / reconnect
P->>P: wait for registration (poll tools.get + tools/change)
P->>S: tools.execute (in-plugin execution)
S-->>P: result
P-->>M: text result
Note over P: refcount -1; idle 30s then reap (AI-enabled only)
Catalog collection: tools/change (root listener, 150ms debounce) incrementally snapshots enabled servers; when agents is unavailable in the apply context it falls back to agentPresets.standingKeyFor() to resolve the scope (v0.4.1 fix); empty snapshots never overwrite the on-disk last-good; catalog.json is written atomically (tmp + rename, 0600).
✅ Verification checklist
| Check | Action | Expected |
|---|---|---|
| Panel entry | Restart, open Settings | "MCP & Skill Manager" appears with two tabs; zh/en follows UI language |
| Disable MCP | Turn a server off | Card shows "Disabled"; new turns no longer include mcp__<server>__*; the card still shows catalog tool count |
| Enable MCP | Turn it back on | Tools restored, no restart |
| Persistence | Disable, restart dsh | Server stays disabled |
| Skill toggle | Flip a skill | Card flips instantly without bouncing; model catalog updated |
| External change | Session A disables an MCP, session B opens the panel | Fresh state without manual refresh |
| AI middle layer | Turn autoManage on in the panel | Disabled servers hidden from the model, mcp_search/mcp_call available; user-enabled servers show the "visible" badge |
| Reaper safety | Let a model-called server idle 30s | AI-temporarily-enabled server auto-disables; user-enabled servers are never reclaimed |
⚠️ Known limitations
- Toggles act at the preset layer: one server/skill switch affects all sessions under that preset.
- SKILL.md files without frontmatter cannot be toggled (the provider ignores them anyway).
- Tool counts/tokens are estimates (
JSON.stringify(parameters).length / 4), approximate to the real injection surface. - After disabling, tools disappear immediately, but the current turn's cached request (if any) may still reference old schemas; the next request refreshes naturally.
- Persistence lag: toggles take effect live; surviving a restart depends on materialization at next startup — if the plugin is hot-updated while sessions are running, this process does not materialize; the next restart applies it.
- Manually editing MCP rows in the preset file (e.g. removing
disabled: trueby hand) removes that row from the plugin's management (your edit is respected at next startup). - Writing SKILL.md at runtime is safe (the skill-filesystem watcher expects edits); writing the preset composition file at runtime triggers the stamp-remount incident, which the plugin deliberately never does.
- The capability summary (
mcp_searchwith no args) only covers servers that have a catalog snapshot or a configuredserverSummary; servers that never started successfully (e.g. codegraph) are not listed.
🛠️ Development
npm run setup # junction DSH closure types into node_modules/@deepseek-ai
npm run typecheck # tsc type check (closure types)
npm run build # tsc dts + tsdown (node external all @deepseek-ai/*)
npm run verify # artifact verification (no inlined TOOL_RUNTIME_SCHEDULER, client wrapper intact)
node scripts/selftest-mcp.mjs # catalog unit tests
The node-half tsdown build must use external: [/^@deepseek-ai\//]: inlining dsh-tools creates a second TOOL_RUNTIME_SCHEDULER Symbol and breaks tool dispatch (same lesson as dsh-context-doctor).
📋 Changelog
| Version | Content |
|---|---|
| 0.4.3 | Performance pass: restore race fixed (a user manually enabling a server mid-call is never disabled on failure); per-turn visibility Map cache in the assembly filter (O(1) lookups); 500ms schemas reuse window; 300ms catalog persist debounce; in-memory state.json with write merging; 80-char summary truncation; disabled-state token estimate cache; proactive TTL pruning of cache maps; snapshotServer dead code removed |
| 0.4.2 | Assembly filter keyed by server state: MCP tools of user-enabled servers enter the model context (memory high-sensitivity recall); disabled ones are hidden and called on demand via mcp_search/mcp_call; AI-temporary enables never pollute context; manual enable clears AI marks (reaper safety); panel autoManage switch + model-visibility badges |
| 0.4.1 | Catalog collection pipeline fixes: empty agents in apply ctx made auto collection always empty (fallback to standingKeyFor for scope), last-good guard failure, empty-snapshot disk overwrite at startup, persist race; debug diagnostic endpoints; case retests passed (chrome→mimo cross-server, calcmcp burst zero-respawn + 30s reaping) |
| 0.4.0 | AI middle layer (autoManage): mcp_search/mcp_call on-demand MCP usage (keep-alive + idle reaping + assembly filter); private catalog persistence + disabled-state backfill |
| 0.3.2 | API prefix aligned with package name (legacy prefix kept); local dir renamed |
| 0.3.1 | Versioned MCP aggregate reuse; frontend fetch out-of-order guard |
| 0.3.0 | Scoped endpoints + domain caches + event-driven invalidation (tab lazy loading) |
| 0.2.1 | Skill toggle 30s UI lag root cause fixed (confirmed values override stale catalog) |
| 0.2.0 | Renamed to "MCP & Skill Manager" + GitHub repo dsh-mcp-skill-panel |
| 0.1.1 | MCP persistence rework (state file + early-startup materialization), fixing session creation failures caused by writing the preset file at runtime |
| 0.1.0 | Initial: MCP/Skill listing + toggles |
📄 License
MIT © lilyblessing
Links
More in this category
zhu1090093659/dsh-web-ui#packages/dsh-web-ui-all★ 3362
Plugin and skin collection for the DSH Web UI: task board, Git graph, right-side panel, remote mobile UI, pet, live token stats, and a skin center.
omdsh-dev/DSH-better-sidebar★ 1600
Full sidebar workbench with file rendering and editing, terminal, Git, and subagents; third-party plugins can register new tabs.
ccch1mneyyy/dsh-TUI★ 1519
Claude Code-style full-screen terminal UI: pixel-whale header, live status line, and streaming thought expansion.
omdsh-dev/dsh-at-file★ 262
Codex-style `@file` mentions: search workspace files in the composer and attach their contents to prompts.
huiliyi37/dsh-tianshu-tui★ 190
A terminal UI (TUI) for DeepSeek Harness.
Nagi-ovo/dsh-visualize★ 159
In-conversation generative UI: the model renders interactive HTML cards into the chat stream, with streaming preview and sandboxed rendering.