Skip to content

dsh-mcp-ctl

Verified

dsh-mcp-ctl ยท v0.1.3 ยท MIT ยท Web UI

Claude Code style /mcp slash command for DeepSeek Harness: list MCP servers with live status and toggle them on/off.

Install

dsh plugin add dsh-mcp-ctl

Confirm the layer applied with dsh --profile default --dump-config โ€” see the install guide.

Source

Tags

Creators

Readme

dsh-mcp-ctl

Claude Code ้ฃŽๆ ผ็š„ /mcp ๆŒ‡ไปค for DeepSeek Harness (DSH): list every MCP server mounted in the harness with its live status, and toggle each one on/off โ€” from the chat.

What it does

In DSH, each MCP server is a composition row running @deepseek-ai/dsh-mcp-client (one plugin instance per server, e.g. mcp-github, mcp-gitee). This plugin:

  • registers the human slash command /mcp (command plane โ€” results never enter model history);
  • auto-discovers every @deepseek-ai/dsh-mcp-client row in the live loader tree (including disabled ones), reading its enablement, loader fiber phase (pending / loading / active / failed / unloading) and the number of tools registered as mcp__<serverName>__*;
  • renders an interactive card in the conversation (via the conversation.chat.commandview slot, key mcp) with per-server toggle buttons, status dots and tool counts โ€” a warning flag when a server is enabled but exposes zero tools (likely a failed connection);
  • toggles live and durably: the loader entry is restarted/disposed immediately (entry.update), and the row's disabled: flag is written back into the profile's cordis.patch.yml so the choice survives restarts (HMR applies the file change; the in-memory state already matches, so the reload is a no-op).

Usage

/mcp                          # open the interactive server card
/mcp github                   # open the card with that server highlighted
/mcp on github                # enable  (text shortcut, no card needed)
/mcp off gitee                # disable (text shortcut, no card needed)
/mcp toggle chrome-devtools   # flip

The card's buttons are equivalent to the text shortcuts โ€” each toggle logs a compact confirmation row in the chat, and every open card converges on the fresh state.

Install

้ฆ–้€‰:ไปŽ npm ๅฎ‰่ฃ…

Requires a DSH 0.1.5-rc.x web profile (pnpm workspace) โ€” see Compatibility for the exact contracts each half uses. In the profile directory:

cd "$DSH_HOME/profiles/web"                 # e.g. ~/.dsh/profiles/web
dsh plugin --profile web add dsh-mcp-ctl    # installs AND mounts

The package declares dsh.bundle.patch, so dsh plugin add reconciles it into dsh.profile.bundles and the profile boot merges this package's own cordis.patch.yml. One command installs and mounts โ€” there is no row to add by hand any more.

ไธญๅ›ฝๅคง้™†้•œๅƒๆณจๆ„:่‹ฅ npmmirror ๅฐšๆœชๅŒๆญฅๆ–ฐ็‰ˆๆœฌ,ๆ˜พๅผๆŒ‡ๅฎšๅฎ˜ๆ–นๆบ --registry=https://registry.npmjs.org/ใ€‚

Legacy / manual channel. Only needed when the package sits in node_modules but is not listed in dsh.profile.bundles โ€” that is, after a plain pnpm add without the CLI, which is also the shape dsh-market mounts as a client-only shim. Add one row to the profile's cordis.patch.yml โ€” as an insert (a bare top-level - id: entry would be treated as an override of an existing entry and silently skipped):

- insert:
    - id: mcp-ctl
      name: 'dsh-mcp-ctl'

Both channels insert the same row id mcp-ctl. If the profile patch still carries the manual row, delete it before switching to the bundle channel, or the host half mounts twice (two /mcp registrations, two client cards).

The loader's HMR mounts the host half immediately; the /mcp command works right away. The Web client card appears after one browser refresh (the client bundle is served by the profile's client-modules route once the entry is live). No process restart is required.

ๅค‡้€‰:ไปŽๆบ็ ๅฎ‰่ฃ… (git)

For unreleased code or development:

cd "$DSH_HOME/profiles/web"
dsh plugin --profile web add git+https://github.com/elephanttalkheads/dsh-mcp-ctl.git

The bundle layer travels with the checkout, so the same one command installs and mounts. Note the harness process usually runs with the workspace directory as its cwd; a package with this repo's name/exports can then resolve to the checkout itself (Node self-reference) instead of the profile copy โ€” keep the checkout in sync and restart after updating.

Config

Field Default Description
patchFile <profile>/cordis.patch.yml Which composition patch file the toggle writes to. Defaults to cordis.patch.yml in the profile directory the plugin was mounted from (ctx.baseUrl, a file:// URL), falling back to $DSH_HOME/profiles/web/cordis.patch.yml. Set it when your servers live in another file.

Example:

- id: mcp-ctl
  name: 'dsh-mcp-ctl'
  config:
    patchFile: 'C:/Users/me/.dsh/profiles/web/cordis.patch.yml'

Re-enabling a row another manager disabled

A plugin manager (for example dshmarket) switches a row off by appending a top-level override to the patch file:

- id: mcp-ctl
  disabled: true

A file-order patch layer gives that override the last word, so /mcp (which cannot run while its own row is disabled) will not start by itself. Flip it by hand once โ€” /mcp then keeps the row consistent from then on, because it edits the row's effective (last) mention and adds the same kind of override for rows a bundle layer defines.

Updating

cd "$DSH_HOME/profiles/web"
pnpm update dsh-mcp-ctl            # or: pnpm add dsh-mcp-ctl@latest
# restart `dsh web` โ€” loader HMR ignores node_modules, so a running process
# keeps the module it imported at startup

Publishing

Releases go through npm Trusted Publishing: the GitHub Actions workflow exchanges an OIDC token for a short-lived publish credential, so no long-lived npm token is stored in this repository or in CI secrets. (npm also now refuses 2FA-bypassing tokens for direct publishing, so a stored token is not an option.)

One-time setup on npmjs.com โ€” package โ†’ Settings โ†’ Trusted publishing โ†’ GitHub Actions:

Field Value
Organization or user elephanttalkheads
Repository dsh-mcp-ctl
Workflow filename publish.yml
Environment name (leave empty)
Allowed actions npm publish

Release:

npm version patch      # or minor / major โ€” semver: fixes=patch, features=minor
git push --follow-tags # the v<version> tag runs .github/workflows/publish.yml

The workflow checks the tag against package.json, runs both test suites and publishes with provenance. To publish the version already on the default branch (or to retry a failed run), start the workflow manually from the Actions tab โ€” the workflow_dispatch trigger skips the tag check.

How it works under the hood

Piece Mechanism
command ctx.commands.register (@deepseek-ai/dsh-commands), with an input.hint for the composer
discovery ctx.loader.entries() filtered by name === '@deepseek-ai/dsh-mcp-client'
status entry.disabled + entry.fiber.state (same mapping as dsh-host-plugin-inventory)
tool count ctx.tools.schemas() prefix-matched by mcp__<serverName>__
live toggle entry.update({ disabled }) โ€” the loader's own mutation API
persistence surgical edit of the row's effective disabled: value in cordis.patch.yml โ€” the last block mentioning the row id, or a newly appended top-level - id: override when no layer in that file defines it (comments, !!js expressions and line endings elsewhere are byte-preserved)
card UI conversation.chat.commandview keyed slot + the useChat session hook + ctx.remote.commands.execute

State travels as JSON inside the command result text; the card always renders the newest mcp command node found in the live chat snapshot.

Compatibility

The package is tested against the contracts shipped in DSH 0.1.5-rc.x (harness packages 0.1.5-rc.2). The version-sensitive points, all covered by npm test:

Half Contract Notes
Host ctx.commands.register({ name, description, input, handler }); the handler receives { commandId, agent, rawInput, attachments, signal } and returns { kind, text } input.hint was added to the descriptor in 0.1.5; the registry logs the command/runโ†’command/done pair itself
Host loader Entry.disabled, Entry.fiber.state, Entry.update({ disabled }) fiber state โ†’ phase mapping matches dsh-host-plugin-inventory; Entry.update changes live state only, which is why the patch file is the durable channel
Host ctx.baseUrl is a file:// directory URL (dsh-app-boot anchors it with pathToFileURL) resolved with fileURLToPath; a plain directory path is still accepted
Host patch layers apply in file order (include's applyEntryPatches), so the last mention of a row id owns its fields, and a top-level - id: X overrides a row an earlier layer inserted this is what makes a manager-written disabled: true override win
Client conversation.chat.commandview is a keyed, session-scoped slot whose owner props are { node: CommandNode, compaction? } the key is the command name (mcp)
Client the live conversation nodes come from the useChat session standard prop (contributed by the Chat view); SessionSnapshot.chat no longer exists the card falls back to useSession(...).chat.legacy.nodes on older releases and to its own node when neither hook is present
Client ctx.remote.commands.execute(agentId, line, submittedAttachments, signal?) returns { ok, value } / { ok, error } the same Remote the composer dispatches through
Client dsh.client.inject is an informational package-edge list (not Cordis service injection); the bundle itself only requires the platform seed word react it now names dsh-client-ui-chat, dsh-client-ui-slots, dsh-api-remotes

Known limitation: if the home-level layer ($DSH_HOME/cordis.patch.yml) pins the same row, it is applied after the profile layer and outranks this plugin's edit; remove the entry there, or point patchFile at that file.

Development

pnpm check   # syntax-check host and client bundles
pnpm test    # patch semantics + client behaviour (incl. the real profile patch, read-only)

test/patch.test.mjs pins the patch-file editing rules (including the real $DSH_HOME/profiles/web/cordis.patch.yml, read-only, when present); test/client.test.mjs materializes the client bundle against a stub React and stub slot runtime and drives both the modern useChat path and the legacy useSession fallback.

The client half is a hand-written window.__ModuleLoader__.load bundle (plain JS, no build step), the format the web client runtime consumes.

License

MIT