dsh-project-mcp-manager
English | 中文
A project-level MCP auto-loading plugin for DSH: write MCP server configs in
<projectRoot>/.dsh/mcp.yml 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 is scoped per session cwd. No UI — core functionality only.
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.
- Configuration sources and layers — the six-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.
Design and release records (Chinese): dsh 0.1.5-rc.1 adaptation · dsh 0.1.2-rc.1 adaptation · JSON config layer proposal · 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.
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.
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 (web profile shown as an example;
# substitute the name of any other profile, e.g. headless)
dsh plugin --profile web add dsh-project-mcp-manager@latest
# Install a specific version (check available versions with
# npm view dsh-project-mcp-manager versions)
dsh plugin --profile web add dsh-project-mcp-manager@0.4.2
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 (a junction that live-syncs your source, so code changes take effect immediately):
cd $env:USERPROFILE\.dsh\profiles\web
pnpm add link:<path-to-your-dsh-mcp-project-source> # e.g. D:\dev\dsh-mcp-project
dsh ≥ 0.1.2 note: whether the plugin loads depends on the profile's
dsh.profile.bundleslist, and a plainpnpm add link:does not add the package to it. Options 1 and 2 reconcile it automatically; if you ran pnpm by hand, run anydsh plugin --profile web listonce (or checkdsh --profile web --dump-configfor adsh-project-mcp-managerrow) to trigger the bundle reconcile.
Upgrading / pinning versions: re-run the add command from option 1 with
the desired version suffix — @latest upgrades to the newest release, @0.4.2
pins to a specific version.
Build & test
pnpm install
pnpm run build # tsc → lib/
pnpm test # node test/test-model.mjs / test-mcp-file / test-json-file / test-json-write / test-registry / test-cli
How it works
- Project discovery: the
session.header.cwdof an active agent session, plus the dsh process start directory → walk up to the nearest ancestor containing.gitas 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-clientinstance (ctx.plugin) on the host ctx and registers it into the global tool layer; multiple sessions inside the same project share a single connection. 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 project root (depth 2, ignoring
node_modules/.git/.hg/.svn), but only edits to the exact config files of
known project roots —
<projectRoot>/.dsh/mcp.yml,<projectRoot>/.dsh/mcp.jsonand<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 three exact file paths —~/.dsh/mcp.yml,~/.dsh/mcp.jsonand~/.dsh/profiles/<active profile>/mcp.json(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) orctx.baseUrl, and overridable withDSH_MCP_PROFILE=<name>; when it cannot be resolved the profile layer is not read (the other layers still are). - Effective names: when the original
serverNameis unique across the whole catalog (host global rows + all project rows) it keeps its name; on a conflict project rows are renamed top<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 thatdsh-mcp-clientmakes 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 theserverNamewritten 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.
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 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.
No comments yet. Be the first to write one.