DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

wldxiaobai /

wldxiaobai/dsh-project-mcp-manager

Verified

基于原版 DSH 扩展,提供项目层、用户层更完整 MCP 管理 || Extended from the original DSH code, providing more complete MCP management at both project and user levels

★ 9 Stars2 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@bf122209

dsh-project-mcp-manager

English | 中文

A project-level MCP auto-loading plugin for DSH: write MCP server configs in <projectRoot>/.dsh/mcp.yml or .dsh/mcp.json and they are mounted automatically (via the official @deepseek-ai/dsh-mcp-client) whenever a dsh session opens in that project. Changes to the file hot-reload into the running dsh process, and tool visibility follows the session's original project (session.header.cwd). The default dsh-project-mcp-manager bundle includes the loader and the companion dsh-project-mcp-ui settings page for the web and desktop apps. The UI is enabled by default and can be disabled independently; the loader keeps working.

Capability boundary (dsh ≥ 0.2.1-rc.2): the official client owns the protocol, reconnect, tool names, resources, and server instructions. Shipped profiles already mount shared MCP resource tools. Official configuration is a profile-layer Cordis patch (with that layer's own HMR) plus plugin_manager. This plugin still owns what the host does not:

  1. Project-level discovery of <projectRoot>/.dsh/mcp.yml, .dsh/mcp.json, and the read-only legacy .mcp.json.
  2. Tool visibility isolated by the session's original project (session.header.cwd). working_directory does not retarget it.
  3. The MCP file format, the dsh-mcp CLI, and the settings page.

Project-file hot reload is this plugin's file watcher. It does not replace official profile HMR. Transport types are decided by the official client. v0.9.x targets the dsh 0.2 line starting at 0.2.1-rc.2 (engines.dsh is ^0.2.1-rc.2). Hosts on dsh 0.2.0 stay on plugin v0.8.2. Hosts still on dsh 0.1.5 should stay on plugin v0.6.0.

If this plugin is useful, a GitHub star is appreciated. Bugs, host mismatches, or ideas belong in Issues — even a short report helps.

Documentation

Feature documentation lives in docs/, English and Chinese side by side:

  • Configuration format — native YAML managed block, JSON dialect, divergences from the cordis dialect.
  • Settings page — use the bundled UI, read the server list, add servers (with fill from clipboard), switch, remove, and manage tools.
  • Configuration sources and layers — the seven-layer source model, shadow priority, global vs project mounting, and the read-only legacy Claude Code layer.
  • ${VAR} expansion — mount-time interpolation and its diagnostics.
  • CLI dsh-mcp — scopes, write formats, ownership contract.
  • Query surface — ctx.projectMcp, the projectMcp/updated event, exported view types, and the semver contract for the companion UI.

Design and release records (Chinese): dsh 0.2.1-rc.2 adaptation · dsh 0.2.0-rc.2 adaptation · dsh 0.1.6-alpha.2 adaptation plan · dsh 0.1.5-rc.2 adaptation · dsh 0.1.5-rc.1 adaptation · dsh 0.1.2-rc.1 adaptation · JSON config layer proposal · Runtime robustness & JSON interop proposal · v0.9.0 release notes · v0.8.2 release notes · v0.7.2 release notes · v0.7.1 release notes · v0.7.0 release notes · v0.6.0 release notes · v0.4.3 release notes · v0.4.2 release notes · v0.4.1 release notes · v0.4.0 release notes · v0.3.1 release notes.

Code review records (Chinese): TypeScript changes since v0.3.1 · v0.4.3 to v0.6.0 · 7e0088d to 804662f (fix follow-up) · feat/adapt-dsh-0.2.0-rc.2 (v0.7.0).

Installation (mount into a profile)

The plugin is mounted through a bundle patch: once the package is added to dsh.profile.bundles, dsh synthesizes each bundle's patch (the cordis.patch.yml pointed to by dsh.bundle.patch) into plugin lines at startup, in order. The manager's default bundle contains two rows: mcp-project (core loader) and mcp-project-ui (settings page). The manager installs its same-exact-version UI dependency automatically; the UI package is not a standalone dsh.bundle and should not be selected separately.

The core-plus-UI bundle is included in source version 0.8.0. The npm examples below provide that layout once both 0.8.0 packages are published; a Git tag alone does not publish packages or change npm's latest tag.

Prerequisite: install dsh itself (for users who don't have dsh yet):

npm install -g @deepseek-ai/dsh        # official npm package
npm install -g deepseek-ai/dsh         # or install from the GitHub source

Option 1: the dsh plugin command (recommended) — dsh plugin forwards pnpm inside the profile directory and handles installing/upgrading dependencies:

# Install the latest version with the UI included (choose your profile)
dsh plugin --profile web add dsh-project-mcp-manager@latest
dsh plugin --profile desktop add dsh-project-mcp-manager@latest
# For other profiles, e.g. headless, substitute the profile name.

# Pin a release version (check available versions with
# npm view dsh-project-mcp-manager versions; bundled UI needs the coordinated release)
dsh plugin --profile web add dsh-project-mcp-manager@<version>

Option 2: install directly with pnpm (equivalent to option 1):

# dshHome defaults to %USERPROFILE%\.dsh (uses $DSH_HOME if set)
cd $env:USERPROFILE\.dsh\profiles\web
pnpm add dsh-project-mcp-manager@latest

Option 3: local development install — build the checkout, then link only its root manager package. The workspace dependency supplies the matching UI; no separate UI link or bundle selection is needed:

# Run in the source checkout
pnpm install
pnpm run build     # core first, then UI (host code + browser bundle)
dsh plugin --profile web add link:<path-to-your-dsh-mcp-project-source>
# e.g. link:D:\dev\dsh-mcp-project; use --profile desktop for the desktop app

The link points at the checkout; rebuild after source changes so the host sees updated compiled output.

dsh ≥ 0.1.2 note: whether the plugin loads depends on the profile's dsh.profile.bundles list, not just installed dependencies. The recommended dsh plugin ... add command handles bundle reconcile. If you installed with pnpm directly, run dsh plugin --profile web list once to trigger it (use desktop for that profile). In the web or desktop app's Plugins page, inspect the manager's two contained components. The desktop profile is managed by Electron; do not use a desktop CLI --dump-config command to inspect it. Select only dsh-project-mcp-manager, not its UI dependency.

Upgrading / pinning versions: re-run the add command from option 1 with the desired version suffix — @latest upgrades to the newest release, @<version> pins a specific release. For the bundled UI, choose the coordinated manager/UI release described above. v0.9.x needs dsh 0.2.1-rc.2 or a later 0.2 release (engines.dsh: ^0.2.1-rc.2). v0.7.x and v0.8.x need dsh 0.2.0-rc.2 (the 0.2.0 line). dsh 0.1.5 keeps working with plugin @0.6.0.

Settings page (included by default): the manager install above supplies the UI automatically in web and desktop profiles. Open Settings → Built-in plugins → Plugin: MCP Manager. To hide the page without stopping the loader, turn off only the manager bundle's mcp-project-ui component in the plugin manager's contained-components controls (or disable that row with a profile patch override). Keep mcp-project enabled.

A headless profile installs the same bundle but has no browser page. Without the dsh connection service, the UI registers no routes; its connection integration waits without blocking the core loader or MCP mounting.

Migrating from a separate UI install: after installing the manager bundle, remove dsh-project-mcp-ui from the profile's bundle selection (dsh.profile.bundles), keeping the manager selected. You may then remove the direct UI dependency with dsh plugin --profile web remove dsh-project-mcp-ui (use desktop for the desktop profile); the manager still supplies its own UI dependency. Before running the new bundle, remove an old direct UI dependency (recommended) or align it to the manager's exact version; it can take precedence over transitive resolution. If the old UI bundle was disabled, transfer that intent to a mcp-project-ui row override with disabled: true before loading the manager. Neither old bundle selection nor direct dependencies are promised to be removed automatically. Details: settings page.

Build & test

pnpm install
pnpm run build     # core: tsc → lib/; then UI: tsc + browser bundle → packages/ui/lib/
pnpm test          # node test/*.mjs (loader, CLI, and settings-page helpers)

How it works

  • Project discovery: the session.header.cwd of an active agent session, plus the dsh process start directory → walk up to the nearest ancestor containing .git as the project root (falls back to the directory itself when there is no .git).
  • Mounting: each (project, serverName) pair in the project layers mounts one @deepseek-ai/dsh-mcp-client instance (ctx.plugin) on the host ctx and registers it into the global tool layer; multiple sessions inside the same project share a single connection. Before a workspace is focused, project-layer fibers are created only for projects with a live session or the process cwd, and they unmount after a 5 minute grace. Once the user focuses a workspace, only that project stays mounted and the others unmount immediately, so a desktop workspace switch cannot leave another project's tools in the global list. The catalog entry and watcher remain. Every user-layer row mounts exactly one instance (global, independent of the number of projects) — see configuration sources and layers.
  • Hot reload: chokidar watches each known project root and its .dsh directory (depth 1). The allow-list is that root, .dsh, and the three config files; every other path is ignored, so the watcher does not open lib/, src/, or build/ and a Windows build can delete those directories while dsh is running. Edits to the exact config files of known project roots — <projectRoot>/.dsh/mcp.yml, <projectRoot>/.dsh/mcp.json and <projectRoot>/.mcp.json — trigger a full reconciliation after a 150 ms debounce: added rows are mounted, removed rows are unmounted, and config changes are remounted. A second watcher covers the user layer as exact file paths — ~/.dsh/mcp.yml, ~/.dsh/mcp.json, ~/.dsh/profiles/<active profile>/mcp.yml and mcp.json, and $DSH_HOME/dsh-mcp.json (the other plugin's global store; watched only so creating it can be diagnosed, never mounted). chokidar v5 can deliver an event for a watched missing file when it is created, as long as its parent directory exists — never the home directory at large.
  • Profile name resolution: derived from the loader root include's config.path (~/.dsh/profiles/<name>/cordis.yml) or ctx.baseUrl, and overridable with DSH_MCP_PROFILE=<name>; when it cannot be resolved the profile layer is not read (the other layers still are).
  • Effective names: when the original serverName is unique across the whole catalog (host global rows + all project rows) it keeps its name; on a conflict project rows are renamed to p<first 6 hex chars of sha256(project root)>_<original name> (truncated to 32 characters, deterministic and independent of mount order) to avoid the serverName reservation conflicts that dsh-mcp-client makes per process root. Global rows (profile patch lines and user-layer rows) participate in occupancy determination but are never renamed. Model-visible tool names are built from the effective server name and the MCP tool's own name (mcp__<effectiveServerName>__<toolName>), which may differ from the serverName written in the file.
  • Session visibility: when an agent is created, its session cwd resolves to a project, and tools.restrict({ deny }) is applied to that agent to deny every project server except those of the session's own project, plus the global servers suppressed by the project's own rows; a session without a cwd falls back to the owner project (subagents), then to the project containing the dsh process cwd. Released when the session is destroyed.

Query surface

Other plugins and the companion UI read mount state from ctx.projectMcp (snapshot, serverView, globalState, reload). Queries are the previous reconcile's memory; they do not read disk. A successful reconcile emits projectMcp/updated with no payload. The listener calls snapshot() and diffs. The core loader does not open a browser SSE channel; the bundled UI owns that channel. The same service has the write methods the settings page uses (addServer, setServerEnabled, removeServer, setToolEnabled); they only write a managed mcp.yml.

That surface — the methods, the event, and the view types re-exported from the package entry (ProjectFileState, McpServerRuntimeView, McpServerView, McpRowSource, and the types those views name) — follows semantic versioning for the companion UI. The manager depends on dsh-project-mcp-ui at the same exact version, and the UI peer-depends on dsh-project-mcp-manager at that exact version (no ^ or ~ in either published contract). Both packages must be versioned and released together; users install only the manager. Details: query surface.

Security boundary

stdio lines in .dsh/mcp.yml, .dsh/mcp.json and .mcp.json spawn their command inside the dsh host process — config files are executable code carriers, so only add them in projects you trust. The user layers (~/.dsh/mcp.yml, ~/.dsh/mcp.json, the profile yml and json) are executable code carriers too, they just belong to your own machine: user-layer rows mount globally (one host-level connection, visible to every project) and are no longer fanned out per project. Lines that fail to mount or are invalid are skipped with a warning and do not affect other servers. Claude user-state monoliths such as ~/.claude.json (mixing credentials with project history) are no longer read at all as of v0.4.0.

The settings page writes the same managed files, so adding a local-command server there spawns that command on the host. Its routes (/api/project-mcp/*) are served on the dsh connection: whoever can use the dsh web GUI can add and start servers through it.

Coexistence with other MCP manager plugins

This plugin and @wingsky-1/dsh-mcp-manager both auto-load per-project MCP servers, but they do not share a file format:

  1. Project files are mutually incompatible. This plugin reads { mcpServers: { … } } in <projectRoot>/.dsh/mcp.json. The other plugin stores { version, servers: [] } at the same path. A missing mcpServers key is a legal empty layer here, so the other format would otherwise look like "I configured it but nothing happens". The loader now writes a diagnostic naming that format and suggesting mcpServers or .dsh/mcp.yml. The same hint applies to ~/.dsh/dsh-mcp.json. If that file already uses this plugin's mcpServers dialect, the diagnostic tells you to move the object into mcp.json — it is still not loaded from the other plugin's filename.
  2. The same serverName can be started twice (once by each plugin). stdio servers may contend for ports or exclusive resources.
  3. Prefer one plugin per project, or keep this plugin on .dsh/mcp.yml and the other on .dsh/mcp.json.

globalNames() only sees official loader patch rows, not tools registered by the other plugin at runtime, so rename-to-avoid-collision does not cover that other instance.

—/ 5

No ratings yet

Verified DSH bundle

Commit bf122209e3d5

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout