VCPr0j3k7/dsh-mcp-manager

dsh-mcp-manager

Graphical MCP server manager for the DeepSeek Harness desktop shell — add, remove, toggle, test, and scan Model Context Protocol servers

Uncategorizedcordisdeepseek-harnessdsh

dsh-mcp-manager

English | 简体中文

A graphical management UI for MCP (Model Context Protocol) servers in DeepSeek Harness (DSH): list, add, remove, enable/disable, test connectivity, and import servers discovered from MCP configurations already present on the machine.

It installs as a standalone bundle into a DSH profile. No official source files are modified.


Contents

Background

The official base bundle already ships the MCP client @deepseek-ai/dsh-mcp-client, but it has no management UI. Every MCP server has to be written by hand into the user layer of the profile's 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"]

The problem is that this file is also boot input:

  • Its top level must be a YAML array, and every element must be a patch;
  • It is parsed by dsh-app-boot using js-yaml plus a custom schema, and a parse failure is fatal — the application simply does not start, and there is no repair entry point in the UI;
  • The same file is also the user's own patch layer, so hand-editing it easily breaks unrelated content.

Hence the need for a validated graphical editing entry point: treat the configuration as structured data, validate before writing, and touch only the section this plugin owns.

Requirements

ItemRequirement
Node.js^22.19.0 || >=24
DeepSeek HarnessOfficial desktop build (Windows). This plugin relies on the process.argv layout of dsh-desktop-host and on the bundled Node / pnpm
Official base bundleMust include @deepseek-ai/dsh-mcp-client (the official base bundle already does)
Optional@modelcontextprotocol/sdk present in the runtime node_modules; the "test connection" feature needs it

Installation

Installation has two steps. The second step is not optional — running pnpm add alone does not add the plugin to the bundle layer stack.

Step 1: install the package into the profile

# 1. Enter the target profile directory (the official desktop build uses the desktop profile)
cd ~/.dsh/profiles/desktop

# 2. Install with the pnpm bundled with the official desktop build.
#    It lives at <install dir>/resources/runtime/pnpm/bin/pnpm.cjs and can be run
#    with the bundled node: node "<that path>" add ...
pnpm add "file:<absolute path to this plugin>"

It can also be installed straight from GitHub:

pnpm add "github:VCPr0j3k7/dsh-mcp-manager"

Step 2: register it in dsh.profile.bundles

Edit the package.json inside the profile directory and add the package name to the dsh.profile.bundles array:

{
  "name": "dsh-profile-desktop",
  "private": true,
  "dependencies": {
    "dsh-mcp-manager": "file:<absolute path to this plugin>"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-mcp-manager"
      ]
    }
  }
}

dsh.profile.bundles is the profile's bundle layer stack: DSH only assembles the plugin tree from the bundles listed here. When the package is installed but not registered, the plugin is not loaded and nothing is reported anywhere — the only symptom is that "MCP Connector" does not appear in the sidebar. This is the most common installation problem for this plugin.

Step 3: restart

Assembly happens at startup. After changing package.json or cordis.patch.yml the DSH desktop build must be restarted; a running instance does not hot-reload. After the restart the sidebar should show the "MCP Connector" entry.

Uninstallation

cd ~/.dsh/profiles/desktop
pnpm remove dsh-mcp-manager

Then remove the dsh-mcp-manager entry from dsh.profile.bundles and restart DSH.

Uninstalling does not delete the MCP server entries already written into cordis.patch.yml — they live inside the marked block and continue to be used by the official @deepseek-ai/dsh-mcp-client. To remove them as well, delete that block by hand (including both marker lines).

How It Works

Two halves

HalfRuns inLoaded via
Host half: index.js + host/Node.js (official host process)Cordis plugin exporting name / inject / apply(ctx)
Client half: client.jsBrowser (SPA inside the desktop shell)window.__ModuleLoader__.load({ id, factory })

The host half registers exactly one kind: 'prefix' route on ctx.webServer at /dsh-mcp-manager/api; dispatching inside that prefix is done by host/router.mjs. This keeps the registration point in a single place, and the framework cleans it up on unload.

Requests and responses

Every endpoint returns the same envelope:

{ "ok": true,  "data": { /* ... */ } }
{ "ok": false, "error": "human-readable message" }

Business failures also return HTTP 200; status codes only express transport-level facts (an unknown route returns 404). The request body limit is 8 MB.

Base URL probing

The page is served by the official shell, so its origin may be the host's HTTP address, the shell's custom scheme, or an opaque origin. The client therefore tries the candidate base URLs in order on the first request (relative path → http://dsh.internal) and decides by whether /info reports data.plugin equal to this plugin's id — a 200 alone is not enough, because the SPA fallback route also answers unknown paths with 200 + HTML. Once chosen, the base URL is reused for subsequent requests.

Event channel

Host events (such as MCP configuration update logs) are fetched through GET /events?since=<seq>, polled by the client every 300 ms. SSE is not used because it cannot be confirmed whether the shell buffers response bodies when forwarding a custom scheme; polling has no such unknown.

Writing configuration

The actual MCP connections are made by the official @deepseek-ai/dsh-mcp-client; this plugin only writes entries into the user layer of the profile patch. The profile's patchReload is startup, so every write operation returns needsRestart: true and the UI shows a restart notice.

Where the YAML library comes from

Validation before writing must use the same library the official boot path uses, otherwise the plugin could accept text that boot rejects. host/yaml-libs.mjs therefore looks up js-yaml by absolute path (requiring the directory entry, ignoring exports) in this order:

  1. <runtimeDir>/node_modules (the runtime directory)
  2. node_modules under the runtime directory given in process.argv[2]
  3. node_modules under the profile directory
  4. <DSH_HOME>/profiles/node_modules

The runtime copy takes precedence; the copy in the profile (this package's declared js-yaml dependency) is the fallback.

User Interface

The sidebar entry "MCP Connector" opens a page with three tabs:

TabContents
ConnectedCards for the servers in the current profile: transport, command or URL, entry id, connection status, tool count, raw configuration. Supports test, enable/disable and delete
DiscoverGrouped servers that can be adopted in one click: other AI client configurations, other DSH homes, and a built-in catalog. "Use" carries the prefilled parameters into the add form
AddPaste import (an mcpServers JSON snippet, or a single npx / uvx command line) and a manual form (stdio / streamable-http)

Connection status comes from the host rather than from local guesswork: a server shows as "connected" only when tools named mcp__<serverName>__* are actually present in the host's tool registry. A configuration that has been written but not yet loaded shows as "pending restart".

The Add tab supports testing before writing, which avoids the "found out it cannot connect after a restart" round trip.

Host API

All routes live under /dsh-mcp-manager/api.

Common routes (provided by host/router.mjs):

RouteDescription
GET /infoPlugin and host information; data.plugin is used by the client to validate the base URL
GET /configRead the shared configuration
POST /configDeep-merge write of the shared configuration
POST /open-externalOpen a URL with the system default handler (http / https only)
POST /pick-directoryOpen the system directory picker
GET /restart-pendingWhether profile key files are newer than the host-ready timestamp
POST /restart-hostRequest a host restart. A plugin cannot restart the host process, so this route always returns an error and the UI asks for a manual restart
GET /events?since=<seq>Fetch host events (polled by the client every 300 ms)

Plugin routes (registered by index.js):

RouteDescription
GET /mcp/listList the servers in the current profile with their real host-side status
POST /mcp/addAdd a server; overwriting a same-named entry requires overwrite: true
POST /mcp/removeRemove a server by entry id
POST /mcp/toggleEnable / disable (writes the disabled field; the configuration is kept)
GET /mcp/scanScan the machine for servers that can be adopted
POST /mcp/testPerform a real MCP handshake and return the tool list
POST /mcp/parseParse a pasted JSON snippet or a single command line

How Configuration Is Written

This plugin only modifies the block in cordis.patch.yml delimited by paired marker comments:

# >>> dsh-desktop:mcp —— 以下内容由桌面版维护,请勿手工编辑
- insert:
    - id: mcp-filesystem
      name: "@deepseek-ai/dsh-mcp-client"
      config: { /* ... */ }
# <<< dsh-desktop:mcp

A few hard rules:

  • The block content is a complete patch list item (- insert: [...]), not a fragment. The top level of cordis.patch.yml must be a YAML array; flattening insert: into the file makes it unparseable.
  • Validate before writing: the full candidate text is parsed with the same js-yaml the official boot path uses, and is only written if that succeeds.
  • Atomic write: a temporary file is written and then renamed, so on disk there is either the old version or the new one — never a half-written file.
  • Idempotent: writing the same configuration twice produces byte-identical output, so the startup sync does not rewrite the profile over and over.
  • Nothing outside the block is modified: MCP entries written by hand elsewhere in the file are listed in the UI as "manual entries" and are read-only — this plugin never modifies or deletes them.

Built-in Server Catalog

The Discover tab ships a built-in catalog. It only includes servers that are official or widely used and that start with a single npx command:

NameDescription
FilesystemLets the model read and write files under a given directory (a directory must be specified, otherwise the server refuses to start)
MemoryA knowledge-graph based memory server: write facts in, retrieve them across sessions
Sequential ThinkingBreaks a complex problem into revisitable reasoning steps; useful for multi-step tasks
EverythingOfficial demo server: provides a set of example tools to verify that the MCP path works
Playwright browserLets the model actually open a browser: click pages, fill forms, take screenshots, extract content
Context7 docsFetches up-to-date official documentation and code samples by library name, reducing invented APIs

Starting these servers for the first time requires network access so that npx can fetch the package. Entries that need parameters come with the form fields prefilled; nothing is written until the user confirms.

Activation Criteria

HalfCriterion
Host halfDeclares inject = ['webServer'], so the host composition must provide webServer (the official base bundle does)
Client halfDeclares dsh.client.inject = ['@deepseek-ai/dsh-client-ui-slots'] and dsh.client.platform = 'web'
BothThe package name must be registered in the profile package.json's dsh.profile.bundles array

Reading and writing MCP configuration additionally requires both the runtime directory and the profile directory (from process.argv[2] and process.argv[3]). When either is missing, GET /mcp/list returns a readable error message instead of throwing ENOENT.

Testing

npm test

test/check.mjs is a pure Node self-check (37 assertions) that does not depend on the DSH runtime. It covers:

  • Whether the declarations in package.json and the entry files they point to actually exist (dsh.bundle.patch, exports['./client'], dsh.client.platform);
  • The host half's export contract (name / inject / apply) and whether apply() registers the prefix route;
  • Route alignment: every route the client calls is extracted from client.js and checked against the host route table;
  • data.plugin from /info, a 404 for unknown routes, and that a cleanup function is registered through ctx.effect;
  • That the shared configuration defaults contain a githubToken field.

The self-check points DSH_HOME and the profile directory at a temporary directory created for that run, so it never reads or writes the real ~/.dsh.

End-to-end verification: after restarting the desktop build, "MCP Connector" should appear in the sidebar. Adopt a server on the Discover tab and restart — the Connected tab should then show "connected · N tools".

Known Limitations

  • Changes only take effect after a host restart. The profile's patchReload is startup; this is the official composition's load ordering, not a choice made by this plugin. The UI shows a restart notice.
  • A plugin cannot restart the host process. POST /restart-host always returns an error (terminating the host process would also lose the user's session), so the "Restart now" button in the UI receives an explicit error and a manual exit and reopen of DSH is required.
  • Hand-written entries are read-only. MCP entries outside the marked block are listed in the UI but cannot be modified or deleted through this plugin, so that the user's own content is never overwritten.
  • serverName is constrained. Only [A-Za-z0-9_-]{1,32} is accepted, matching the validation in the official dsh-mcp-client. Imports from other clients substitute and truncate characters accordingly.
  • "Test connection" depends on the MCP SDK in the runtime. It needs @modelcontextprotocol/sdk/dist/esm/client under the runtime node_modules; when it is missing that one feature returns an explicit error and nothing else is affected.
  • The built-in catalog needs network access. The server packages are fetched by npx on first use, so those servers cannot be started offline.
  • Scanning only covers the default configuration paths. Configuration files belonging to Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, Cline, Roo Code or DSH homes outside the well-known locations are not discovered.
  • Writing requires a writable profile directory. When the directory is read-only or the runtime directory is missing, the read/write endpoints return readable error messages.

License

MIT