@gyp8735abc/dsh-mcp-manager 0.0.0-stage → 0.2.0
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/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +278 -2
- package/README.zh.md +263 -0
- package/client/client.js +535 -0
- package/cordis.patch.yml +5 -0
- package/icon.svg +11 -0
- package/lib/index.js +1099 -0
- package/lib/skill-filter.js +87 -0
- package/lib/skill-roots.js +518 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +79 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.2.0] — 2026-11-01
|
|
8
|
+
|
|
9
|
+
First public release, as `@gyp8735abc/dsh-mcp-manager`.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Per-workspace MCP switching: a popup on each workspace row lists that
|
|
14
|
+
workspace's own `.mcp.json` servers, the global servers from `~/.claude.json`,
|
|
15
|
+
profile-declared ("manual") servers, and the skills visible to the workspace,
|
|
16
|
+
each with its own switch.
|
|
17
|
+
- Per-workspace skill switching, enforced for the model through a
|
|
18
|
+
`SkillRegistry` prototype filter plus a `tools.guard()` refusal.
|
|
19
|
+
- Union mounting: every workspace with a live session keeps its MCP servers
|
|
20
|
+
mounted, one process per (workspace, server); focusing another session never
|
|
21
|
+
stops them, and a workspace's servers unload with its last session.
|
|
22
|
+
- Session-scoped tool filtering, so another live workspace's `mcp__*` tools never
|
|
23
|
+
reach the current session.
|
|
24
|
+
- Claude Code compatibility: project `.claude/skills` and user `~/.claude/skills`
|
|
25
|
+
roots, including `SKILL.md` files without YAML frontmatter.
|
|
26
|
+
- Dynamic rescan on every popup open, so workspaces and `.mcp.json` edits apply
|
|
27
|
+
without restarting.
|
|
28
|
+
- `GET /api/dsh-mcp-manager/inventory`, `POST /api/dsh-mcp-manager/activate` and
|
|
29
|
+
`POST /api/dsh-mcp-manager/set` endpoints.
|
|
30
|
+
- Plugin market index entry (`docs/index.json`) for one-click installs from the
|
|
31
|
+
Plugins → Discover tab.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- State moved out of the DSH settings namespace into
|
|
36
|
+
`$DSH_HOME/mcp-manager/state.json`; the legacy `mcp:` settings section is
|
|
37
|
+
imported once on first start.
|
|
38
|
+
- No core package is patched any more: the workspace-row button, the Claude skill
|
|
39
|
+
roots and the skill filter are all implemented through public seams
|
|
40
|
+
(`MutationObserver` DOM injection, `ctx.skills.registerProvider()`,
|
|
41
|
+
`SkillRegistry.prototype`).
|
|
42
|
+
|
|
43
|
+
### Known limitations
|
|
44
|
+
|
|
45
|
+
- No filesystem watcher for skills; a skill added while DSH runs appears after a
|
|
46
|
+
restart.
|
|
47
|
+
- The workspace-row button is injected into the sidebar DOM, because DSH 0.2.x
|
|
48
|
+
declares slots for session rows only.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gyp8735abc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,279 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-mcp-manager
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README.zh.md) · [](https://www.npmjs.com/package/@gyp8735abc/dsh-mcp-manager) · [](LICENSE)
|
|
4
|
+
|
|
5
|
+
A **per-workspace** MCP and skill manager for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
6
|
+
|
|
7
|
+
One button on each workspace row opens a popup where you decide which MCP servers and which
|
|
8
|
+
skills that workspace gets. Global MCP, project MCP and skills all switch **independently per
|
|
9
|
+
workspace**.
|
|
10
|
+
|
|
11
|
+
## The problem it solves
|
|
12
|
+
|
|
13
|
+
DSH's own MCP configuration is **profile-wide**: whatever is in `mcp:` applies to every
|
|
14
|
+
workspace and every session. In daily use that means:
|
|
15
|
+
|
|
16
|
+
- `codebase` is useful in project A and in project B, but the two projects keep different
|
|
17
|
+
`.codebase/codebase.db` files — one global config can only point at one of them.
|
|
18
|
+
- You keep five MCP servers around, but this workspace needs one. The other four hold their
|
|
19
|
+
processes open and stuff the tool list, so the model picks the wrong tool more often.
|
|
20
|
+
- The same for skills: the dozens under `~/.claude/skills` show up in every session although
|
|
21
|
+
only a couple of workspaces really want them.
|
|
22
|
+
|
|
23
|
+
This plugin moves the switches down to the **workspace** layer:
|
|
24
|
+
|
|
25
|
+
| Object | Source | Switch granularity |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Global MCP | `mcpServers` in `~/.claude.json` | per workspace |
|
|
28
|
+
| Project MCP | the workspace's own `.mcp.json` | per workspace |
|
|
29
|
+
| Manual MCP | hand-written `dsh-mcp-client` rows in the profile | per workspace |
|
|
30
|
+
| Skills | `.dsh/skills`, `.claude/skills`, `.agents/skills`, `~/.dsh/skills`, `~/.claude/skills`, `~/.agents/skills` | per workspace |
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **One popup for everything** — grouped into project MCP / global MCP / manual MCP / project
|
|
35
|
+
skills / global skills, one switch per row, with `Enabled`/`Disabled` and
|
|
36
|
+
`Connected`/`Not connected` shown inline.
|
|
37
|
+
- **Real per-workspace isolation** — the state key is the workspace path; a server you turn
|
|
38
|
+
off in A stays on in B.
|
|
39
|
+
- **Union mounting** — every workspace with a live session keeps its own MCP servers mounted,
|
|
40
|
+
one process per (workspace, server) with its own config and `cwd`. Focusing or switching a
|
|
41
|
+
session never stops another workspace's servers; a workspace's servers unload with its last
|
|
42
|
+
session.
|
|
43
|
+
- **Session-scoped tool filtering** — only this workspace's `mcp__<workspace>__*` tools plus
|
|
44
|
+
global/manual ones reach the current session, so another live workspace's tools never leak in.
|
|
45
|
+
- **Skills filtered the same way** — a disabled skill disappears from the model's catalog and
|
|
46
|
+
from `/name` injection, and a direct `skill` tool call is refused with an explicit reason.
|
|
47
|
+
- **Claude Code skill compatibility** — project `.claude/skills` and user `~/.claude/skills`
|
|
48
|
+
are scanned, including `SKILL.md` files without YAML frontmatter.
|
|
49
|
+
- **Dynamic rescan** — every popup open re-reads the workspace list and each workspace's
|
|
50
|
+
`.mcp.json`, so new workspaces and edited configs apply without restarting DSH.
|
|
51
|
+
- **Zero core patches** — nothing inside the DSH installation is modified; installing or
|
|
52
|
+
removing the plugin only touches the profile and `$DSH_HOME`.
|
|
53
|
+
- **Bilingual UI** — follows the DSH interface language (English / 简体中文).
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
### Option 1: plugin market (two steps)
|
|
58
|
+
|
|
59
|
+
1. Open **Plugins → Discover → Add index source** and enter:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
https://gyp8735abc.github.io/dsh-mcp-manager/index.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. Search for `MCP manager`, open the entry, press **Install**.
|
|
66
|
+
|
|
67
|
+
> Market indexes are unsigned; titles, authors and sources are publisher claims. Only add
|
|
68
|
+
> index sources you trust, and check in the confirmation dialog that the source is
|
|
69
|
+
> `@gyp8735abc/dsh-mcp-manager`.
|
|
70
|
+
|
|
71
|
+
### Option 2: one command
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
dsh plugin --profile web add @gyp8735abc/dsh-mcp-manager
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The package is also a DSH bundle (`dsh.bundle.patch`), so the plugin row is inserted into the
|
|
78
|
+
profile composition automatically — no hand-written `cordis.patch.yml` needed.
|
|
79
|
+
|
|
80
|
+
### Option 3: from source
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
git clone https://github.com/gyp8735abc/dsh-mcp-manager.git
|
|
84
|
+
cd dsh-mcp-manager
|
|
85
|
+
dsh plugin --profile web add .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Verify
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
dsh --profile web --dump-config | grep -A 3 dsh-mcp-manager
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Seeing `id: dsh-mcp-manager` means it is in the composition tree. Then **restart `dsh web`**
|
|
95
|
+
and hard-reload the browser (`Ctrl+Shift+R`) — the client bundle is injected at startup only.
|
|
96
|
+
|
|
97
|
+
After that, every workspace row gains a plug icon to the left of its `⋯` menu.
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
The popup shows:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
┌ MCP & skills ───────────────────────────────┐
|
|
105
|
+
│ Workspace D:\ai-workspace Active │
|
|
106
|
+
│ │
|
|
107
|
+
│ Project MCP (.mcp.json) │
|
|
108
|
+
│ ● codebase stdio Enabled Connected │
|
|
109
|
+
│ ○ e2e-runner stdio Disabled — │
|
|
110
|
+
│ │
|
|
111
|
+
│ Global MCP (~/.claude.json) │
|
|
112
|
+
│ ● everything-search http Enabled Connected│
|
|
113
|
+
│ │
|
|
114
|
+
│ Manual MCP (profile) │
|
|
115
|
+
│ ○ codegraph stdio Disabled — │
|
|
116
|
+
│ │
|
|
117
|
+
│ Project skills (this workspace) │
|
|
118
|
+
│ ● officecli .dsh Enabled │
|
|
119
|
+
│ ○ probe .claude Disabled │
|
|
120
|
+
│ │
|
|
121
|
+
│ Global skills (all workspaces) │
|
|
122
|
+
│ ● diagnose-windows-sandbox-acl ~/.dsh │
|
|
123
|
+
└──────────────────────────────────────────────┘
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- **Active workspace** (noted at the top): toggles apply **immediately** — enabling connects the
|
|
127
|
+
process and registers its tools, disabling disconnects and unmounts them.
|
|
128
|
+
- **Not the active workspace**: toggles only save state; the servers mount when that workspace
|
|
129
|
+
gets a session.
|
|
130
|
+
- Empty groups are hidden; a workspace with nothing to show gets an empty-state hint.
|
|
131
|
+
- Project servers carry a workspace prefix at runtime: `codebase` in workspace A becomes
|
|
132
|
+
`a__codebase`, and the model sees `mcp__a__codebase__search_code`. That is what lets two
|
|
133
|
+
workspaces' same-named servers coexist as separate processes instead of shadowing each other.
|
|
134
|
+
|
|
135
|
+
## State
|
|
136
|
+
|
|
137
|
+
`$DSH_HOME/mcp-manager/state.json` (`$DSH_HOME` defaults to `~/.dsh`):
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"projects": [
|
|
142
|
+
{
|
|
143
|
+
"path": "D:\\ai-workspace",
|
|
144
|
+
"disabled": ["dsh-mcp-manager__codebase"],
|
|
145
|
+
"disabledSkills": ["office"]
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Field | Meaning |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `path` | Absolute workspace path (identity key) |
|
|
154
|
+
| `disabled` | MCP runtime names disabled in this workspace; project servers use the workspace-qualified name (`<slug>__<name>`), global/manual servers their plain name |
|
|
155
|
+
| `disabledSkills` | Skill names disabled in this workspace |
|
|
156
|
+
|
|
157
|
+
- On first start the legacy `mcp:` section of `$DSH_HOME/settings.yaml` is imported once.
|
|
158
|
+
- Both qualified and legacy plain names (`codebase`) are honoured in `disabled`, so old lists
|
|
159
|
+
keep working.
|
|
160
|
+
- Editing the file by hand takes effect on restart or on the next popup open.
|
|
161
|
+
|
|
162
|
+
### HTTP API
|
|
163
|
+
|
|
164
|
+
The popup uses three same-origin endpoints, which you can also call yourself:
|
|
165
|
+
|
|
166
|
+
| Method | Path | Purpose |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `GET` | `/api/dsh-mcp-manager/inventory?active=<workspace path>` | Full inventory for one workspace (MCP groups, skills, connection state) |
|
|
169
|
+
| `POST` | `/api/dsh-mcp-manager/activate` | `{ "path": "..." }` — remember the focused workspace |
|
|
170
|
+
| `POST` | `/api/dsh-mcp-manager/set` | `{ "path": "...", "kind": "mcp"\|"skill", "name": "...", "enabled": true }` |
|
|
171
|
+
|
|
172
|
+
## Mounting semantics
|
|
173
|
+
|
|
174
|
+
- **Union model**: the mount set is the union of what every workspace with a live session wants.
|
|
175
|
+
With no live session it falls back to the focused workspace's set.
|
|
176
|
+
- **Process granularity**: one process per (workspace, server) for project servers, with `cwd`
|
|
177
|
+
defaulting to the workspace directory — so relative paths such as `.codebase/codebase.db`
|
|
178
|
+
land in their own workspace.
|
|
179
|
+
- **Global servers**: one shared process, mounted while any live workspace enables it.
|
|
180
|
+
- **Manual servers** (hand-written `dsh-mcp-client` rows): kept enabled while any live workspace
|
|
181
|
+
wants them, because one loader row can only own one process.
|
|
182
|
+
- **Unmount**: a workspace's dedicated processes are disposed after its last session closes, and
|
|
183
|
+
the manager sweeps leftover child processes by their command-line signature.
|
|
184
|
+
- **Orphans**: when a managed server is disabled or unmounted, a sweep kills leftover children
|
|
185
|
+
so stdio servers do not survive as stray processes.
|
|
186
|
+
|
|
187
|
+
## Skills
|
|
188
|
+
|
|
189
|
+
- Roots are merged with **lowest rank winning**, one entry per skill name:
|
|
190
|
+
|
|
191
|
+
| Order | Root | Source tag |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| 1 | `<workspace>/.dsh/skills` | `project-dsh` |
|
|
194
|
+
| 2 | `<workspace>/.claude/skills` | `project-claude` |
|
|
195
|
+
| 3 | `<workspace>/.agents/skills` | `project-agents` |
|
|
196
|
+
| 4 | `~/.dsh/skills` | `user-dsh` |
|
|
197
|
+
| 5 | `~/.claude/skills` | `user-claude` |
|
|
198
|
+
| 6 | `~/.agents/skills` | `user-agents` |
|
|
199
|
+
|
|
200
|
+
The `.claude` roots and the frontmatter-less parser are added through
|
|
201
|
+
`ctx.skills.registerProvider()` — the same public seam the shipped provider uses — so no core
|
|
202
|
+
package is patched.
|
|
203
|
+
- A skill is `<dir>/SKILL.md` or a bare `.md` file directly under a root. Without frontmatter the
|
|
204
|
+
directory name becomes the skill name and the first body line the description (the usual Claude
|
|
205
|
+
Code shape).
|
|
206
|
+
- A disabled skill takes effect in three places: the session catalog stops listing it,
|
|
207
|
+
`/skill-name` is no longer injected, and the `skill` tool refuses it with an explicit
|
|
208
|
+
“disabled for this workspace” message.
|
|
209
|
+
|
|
210
|
+
## Compatibility
|
|
211
|
+
|
|
212
|
+
| Item | Requirement |
|
|
213
|
+
|---|---|
|
|
214
|
+
| DSH | `>=0.2.0-rc.2 <0.3.0` (checked against the running version at install time) |
|
|
215
|
+
| Node.js | `>=22` |
|
|
216
|
+
| Surface | Web only (`dsh web` and the desktop GUI's web view); `headless` / `acp` have no UI entry |
|
|
217
|
+
|
|
218
|
+
The only runtime dependencies are packages that ship with the DSH installation
|
|
219
|
+
(`@deepseek-ai/dsh-mcp-client` etc.); the plugin itself has none.
|
|
220
|
+
|
|
221
|
+
## Known limitations
|
|
222
|
+
|
|
223
|
+
- **No skill file watcher** — a skill added to `.claude/skills` while DSH is running appears after
|
|
224
|
+
a restart (existing and project skills all load at startup).
|
|
225
|
+
- **The workspace button is DOM-injected** — DSH 0.2.x declares slots for session rows only, so
|
|
226
|
+
the button is inserted with a `MutationObserver` and re-added after re-renders. If it does not
|
|
227
|
+
show up, first check that the DSH version is inside `>=0.2.0-rc.2 <0.3.0`.
|
|
228
|
+
- **The skill filter patches a prototype** — it affects every consumer of the skill service (for
|
|
229
|
+
example a skill picker UI). That is intended: the switch is per workspace and every lookup
|
|
230
|
+
carries the `cwd`.
|
|
231
|
+
- **State is not in the DSH settings page** — DSH 0.2.x no longer lets a plugin claim an arbitrary
|
|
232
|
+
settings namespace, so the switches live in the plugin's own `state.json`.
|
|
233
|
+
- **Non-active workspaces only save state** — they do not spawn processes eagerly, which keeps
|
|
234
|
+
unused workspaces from holding servers open.
|
|
235
|
+
|
|
236
|
+
## Security
|
|
237
|
+
|
|
238
|
+
- The plugin spawns the **stdio commands** declared in project `.mcp.json` files and
|
|
239
|
+
`~/.claude.json` — that is the same trust you would give that configuration. Only use it with
|
|
240
|
+
repositories and configs you trust.
|
|
241
|
+
- The three HTTP endpoints are **unauthenticated**; the design assumes `dsh web` listens on
|
|
242
|
+
loopback only (the default `127.0.0.1`). With `--host 0.0.0.0`, anyone who can reach the port
|
|
243
|
+
can toggle your MCP servers.
|
|
244
|
+
- The plugin never modifies the DSH installation and never writes `settings.yaml`; its only write
|
|
245
|
+
is `$DSH_HOME/mcp-manager/state.json`.
|
|
246
|
+
|
|
247
|
+
## Development
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
node --check lib/index.js # host: inventory, per-workspace state, union mounting, routes
|
|
251
|
+
node --check lib/skill-filter.js # skill filter (SkillRegistry prototype + tools.guard)
|
|
252
|
+
node --check lib/skill-roots.js # Claude skill-root provider
|
|
253
|
+
node --check client/client.js # browser bundle (ModuleLoader factory form)
|
|
254
|
+
|
|
255
|
+
npm run pack:check # verify the npm package contents (run before publishing)
|
|
256
|
+
npm run market:index # regenerate docs/index.json
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
| Path | Purpose |
|
|
260
|
+
|---|---|
|
|
261
|
+
| `lib/index.js` | Host: state, `inventory` / `activate` / `set`, union mounting, session tool filtering |
|
|
262
|
+
| `lib/skill-filter.js` | Per-workspace skill filtering (catalog, injection, tool call) |
|
|
263
|
+
| `lib/skill-roots.js` | `.claude` roots and frontmatter-less `SKILL.md` parsing |
|
|
264
|
+
| `client/client.js` | Browser half: workspace-row button and the popup (English / 中文) |
|
|
265
|
+
| `cordis.patch.yml` | Bundle patch that inserts the plugin row |
|
|
266
|
+
| `locale/{en,zh}.json` | Title/description shown on the Plugins page |
|
|
267
|
+
| `docs/index.json` | Plugin market index (format 1), served from GitHub Pages |
|
|
268
|
+
| `docs/PUBLISHING.md` | Release process (npm, GitHub, market index) |
|
|
269
|
+
|
|
270
|
+
The browser half is a **hand-written ModuleLoader bundle**
|
|
271
|
+
(`window.__ModuleLoader__.load({ factory })`) with no build step — edit and reload the page.
|
|
272
|
+
|
|
273
|
+
## Publishing
|
|
274
|
+
|
|
275
|
+
See [docs/PUBLISHING.md](docs/PUBLISHING.md).
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
[MIT](LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# dsh-mcp-manager
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文 · [](https://www.npmjs.com/package/@gyp8735abc/dsh-mcp-manager) · [](LICENSE)
|
|
4
|
+
|
|
5
|
+
给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 用的 **按工作区** MCP 与技能管理器。
|
|
6
|
+
|
|
7
|
+
在每个工作区行点一个按钮,就能决定「这个工作区要挂哪些 MCP 服务器、要看哪些技能」——
|
|
8
|
+
全局 MCP 和项目 MCP 的开关**按工作区独立**,互不影响。
|
|
9
|
+
|
|
10
|
+
## 它解决什么问题
|
|
11
|
+
|
|
12
|
+
DSH 自带的 MCP 配置是 **profile 级**的:`mcp:` 里写什么,所有工作区、所有会话都吃同一份。
|
|
13
|
+
于是日常用起来是这样的:
|
|
14
|
+
|
|
15
|
+
- 你在 A 项目里用 `codebase`(SQLite 索引)、在 B 项目里也想用,但两边的 `.codebase/codebase.db`
|
|
16
|
+
是不同文件——一份全局配置只能指向一个库;
|
|
17
|
+
- 你常开 5 个 MCP 服务器,但当前这个工作区只需要 1 个,另外 4 个的进程白白常驻,
|
|
18
|
+
工具列表也被塞满,模型选错工具的概率跟着上去;
|
|
19
|
+
- 技能同理:用户级 `~/.claude/skills` 下的几十个技能,在每个会话里都出现,
|
|
20
|
+
但只有个别工作区真的需要它们。
|
|
21
|
+
|
|
22
|
+
这个插件把「开关」下沉到**工作区**这一层:
|
|
23
|
+
|
|
24
|
+
| 对象 | 来源 | 开关粒度 |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| 全局 MCP | `~/.claude.json` 的 `mcpServers` | 每个工作区独立 |
|
|
27
|
+
| 项目 MCP | 该工作区自己的 `.mcp.json` | 每个工作区独立 |
|
|
28
|
+
| 手动 MCP | profile 配置里手写的 `dsh-mcp-client` 行 | 每个工作区独立 |
|
|
29
|
+
| 技能 | `.dsh/skills`、`.claude/skills`、`.agents/skills`、`~/.dsh/skills`、`~/.claude/skills`、`~/.agents/skills` | 每个工作区独立 |
|
|
30
|
+
|
|
31
|
+
## 功能
|
|
32
|
+
|
|
33
|
+
- **一个弹窗管全部** — 弹窗按「项目 MCP / 全局 MCP / 手动 MCP / 项目技能 / 全局技能」分组,
|
|
34
|
+
每行一个开关,直接显示 `已启用 / 已停用` 与 `已连接 / 未连接`。
|
|
35
|
+
- **真正的按工作区隔离** — 状态键是工作区路径;在 A 里关掉的服务器,B 照常启用。
|
|
36
|
+
- **并集挂载,切换不打架** — 所有「有存活会话」的工作区同时挂载各自的 MCP,每个
|
|
37
|
+
(工作区, 服务器)一个独立进程(各自的配置和 `cwd`)。切换/聚焦会话**不会**停掉别的工作区的 MCP;
|
|
38
|
+
某个工作区的 MCP 在它最后一个会话关闭后才卸载。
|
|
39
|
+
- **会话级工具过滤** — 只有本工作区的 `mcp__<工作区>__*` 和全局/手动服务器进入当前会话的
|
|
40
|
+
工具列表,别的工作区挂着的服务器不会漏进来。
|
|
41
|
+
- **技能也按工作区过滤** — 停用的技能从模型侧目录里消失、`skill` 工具调用会被明确拒绝,
|
|
42
|
+
而不是变成「未知工具」。
|
|
43
|
+
- **兼容 Claude Code 的技能目录** — 项目 `.claude/skills` 与用户 `~/.claude/skills` 都会被扫描,
|
|
44
|
+
包括没有 YAML frontmatter 的 `SKILL.md`。
|
|
45
|
+
- **动态扫描** — 每次打开弹窗都重新读工作区列表与各工作区的 `.mcp.json`,
|
|
46
|
+
新增工作区、改配置文件都不需要重启 DSH。
|
|
47
|
+
- **零核心补丁** — 不改 DSH 安装目录里的任何文件,装/卸插件只动 profile 与 `$DSH_HOME`。
|
|
48
|
+
- **中英双语 UI** — 跟随 DSH 界面语言。
|
|
49
|
+
|
|
50
|
+
## 安装
|
|
51
|
+
|
|
52
|
+
### 方式一:插件市场(推荐,两步)
|
|
53
|
+
|
|
54
|
+
1. 打开 **插件 → 发现 → 添加索引源**,填入:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
https://gyp8735abc.github.io/dsh-mcp-manager/index.json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. 搜索 `MCP manager`,打开条目,点 **安装**。
|
|
61
|
+
|
|
62
|
+
> 索引没有签名,市场里的标题、作者、来源都是发布方的声明。只添加你信任的索引源,
|
|
63
|
+
> 并在安装确认框里核对来源是 `@gyp8735abc/dsh-mcp-manager`。
|
|
64
|
+
|
|
65
|
+
### 方式二:一条命令
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
dsh plugin --profile web add @gyp8735abc/dsh-mcp-manager
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
本包同时是一个 DSH bundle(`dsh.bundle.patch`),安装后插件行会自动插入 profile 的组合,
|
|
72
|
+
不需要手写 `cordis.patch.yml`。
|
|
73
|
+
|
|
74
|
+
### 方式三:从源码
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
git clone https://github.com/gyp8735abc/dsh-mcp-manager.git
|
|
78
|
+
cd dsh-mcp-manager
|
|
79
|
+
dsh plugin --profile web add .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 验证安装
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
dsh --profile web --dump-config | grep -A 3 dsh-mcp-manager
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
能看到 `id: dsh-mcp-manager` 就说明它已经进组合树。然后**重启 `dsh web`** 并强制刷新浏览器
|
|
89
|
+
(`Ctrl+Shift+R`)——浏览器端的 bundle 只在启动时注入。
|
|
90
|
+
|
|
91
|
+
装好后,工作区列表每一行的 `⋯` 菜单**左边**会多出一个插头图标按钮。
|
|
92
|
+
|
|
93
|
+
## 使用
|
|
94
|
+
|
|
95
|
+
点工作区行上的插头按钮,弹窗会显示:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
┌ MCP 与技能 ────────────────────────────────┐
|
|
99
|
+
│ 工作区 D:\ai-workspace 当前工作区 │
|
|
100
|
+
│ │
|
|
101
|
+
│ 项目 MCP(.mcp.json) │
|
|
102
|
+
│ ● codebase stdio 已启用 已连接 │
|
|
103
|
+
│ ○ e2e-runner stdio 已停用 未连接 │
|
|
104
|
+
│ │
|
|
105
|
+
│ 全局 MCP(~/.claude.json) │
|
|
106
|
+
│ ● everything-search http 已启用 已连接 │
|
|
107
|
+
│ │
|
|
108
|
+
│ 手动 MCP(profile 配置) │
|
|
109
|
+
│ ○ codegraph stdio 已停用 未连接 │
|
|
110
|
+
│ │
|
|
111
|
+
│ 项目技能(本工作区) │
|
|
112
|
+
│ ● officecli .dsh 已启用 │
|
|
113
|
+
│ ○ probe .claude 已停用 │
|
|
114
|
+
│ │
|
|
115
|
+
│ 全局技能(所有工作区) │
|
|
116
|
+
│ ● diagnose-windows-sandbox-acl ~/.dsh │
|
|
117
|
+
└───────────────────────────────────────────────┘
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **当前工作区**(弹窗顶部有提示):拨动开关**立即生效**——启用就连接进程并注册工具,
|
|
121
|
+
停用就断开并卸载工具。
|
|
122
|
+
- **非当前工作区**:开关只保存状态,等这个工作区有会话时才按状态挂载。
|
|
123
|
+
- 空分组不显示;某个工作区一个 MCP、一个技能都没有时,弹窗给出空状态提示。
|
|
124
|
+
- 项目 MCP 的名字会带工作区前缀:A 工作区里的 `codebase` 在运行时叫 `a__codebase`,
|
|
125
|
+
模型看到的工具名是 `mcp__a__codebase__search_code`。这样两个工作区的同名服务器可以
|
|
126
|
+
以各自独立的进程共存,而不是互相顶掉。
|
|
127
|
+
|
|
128
|
+
## 数据与状态
|
|
129
|
+
|
|
130
|
+
状态文件是 `$DSH_HOME/mcp-manager/state.json`(`$DSH_HOME` 默认 `~/.dsh`):
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"projects": [
|
|
135
|
+
{
|
|
136
|
+
"path": "D:\\ai-workspace",
|
|
137
|
+
"disabled": ["dsh-mcp-manager__codebase"],
|
|
138
|
+
"disabledSkills": ["office"]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| 字段 | 含义 |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `path` | 工作区绝对路径(身份键) |
|
|
147
|
+
| `disabled` | 该工作区停用的 MCP 运行名列表;项目服务器写工作区限定名(`<slug>__<name>`),全局/手动服务器写原名 |
|
|
148
|
+
| `disabledSkills` | 该工作区停用的技能名列表 |
|
|
149
|
+
|
|
150
|
+
- 首次启动会自动从旧的 `$DSH_HOME/settings.yaml` 的 `mcp:` 段导入一次,无需手工迁移。
|
|
151
|
+
- 停用名单里同时认限定名与旧 plain 名(`codebase`),所以老配置不会失效。
|
|
152
|
+
- 手改这个文件后,重启 DSH 或重新打开弹窗即生效。
|
|
153
|
+
|
|
154
|
+
### HTTP API
|
|
155
|
+
|
|
156
|
+
弹窗只用这三个接口,也可以自己调用(同源,无鉴权):
|
|
157
|
+
|
|
158
|
+
| 方法 | 路径 | 用途 |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `GET` | `/api/dsh-mcp-manager/inventory?active=<工作区路径>` | 该工作区的完整清单(MCP 分组 + 技能 + 连接状态) |
|
|
161
|
+
| `POST` | `/api/dsh-mcp-manager/activate` | `{ "path": "..." }`,记住当前聚焦的工作区 |
|
|
162
|
+
| `POST` | `/api/dsh-mcp-manager/set` | `{ "path": "...", "kind": "mcp"\|"skill", "name": "...", "enabled": true }` |
|
|
163
|
+
|
|
164
|
+
## 挂载语义(想改配置文件前先读这段)
|
|
165
|
+
|
|
166
|
+
- **并集模型**:挂载集合 = 所有「有存活会话」的工作区各自想要的服务器之并集。
|
|
167
|
+
没有任何存活会话时,退化为「当前聚焦工作区」的集合。
|
|
168
|
+
- **进程粒度**:项目服务器每个(工作区, 服务器)一个进程,`cwd` 默认是该工作区目录,
|
|
169
|
+
所以 `.codebase/codebase.db` 这类相对路径各自落到自己的工作区。
|
|
170
|
+
- **全局服务器**:只要任一存活工作区启用,就挂一个共享进程。
|
|
171
|
+
- **手动服务器**(profile 里手写的 `dsh-mcp-client` 行):只要任一存活工作区需要就保持启用,
|
|
172
|
+
因为一个 loader 行只能有一个进程。
|
|
173
|
+
- **卸载时机**:某工作区最后一个会话关闭后,它专属的服务器进程被卸载;
|
|
174
|
+
卸载后会按命令行特征清扫可能残留的子进程。
|
|
175
|
+
- **孤儿进程**:托管服务器被停用/卸载时,插件会用启动命令的特征串找并杀掉残留子进程,
|
|
176
|
+
避免 stdio 服务器变成野进程。
|
|
177
|
+
|
|
178
|
+
## 技能
|
|
179
|
+
|
|
180
|
+
- 技能根按以下优先级合并(**rank 小者赢**,同名技能只出现一次):
|
|
181
|
+
|
|
182
|
+
| 顺序 | 根 | 来源标记 |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| 1 | `<工作区>/.dsh/skills` | `project-dsh` |
|
|
185
|
+
| 2 | `<工作区>/.claude/skills` | `project-claude` |
|
|
186
|
+
| 3 | `<工作区>/.agents/skills` | `project-agents` |
|
|
187
|
+
| 4 | `~/.dsh/skills` | `user-dsh` |
|
|
188
|
+
| 5 | `~/.claude/skills` | `user-claude` |
|
|
189
|
+
| 6 | `~/.agents/skills` | `user-agents` |
|
|
190
|
+
|
|
191
|
+
`.claude` 与 `.agents` 这两组是 DSH 默认不认的(DSH 只认 `.dsh` 与 `.agents`),
|
|
192
|
+
由本插件通过 `ctx.skills.registerProvider()` 补上,不 patch 任何核心包。
|
|
193
|
+
- **技能目录**里每个技能是一层目录下的 `SKILL.md`,或根目录下的单个 `.md` 文件;
|
|
194
|
+
没有 frontmatter 时用目录名作名字、正文第一行作描述(Claude Code 的常见写法)。
|
|
195
|
+
- **停用技能的三重生效点**:会话技能目录不再列出、`/技能名` 不会被注入、
|
|
196
|
+
`skill` 工具调用被拒绝并返回「该技能在此工作区已停用」。
|
|
197
|
+
|
|
198
|
+
## 兼容性
|
|
199
|
+
|
|
200
|
+
| 项目 | 要求 |
|
|
201
|
+
|---|---|
|
|
202
|
+
| DSH | `>=0.2.0-rc.2 <0.3.0`(安装时按运行时版本校验,不匹配会拒绝安装) |
|
|
203
|
+
| Node.js | `>=22` |
|
|
204
|
+
| 界面 | 仅 Web(`dsh web` 与桌面版 GUI 的 Web 视图);`headless` / `acp` 无 UI 入口 |
|
|
205
|
+
|
|
206
|
+
运行时依赖只有 DSH 安装目录自带的包(`@deepseek-ai/dsh-mcp-client` 等),
|
|
207
|
+
插件本身没有 npm 运行时依赖。
|
|
208
|
+
|
|
209
|
+
## 已知限制
|
|
210
|
+
|
|
211
|
+
- **技能文件没有 watcher** — DSH 运行期间新增到 `.claude/skills` 的技能,要重启才会出现
|
|
212
|
+
(已有技能与项目技能在启动时全部加载)。
|
|
213
|
+
- **工作区按钮是 DOM 注入的** — DSH 0.2.x 只为「会话行」声明了 slot,工作区行没有,
|
|
214
|
+
所以按钮通过 `MutationObserver` 注入并在重渲染后补回。按钮若在某些主题/布局下没出现,
|
|
215
|
+
先确认 DSH 版本是否仍在 `>=0.2.0-rc.2 <0.3.0` 区间。
|
|
216
|
+
- **技能过滤是原型补丁** — 它影响所有访问技能服务的消费者(比如技能选择器 UI)。
|
|
217
|
+
这是有意为之:开关本身就是按工作区的,而每次查询都带 `cwd`。
|
|
218
|
+
- **状态不在 DSH 设置页** — DSH 0.2.x 不再允许插件注册任意设置命名空间,
|
|
219
|
+
所以开关状态存在自己的 `state.json` 里,不出现在设置页。
|
|
220
|
+
- **切换语义** — 「非当前工作区」的开关只保存状态,不会立刻拉起进程;这是为了避免
|
|
221
|
+
给没在用的工作区白开进程。
|
|
222
|
+
|
|
223
|
+
## 安全
|
|
224
|
+
|
|
225
|
+
- 插件会拉起项目 `.mcp.json` 与 `~/.claude.json` 里声明的 **stdio 命令**,
|
|
226
|
+
等同于把代码执行权交给这些配置。只在你信任的仓库/配置里使用。
|
|
227
|
+
- 三个 HTTP 接口**没有鉴权**,设计前提是 `dsh web` 只监听 loopback(默认 `127.0.0.1`)。
|
|
228
|
+
用 `--host 0.0.0.0` 暴露到网络时,任何能访问该端口的人都能开关你的 MCP 服务器。
|
|
229
|
+
- 插件不改 DSH 安装目录,不写 `settings.yaml`,只写 `$DSH_HOME/mcp-manager/state.json`。
|
|
230
|
+
|
|
231
|
+
## 开发
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
node --check lib/index.js # host:清单收集、按工作区状态、并集挂载、路由
|
|
235
|
+
node --check lib/skill-filter.js # 技能过滤(SkillRegistry 原型 + tools.guard)
|
|
236
|
+
node --check lib/skill-roots.js # Claude 技能根 provider
|
|
237
|
+
node --check client/client.js # 浏览器 bundle(ModuleLoader factory 形式)
|
|
238
|
+
|
|
239
|
+
npm run pack:check # 校验 npm 包内容(发布前必跑)
|
|
240
|
+
npm run market:index # 生成 docs/index.json 插件市场索引
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| 路径 | 作用 |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `lib/index.js` | Host 主逻辑:状态、`inventory` / `activate` / `set`、并集挂载、会话工具过滤 |
|
|
246
|
+
| `lib/skill-filter.js` | 按工作区过滤技能(目录 / 注入 / 工具调用三处) |
|
|
247
|
+
| `lib/skill-roots.js` | `.claude` 技能根 + 无 frontmatter 的 `SKILL.md` 解析 |
|
|
248
|
+
| `client/client.js` | 浏览器半:工作区行按钮 + 弹窗(中英双语) |
|
|
249
|
+
| `cordis.patch.yml` | bundle 补丁:把插件行插进 profile 组合 |
|
|
250
|
+
| `locale/{en,zh}.json` | 插件页显示用的标题/简介 |
|
|
251
|
+
| `docs/index.json` | 插件市场索引(format 1),托管在 GitHub Pages |
|
|
252
|
+
| `docs/PUBLISHING.md` | 发布流程(npm + GitHub + 市场索引) |
|
|
253
|
+
|
|
254
|
+
浏览器半是**手写的 ModuleLoader bundle**(`window.__ModuleLoader__.load({ factory })`),
|
|
255
|
+
没有构建步骤;改完直接刷新页面即可。
|
|
256
|
+
|
|
257
|
+
## 发布
|
|
258
|
+
|
|
259
|
+
见 [docs/PUBLISHING.md](docs/PUBLISHING.md)。
|
|
260
|
+
|
|
261
|
+
## 许可证
|
|
262
|
+
|
|
263
|
+
[MIT](LICENSE)
|