dsh-project-mcp-manager 0.3.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -296
- package/docs/README.zh.md +125 -244
- package/docs/code-review/ts-review-since-v0.3.1.zh.md +451 -0
- package/docs/design/adaptation-dsh-0.1.2-rc1.md +117 -0
- package/docs/design/adaptation-dsh-0.1.5-rc1.md +121 -0
- package/docs/design/proposal-json-mcp-config.md +195 -0
- package/docs/guide/cli.md +48 -0
- package/docs/guide/cli.zh.md +39 -0
- package/docs/guide/env-expansion.md +24 -0
- package/docs/guide/env-expansion.zh.md +19 -0
- package/docs/guide/format.md +85 -0
- package/docs/guide/format.zh.md +74 -0
- package/docs/guide/layers.md +103 -0
- package/docs/guide/layers.zh.md +77 -0
- package/docs/releases/v0.3.1.md +152 -0
- package/docs/releases/v0.4.0.md +159 -0
- package/docs/releases/v0.4.1.md +117 -0
- package/docs/releases/v0.4.2.md +65 -0
- package/docs/releases/v0.4.3.md +54 -0
- package/lib/cli.js +338 -126
- package/lib/dsh-paths.js +65 -0
- package/lib/index.js +26 -1
- package/lib/json-file.js +202 -0
- package/lib/json-write.js +125 -0
- package/lib/mcp-file.js +27 -6
- package/lib/model.js +24 -15
- package/lib/registry.js +710 -329
- package/package.json +8 -6
- package/lib/cc-file.js +0 -192
package/README.md
CHANGED
|
@@ -1,296 +1,153 @@
|
|
|
1
|
-
# dsh-project-mcp-manager
|
|
2
|
-
|
|
3
|
-
English | [中文](docs/README.zh.md)
|
|
4
|
-
|
|
5
|
-
A project-level MCP auto-loading plugin for DSH: write MCP server configs in
|
|
6
|
-
`<projectRoot>/.dsh/mcp.yml` and they are mounted automatically (via the
|
|
7
|
-
official `@deepseek-ai/dsh-mcp-client`) whenever a dsh session opens in that
|
|
8
|
-
project. Changes to the file hot-reload into the running dsh process, and tool
|
|
9
|
-
visibility is scoped per session cwd. No UI — core functionality only.
|
|
10
|
-
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
server across all layers, keep a `disabled: true` placeholder row (matching
|
|
155
|
-
name or normalized name) in `.dsh/mcp.yml`.
|
|
156
|
-
- Broken files/entries never take down the valid ones; each source fails
|
|
157
|
-
independently, so an unreadable `.mcp.json` cannot unmount the project's
|
|
158
|
-
yml rows (and vice versa). Entry errors land in `.dsh/.mcp-diag.json` and
|
|
159
|
-
the host log (file content is never echoed). A project whose only MCP
|
|
160
|
-
config is `.mcp.json` still gets a `.dsh/` directory as soon as there is
|
|
161
|
-
anything worth reporting there.
|
|
162
|
-
- `~/.claude.json` is rewritten by CC on every session; the watcher re-reads
|
|
163
|
-
it but only triggers reconciliation when the `mcpServers` subtree actually
|
|
164
|
-
changed (canonical-JSON hash gate).
|
|
165
|
-
- Boundary switches (dsh host environment): `DSH_MCP_READ_CLAUDE_USER=1`
|
|
166
|
-
opts the `~/.claude.json` user layer in; `DSH_MCP_IGNORE_MCP_JSON=1` turns
|
|
167
|
-
the project `.mcp.json` layer off; the legacy `DSH_MCP_IGNORE_CLAUDE_JSON=1`
|
|
168
|
-
force-disables the user layer and **wins over the opt-in** (a one-shot
|
|
169
|
-
warning names the winner; the legacy switch will be removed in a later
|
|
170
|
-
release). While the user layer is on, the host logs its fan-out size once
|
|
171
|
-
("N servers will join M known projects") so extra spawns are explainable.
|
|
172
|
-
|
|
173
|
-
**Shadow priority** — layers merge first-come-first-served (1 → 4 below), and
|
|
174
|
-
a row is shadowed when it collides with an earlier row on **any** of three
|
|
175
|
-
keys: the exact `serverName`; the *normalized name* (lowercased with
|
|
176
|
-
non-alphanumerics stripped — `unityMCP` and `unity-mcp` are one service
|
|
177
|
-
written two ways); or the *service identity* (`stdio`: command + args,
|
|
178
|
-
path-case-insensitive on Windows; `streamable-http`: the url). Rows without a
|
|
179
|
-
command/url register no identity key — `node a.js` and `node b.js` stay
|
|
180
|
-
different services — while `disabled` placeholder rows hold all three keys
|
|
181
|
-
without mounting anything. Identity is compared on the **raw strings as
|
|
182
|
-
written in the file, before `${VAR}` expansion**, and `env`, `headers`, `cwd`
|
|
183
|
-
are *not* part of the key: two genuinely different servers sharing one command
|
|
184
|
-
line (but e.g. different env) still collapse to the winner, while the same
|
|
185
|
-
server written once with a `${VAR}` and once as a literal does not match. If a
|
|
186
|
-
drop was unintended, rename the loser (past normalization) or adjust its
|
|
187
|
-
command line. Losers are reported in `.dsh/.mcp-diag.json`
|
|
188
|
-
(`shadowedByYml` / `shadowedByProject` / `shadowedIdentity`) and every
|
|
189
|
-
identity/normalized-name drop warns in the host log:
|
|
190
|
-
|
|
191
|
-
1. `<projectRoot>/.dsh/mcp.yml` (native, panel/CLI-managed)
|
|
192
|
-
2. `<projectRoot>/.mcp.json` (CC project)
|
|
193
|
-
3. `~/.dsh/mcp.yml` (native user layer — see CLI)
|
|
194
|
-
4. `~/.claude.json` top-level `mcpServers` (CC user, opt-in)
|
|
195
|
-
|
|
196
|
-
User-layer rows apply to every known project, so a name defined both in one
|
|
197
|
-
project and in a user layer (or in two projects) participates in the regular
|
|
198
|
-
effective-name conflict renaming (see How it works).
|
|
199
|
-
|
|
200
|
-
## `${VAR}` expansion
|
|
201
|
-
|
|
202
|
-
`${VAR}` references (matching `\$\{[A-Za-z_][A-Za-z0-9_]*\}` anywhere in the
|
|
203
|
-
string) in `command`, `args[*]`, `env[*]`, `cwd`, `url` and `headers[*]` — from
|
|
204
|
-
**any** of the four sources above — are **interpolated** from the dsh host
|
|
205
|
-
process environment at mount time (same semantics as Claude Code, so
|
|
206
|
-
`"Authorization": "Bearer ${TOKEN}"` works). An unset or empty variable makes
|
|
207
|
-
the row skip with an `env-missing` diagnostic naming the variable (never its
|
|
208
|
-
value); a literal `${NAME}` that must survive unexpanded is not expressible.
|
|
209
|
-
A `url` containing a reference is accepted by the pre-mount schema in any
|
|
210
|
-
position — including the host part, e.g. `https://${HOST}/mcp` — because
|
|
211
|
-
validity is judged only after expansion: expanded inputs are re-validated
|
|
212
|
-
against the mount schema before spawn, and a malformed result (e.g. a non-URL
|
|
213
|
-
`${GATEWAY}/mcp`) skips the row with an `env-invalid` diagnostic instead of
|
|
214
|
-
reaching the mount backend. In the snapshot/row views, `fiberPhase` stays on
|
|
215
|
-
the mount-lifecycle vocabulary (`pending` for a row that never mounted) and
|
|
216
|
-
the reason rides on a separate `skipReason` field (`env-missing` /
|
|
217
|
-
`env-invalid` / `config-invalid` / `plugin-throw`). Values are never
|
|
218
|
-
persisted anywhere by the plugin; the CLI writes `${VAR}` through literally,
|
|
219
|
-
so secrets can live in the environment while configs live in git.
|
|
220
|
-
|
|
221
|
-
## CLI: `dsh-mcp`
|
|
222
|
-
|
|
223
|
-
CC-style command-line management for the **native** files (writes only
|
|
224
|
-
`.dsh/mcp.yml` — never `.mcp.json` / `~/.claude.json`; running hosts
|
|
225
|
-
converge via the file watchers, no dsh connection needed):
|
|
226
|
-
|
|
227
|
-
```powershell
|
|
228
|
-
dsh-mcp add gitlab npx -y @modelcontextprotocol/server-gitlab -e GITLAB_TOKEN=${GITLAB_TOKEN}
|
|
229
|
-
dsh-mcp add --transport http sentry https://mcp.sentry.dev/mcp -H "Authorization: Bearer ${SENTRY_TOKEN}"
|
|
230
|
-
dsh-mcp add --scope user shared node ./tools/shared.js # writes ~/.dsh/mcp.yml
|
|
231
|
-
dsh-mcp list # all four layers, with shadow annotations
|
|
232
|
-
dsh-mcp get gitlab # winning entry, secret values shown as key names only
|
|
233
|
-
dsh-mcp remove gitlab # native yml only; read-only layers get guidance
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Scopes: `--scope project` (default; writes `<projectRoot>/.dsh/mcp.yml` under
|
|
237
|
-
the nearest `.git` ancestor) and `--scope user` (writes `~/.dsh/mcp.yml`,
|
|
238
|
-
mounted into every project). `add` defaults `cwd` to `"."` (project root) for
|
|
239
|
-
project scope and `""` (host directory) for user scope; `-c` overrides.
|
|
240
|
-
There is no `local` scope — `--scope local`
|
|
241
|
-
fails with an explanation. `--transport` accepts `stdio` (default) and `http`;
|
|
242
|
-
`sse` is refused (unsupported by the backend).
|
|
243
|
-
|
|
244
|
-
## How it works
|
|
245
|
-
|
|
246
|
-
- **Project discovery**: the `session.header.cwd` of an active agent session,
|
|
247
|
-
plus the dsh process start directory → walk up to the nearest ancestor
|
|
248
|
-
containing `.git` as the project root (falls back to the directory itself
|
|
249
|
-
when there is no `.git`).
|
|
250
|
-
- **Mounting**: each `(project, serverName)` pair mounts one
|
|
251
|
-
`@deepseek-ai/dsh-mcp-client` instance (`ctx.plugin`) on the host ctx and
|
|
252
|
-
registers it into the global tool layer. Multiple sessions inside the same
|
|
253
|
-
project share a single connection.
|
|
254
|
-
- **Hot reload**: chokidar watches each project root (depth 2, ignoring
|
|
255
|
-
node_modules/.git/.hg/.svn), but only edits to the **exact** config files of
|
|
256
|
-
known project roots — `<projectRoot>/.dsh/mcp.yml` and
|
|
257
|
-
`<projectRoot>/.mcp.json` — trigger a full reconciliation after a 150 ms
|
|
258
|
-
debounce: added lines are mounted, removed lines are unmounted, and config
|
|
259
|
-
changes are remounted. A second watcher covers the user layer as two
|
|
260
|
-
**exact file paths** — `~/.dsh/mcp.yml` and `~/.claude.json` (chokidar v5
|
|
261
|
-
notices a watched file being created as long as its parent directory
|
|
262
|
-
exists) — never the home directory at large. `~/.claude.json` events are
|
|
263
|
-
arbitrated by the canonical-JSON content hash alone (no size/mtime fast
|
|
264
|
-
path: same-instant, same-length rewrites with different content must not be
|
|
265
|
-
swallowed).
|
|
266
|
-
- **Effective names**: when the original `serverName` is unique across the
|
|
267
|
-
whole catalog (global lines + all project lines, where each project's
|
|
268
|
-
merged rows include the user-layer rows that survived shadowing) it keeps
|
|
269
|
-
its name; on a conflict both sides are renamed to
|
|
270
|
-
`p<first 6 chars of sha256(projectRoot)>_<original name>` (truncated to 32
|
|
271
|
-
characters, deterministic and independent of mount order) to avoid the
|
|
272
|
-
serverName reservation conflicts that `dsh-mcp-client` makes per process
|
|
273
|
-
root. Global lines (profile `cordis.patch.yml` / mcp-client lines already
|
|
274
|
-
mounted at the bundle level) participate in occupancy determination but are
|
|
275
|
-
never renamed. Model-visible tool names are built from the **effective**
|
|
276
|
-
server name and the MCP tool's own name (`mcp__<effectiveServerName>__<toolName>`),
|
|
277
|
-
which may differ from the `serverName` written in the file.
|
|
278
|
-
- **Session visibility**: when an agent is created, its session cwd resolves
|
|
279
|
-
to a project, and `tools.restrict({ deny })` is applied to that agent to deny
|
|
280
|
-
every project server except those of the session's own project; a session
|
|
281
|
-
without a cwd falls back to the owner project (subagents), then to the
|
|
282
|
-
project containing the dsh process cwd. Released when the session is
|
|
283
|
-
destroyed.
|
|
284
|
-
|
|
285
|
-
## Security boundary
|
|
286
|
-
|
|
287
|
-
`stdio` lines in `.dsh/mcp.yml` (and `.mcp.json` rows mounted from it) spawn
|
|
288
|
-
their `command` inside the dsh host process — project files are **executable
|
|
289
|
-
code carriers**, so only add them in projects you trust. Lines that fail to
|
|
290
|
-
mount or are invalid are skipped with a warning and do not affect other
|
|
291
|
-
servers. CC's machine-wide `~/.claude.json` user layer is therefore **off by
|
|
292
|
-
default**: reading it opts foreign, environment-level servers into every
|
|
293
|
-
project's spawn set, which must be a deliberate `DSH_MCP_READ_CLAUDE_USER=1`.
|
|
294
|
-
The read-only allowlist exists precisely because that file also holds
|
|
295
|
-
credentials: nothing from it is ever written out, echoed into diagnostics, or
|
|
296
|
-
printed by the CLI.
|
|
1
|
+
# dsh-project-mcp-manager
|
|
2
|
+
|
|
3
|
+
English | [中文](docs/README.zh.md)
|
|
4
|
+
|
|
5
|
+
A project-level MCP auto-loading plugin for DSH: write MCP server configs in
|
|
6
|
+
`<projectRoot>/.dsh/mcp.yml` and they are mounted automatically (via the
|
|
7
|
+
official `@deepseek-ai/dsh-mcp-client`) whenever a dsh session opens in that
|
|
8
|
+
project. Changes to the file hot-reload into the running dsh process, and tool
|
|
9
|
+
visibility is scoped per session cwd. No UI — core functionality only.
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
Feature documentation lives in `docs/`, English and Chinese side by side:
|
|
14
|
+
|
|
15
|
+
- [Configuration format](docs/guide/format.md) — native YAML managed
|
|
16
|
+
block, JSON dialect, divergences from the cordis dialect.
|
|
17
|
+
- [Configuration sources and layers](docs/guide/layers.md) — the
|
|
18
|
+
six-layer source model, shadow priority, global vs project mounting, and the
|
|
19
|
+
read-only legacy Claude Code layer.
|
|
20
|
+
- [`${VAR}` expansion](docs/guide/env-expansion.md) — mount-time interpolation and
|
|
21
|
+
its diagnostics.
|
|
22
|
+
- [CLI `dsh-mcp`](docs/guide/cli.md) — scopes, write formats, ownership contract.
|
|
23
|
+
|
|
24
|
+
Design and release records (Chinese): [dsh 0.1.5-rc.1 adaptation](docs/design/adaptation-dsh-0.1.5-rc1.md) ·
|
|
25
|
+
[dsh 0.1.2-rc.1 adaptation](docs/design/adaptation-dsh-0.1.2-rc1.md) ·
|
|
26
|
+
[JSON config layer proposal](docs/design/proposal-json-mcp-config.md) ·
|
|
27
|
+
[v0.4.3 release notes](docs/releases/v0.4.3.md) ·
|
|
28
|
+
[v0.4.2 release notes](docs/releases/v0.4.2.md) ·
|
|
29
|
+
[v0.4.1 release notes](docs/releases/v0.4.1.md) ·
|
|
30
|
+
[v0.4.0 release notes](docs/releases/v0.4.0.md) ·
|
|
31
|
+
[v0.3.1 release notes](docs/releases/v0.3.1.md).
|
|
32
|
+
|
|
33
|
+
Code review records (Chinese): [TypeScript changes since v0.3.1](docs/code-review/ts-review-since-v0.3.1.zh.md).
|
|
34
|
+
|
|
35
|
+
## Installation (mount into a profile)
|
|
36
|
+
|
|
37
|
+
The plugin is mounted through a **bundle patch**: once the package is added to
|
|
38
|
+
`dsh.profile.bundles`, dsh synthesizes each bundle's patch (the
|
|
39
|
+
`cordis.patch.yml` pointed to by `dsh.bundle.patch`) into plugin lines at
|
|
40
|
+
startup, in order.
|
|
41
|
+
|
|
42
|
+
**Prerequisite: install dsh itself** (for users who don't have dsh yet):
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
npm install -g @deepseek-ai/dsh # official npm package
|
|
46
|
+
npm install -g deepseek-ai/dsh # or install from the GitHub source
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Option 1: the dsh plugin command (recommended)** — `dsh plugin` forwards
|
|
50
|
+
pnpm inside the profile directory and handles installing/upgrading
|
|
51
|
+
dependencies:
|
|
52
|
+
|
|
53
|
+
```powershell
|
|
54
|
+
# Install the latest version (web profile shown as an example;
|
|
55
|
+
# substitute the name of any other profile, e.g. headless)
|
|
56
|
+
dsh plugin --profile web add dsh-project-mcp-manager@latest
|
|
57
|
+
|
|
58
|
+
# Install a specific version (check available versions with
|
|
59
|
+
# npm view dsh-project-mcp-manager versions)
|
|
60
|
+
dsh plugin --profile web add dsh-project-mcp-manager@0.4.2
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Option 2: install directly with pnpm** (equivalent to option 1):
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
# dshHome defaults to %USERPROFILE%\.dsh (uses $DSH_HOME if set)
|
|
67
|
+
cd $env:USERPROFILE\.dsh\profiles\web
|
|
68
|
+
pnpm add dsh-project-mcp-manager@latest
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Option 3: local development install** (a junction that live-syncs your
|
|
72
|
+
source, so code changes take effect immediately):
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
cd $env:USERPROFILE\.dsh\profiles\web
|
|
76
|
+
pnpm add link:<path-to-your-dsh-mcp-project-source> # e.g. D:\dev\dsh-mcp-project
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **dsh ≥ 0.1.2 note**: whether the plugin loads depends on the profile's
|
|
80
|
+
> `dsh.profile.bundles` list, and a plain `pnpm add link:` does **not** add the
|
|
81
|
+
> package to it. Options 1 and 2 reconcile it automatically; if you ran pnpm by
|
|
82
|
+
> hand, run any `dsh plugin --profile web list` once (or check
|
|
83
|
+
> `dsh --profile web --dump-config` for a `dsh-project-mcp-manager` row) to
|
|
84
|
+
> trigger the bundle reconcile.
|
|
85
|
+
|
|
86
|
+
**Upgrading / pinning versions**: re-run the `add` command from option 1 with
|
|
87
|
+
the desired version suffix — `@latest` upgrades to the newest release, `@0.4.2`
|
|
88
|
+
pins to a specific version.
|
|
89
|
+
|
|
90
|
+
## Build & test
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
pnpm install
|
|
94
|
+
pnpm run build # tsc → lib/
|
|
95
|
+
pnpm test # node test/test-model.mjs / test-mcp-file / test-json-file / test-json-write / test-registry / test-cli
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
- **Project discovery**: the `session.header.cwd` of an active agent session,
|
|
101
|
+
plus the dsh process start directory → walk up to the nearest ancestor
|
|
102
|
+
containing `.git` as the project root (falls back to the directory itself
|
|
103
|
+
when there is no `.git`).
|
|
104
|
+
- **Mounting**: each `(project, serverName)` pair in the project layers mounts
|
|
105
|
+
one `@deepseek-ai/dsh-mcp-client` instance (`ctx.plugin`) on the host ctx and
|
|
106
|
+
registers it into the global tool layer; multiple sessions inside the same
|
|
107
|
+
project share a single connection. **Every user-layer row mounts exactly one
|
|
108
|
+
instance** (global, independent of the number of projects) — see
|
|
109
|
+
[configuration sources and layers](docs/guide/layers.md).
|
|
110
|
+
- **Hot reload**: chokidar watches each project root (depth 2, ignoring
|
|
111
|
+
node_modules/.git/.hg/.svn), but only edits to the **exact** config files of
|
|
112
|
+
known project roots — `<projectRoot>/.dsh/mcp.yml`,
|
|
113
|
+
`<projectRoot>/.dsh/mcp.json` and `<projectRoot>/.mcp.json` — trigger a full
|
|
114
|
+
reconciliation after a 150 ms debounce: added rows are mounted, removed rows
|
|
115
|
+
are unmounted, and config changes are remounted. A second watcher covers the
|
|
116
|
+
user layer as three **exact file paths** — `~/.dsh/mcp.yml`,
|
|
117
|
+
`~/.dsh/mcp.json` and `~/.dsh/profiles/<active profile>/mcp.json` (chokidar
|
|
118
|
+
v5 can deliver an event for a watched missing file when it is created, as
|
|
119
|
+
long as its parent directory exists) — never the home directory at large.
|
|
120
|
+
- **Profile name resolution**: derived from the loader root include's
|
|
121
|
+
`config.path` (`~/.dsh/profiles/<name>/cordis.yml`) or `ctx.baseUrl`, and
|
|
122
|
+
overridable with `DSH_MCP_PROFILE=<name>`; when it cannot be resolved the
|
|
123
|
+
profile layer is not read (the other layers still are).
|
|
124
|
+
- **Effective names**: when the original `serverName` is unique across the
|
|
125
|
+
whole catalog (host global rows + all project rows) it keeps its name; on a
|
|
126
|
+
conflict **project rows** are renamed to `p<first 6 hex chars of
|
|
127
|
+
sha256(project root)>_<original name>` (truncated to 32 characters,
|
|
128
|
+
deterministic and independent of mount order) to avoid the serverName
|
|
129
|
+
reservation conflicts that `dsh-mcp-client` makes per process root. Global
|
|
130
|
+
rows (profile patch lines and user-layer rows) participate in occupancy
|
|
131
|
+
determination but are never renamed. Model-visible tool names are built from
|
|
132
|
+
the **effective** server name and the MCP tool's own name
|
|
133
|
+
(`mcp__<effectiveServerName>__<toolName>`), which may differ from the
|
|
134
|
+
`serverName` written in the file.
|
|
135
|
+
- **Session visibility**: when an agent is created, its session cwd resolves to
|
|
136
|
+
a project, and `tools.restrict({ deny })` is applied to that agent to deny
|
|
137
|
+
every project server except those of the session's own project, plus the
|
|
138
|
+
global servers suppressed by the project's own rows; a session without a cwd
|
|
139
|
+
falls back to the owner project (subagents), then to the project containing
|
|
140
|
+
the dsh process cwd. Released when the session is destroyed.
|
|
141
|
+
|
|
142
|
+
## Security boundary
|
|
143
|
+
|
|
144
|
+
`stdio` lines in `.dsh/mcp.yml`, `.dsh/mcp.json` and `.mcp.json` spawn their
|
|
145
|
+
`command` inside the dsh host process — config files are **executable code
|
|
146
|
+
carriers**, so only add them in projects you trust. The user layers
|
|
147
|
+
(`~/.dsh/mcp.yml`, `~/.dsh/mcp.json`, the profile json) are executable code
|
|
148
|
+
carriers too, they just belong to your own machine: user-layer rows mount
|
|
149
|
+
**globally** (one host-level connection, visible to every project) and are no
|
|
150
|
+
longer fanned out per project. Lines that fail to mount or are invalid are
|
|
151
|
+
skipped with a warning and do not affect other servers. Claude user-state
|
|
152
|
+
monoliths such as `~/.claude.json` (mixing credentials with project history)
|
|
153
|
+
are **no longer read at all** as of v0.4.0.
|