@xxxyz/dsh-mcp-manager 2.0.6 → 2.1.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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <div align="center">
5
5
  <b style="font-size: 1.15em;">DeepSeek Harness 的 MCP 服务管理器:装没装、连没连、一页管完。</b><br /><br />
6
6
  <code>服务器列表</code> <code>新增 / 编辑 / 删除</code> <code>启用 / 停用</code> <code>重启</code> <code>工具数健康</code> <code>JSON 导出 / 导入</code><br />
7
- <code>4 个模型工具</code> <code>HTTP API</code> <code>npx / npm / dsh plugin / 脚本</code><br /><br />
7
+ <code>4 个模型工具</code> <code>HTTP API</code> <code>dsh plugin 一条命令</code><br /><br />
8
8
  <b>设置 → MCP 管理</b> 管理项目级与全局 <code>cordis.patch.yml</code> 中的 <code>@deepseek-ai/dsh-mcp-client</code> 行——<br />
9
9
  无需再手改配置文件,所有修改即改即生效(HMR 热应用),重启、升级后依然存在。
10
10
  </div>
@@ -15,9 +15,15 @@
15
15
  [![License](https://img.shields.io/github/license/xxxyz/DeepSeekHarness-MCP-Manager?color=blue)](LICENSE)
16
16
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js)](package.json)
17
17
  [![GitHub](https://img.shields.io/badge/GitHub-xxxyz%2FDeepSeekHarness--MCP--Manager-181717?logo=github)](https://github.com/xxxyz/DeepSeekHarness-MCP-Manager)
18
+ [![dsh.market](https://img.shields.io/badge/dsh.market-%E2%9C%93-3fb950)](https://dsh.market)
19
+ [![awesome-dsh-plugin](https://img.shields.io/badge/awesome--dsh--plugin-%E5%BE%85%E5%90%88%E5%B9%B6-ffd93d)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/2078)
18
20
 
19
21
  </div>
20
22
 
23
+ <div align="center">
24
+ 🛒 已收录于 <a href="https://dsh.market"><b>dsh.market</b></a> · 已提交 <a href="https://awesome-dsh-plugin.com"><b>awesome-dsh-plugin.com</b></a> 官方列表收录(<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/2078"><b>PR #2078</b></a> 待合并)
25
+ </div>
26
+
21
27
  <div align="center">
22
28
  🌏 <a href="./README.md"><b>中文</b></a> · <a href="./README_EN.md">English</a>
23
29
  </div>
@@ -36,110 +42,64 @@
36
42
  - **🩺 健康检查**:每台服务器实时工具数与 loader 阶段,异常一目了然
37
43
  - **📦 备份 / 恢复**:JSON 导出 / 导入,合并新增、已存在自动跳过
38
44
  - **🤖 模型工具**:宿主注册 4 个 `mcp_manager_*` 工具,模型可直接查询与操作 MCP 服务
39
- - **🌐 HTTP API**:`POST /dsh-mcp-manager/api`(JSON `{op, args}` → `{ok, ...}`),供客户端与脚本调用
40
- - **📦 跨平台安装**:Windows / macOS / Linux 一条命令(npx / npm / `dsh plugin` / 脚本)
45
+ - **🌐 HTTP API**:`POST /dsh-mcp-manager/api`(JSON `{op, args}` → `{ok, ...}`),供客户端与脚本调用。带跨站(CSRF)防护:仅接受 POST、必须携带 `x-dsh-plugin: dsh-mcp-manager` 请求头、校验同源 Origin(curl 等本地脚本无需 Origin)
46
+ - **📦 一键安装**:`dsh plugin --profile web add` 一条命令装包 + 自动挂载(Windows / macOS / Linux)
41
47
 
42
48
  ## 🚀 安装
43
49
 
44
- **前置**:DSH 已装好(`dsh web` 能正常运行),Node.js ≥ 18。
50
+ **前置**:已装好 DSH(`dsh web` 能正常运行),Node.js ≥ 18、pnpm ≥ 9
45
51
 
46
- ### 方式一 · npx 一条命令(推荐)
47
-
48
- ```sh
49
- npx -y @xxxyz/dsh-mcp-manager
50
- ```
52
+ ### 方式一 · dsh 命令安装(推荐)
51
53
 
52
- 所有参数照常透传:`npx -y @xxxyz/dsh-mcp-manager --dsh-home /path/.dsh --profile web --repair --port 3080`。
53
-
54
- ### 方式二 · npm 全局安装(适合经常使用)
54
+ 一条命令装包 + **自动挂载**(`dsh.bundle.patch` 机制,无需手动改任何配置文件):
55
55
 
56
56
  ```sh
57
- npm i -g @xxxyz/dsh-mcp-manager
58
- dsh-mcp-manager # 安装插件
59
- dsh-mcp-manager-uninstall # 卸载插件
60
- npm i -g @xxxyz/dsh-mcp-manager@latest # 升级
57
+ dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
61
58
  ```
62
59
 
63
- ### 方式三 · dsh 命令安装(bundle 方式)
60
+ 装完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可看到 **设置 MCP 管理**(DSH 对 client 改动热加载,无需重启;仅 host 半更新时需要重启)。
64
61
 
65
- ```sh
66
- dsh plugin --profile web add @xxxyz/dsh-mcp-manager
67
- # 或 GitHub 源(构建产物 lib/ 已入库,无需本地构建)
68
- dsh plugin --profile web add github:xxxyz/DeepSeekHarness-MCP-Manager
69
- ```
62
+ ### 方式二 · 让 DSH 自己装
70
63
 
71
- ### 方式四 · 免 npm 账号:GitHub 直拉
64
+ 把下面这段提示词发给任意一个 DSH 会话:
72
65
 
73
- ```sh
74
- npx -y github:xxxyz/DeepSeekHarness-MCP-Manager
66
+ ```text
67
+ 帮我安装 dsh-mcp-manager 插件(DSH MCP 服务管理器),步骤:
68
+ 1. 执行 dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
69
+ 2. 完成后提醒我硬刷新浏览器(Cmd/Ctrl+Shift+R)
70
+ 遇到报错先查 https://github.com/xxxyz/DeepSeekHarness-MCP-Manager README 的常见问题表。
75
71
  ```
76
72
 
77
- <details>
78
- <summary><b>脚本安装</b>(源码方式:下载仓库后执行,幂等)</summary>
79
-
80
- **Windows(PowerShell)**:
81
-
82
- ```powershell
83
- .\dsh-mcp-manager\install.ps1 # 默认 ~/.dsh + web profile
84
- .\dsh-mcp-manager\install.ps1 -DshHome D:\path\.dsh -Profile web
85
- ```
86
-
87
- **macOS / Linux(bash)**(无执行权限先 `chmod +x dsh-mcp-manager/install.sh`):
88
-
89
- ```sh
90
- ./dsh-mcp-manager/install.sh # 默认 ~/.dsh + web profile
91
- ./dsh-mcp-manager/install.sh --dsh-home /path/.dsh --profile web
92
- ```
93
-
94
- **任何平台直接运行**:
73
+ **更新**
95
74
 
96
75
  ```sh
97
- node dsh-mcp-manager/install.mjs [--dsh-home <path>] [--profile <name>] [--port <n>] [--repair] [--skip-patch]
76
+ dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
98
77
  ```
99
78
 
100
- 安装脚本会:① 复制到 `local-packages/`(真源备份,DSH 升级不动它)② 复制到 `profiles/node_modules/`(普通复制而非软链接,保证 ESM 能解析 `@deepseek-ai/dsh-tools`)③ 幂等追加 loader 行(insert 块)。
101
-
102
- </details>
103
-
104
- 装完**硬刷新浏览器**(Cmd/Ctrl+Shift+R),打开 **设置 → MCP 管理** 即可看到管理页。若未出现,重启一次 DSH(host 半首次挂载需要)。
79
+ 也可把 `~/.dsh/profiles/web/package.json` 里的版本号改高后 `pnpm install`。改完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可(client 改动无需重启 DSH)。
105
80
 
106
81
  <details>
107
- <summary><b>卸载</b></summary>
108
-
109
- ```sh
110
- dsh-mcp-manager-uninstall # 若用 npm -g 安装
111
- # 或:.\uninstall.ps1 | ./uninstall.sh | node uninstall.mjs [--dsh-home <path>] [--profile <name>]
112
- ```
82
+ <summary><b>常见问题</b></summary>
113
83
 
114
- 然后重启 DSH。卸载删除部署副本、`local-packages` 真源,并清理 loader 行(补丁保持合法)。
84
+ | 现象 | 原因与解决 |
85
+ |---|---|
86
+ | 装完设置里没有「MCP 管理」 | 硬刷新(Cmd/Ctrl+Shift+R);仍没有就重启 DSH 一次。 |
87
+ | 页面出现**两个 MCP 页签 / 工具重复** | 双挂载:同时存在旧的 loader 行与新的 bundle 条目。删掉 `cordis.patch.yml` 里的旧 loader 行,或 `dsh.profile.bundles` 里的条目,重启 DSH。 |
88
+ | 之前用旧方式装过,现在想升级 | 新版 bundle 自带防双挂载 guard,直接 `dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest` 不会重复挂载。要切到新代码:删掉 `~/.dsh/profiles/web/cordis.patch.yml` 里的 `- id: mcp-manager` 行,再删 `local-packages/dsh-mcp-manager` 与 `profiles/node_modules/dsh-mcp-manager` 两个副本,重启 DSH。 |
89
+ | 提示 `dsh: command not found` | 先安装 DSH;或直接用 `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest`。 |
90
+ | `npm view` 报 404 | 国内镜像(npmmirror)同步有延迟:加 `--registry=https://registry.npmjs.org` 或稍等再试。 |
91
+ | 修改配置后未生效 | 所有修改走 HMR 热应用,等 1–2 秒自动刷新;页面会自动轮询。 |
115
92
 
116
93
  </details>
117
94
 
118
95
  <details>
119
- <summary><b>DSH 升级后:--repair</b></summary>
120
-
121
- DSH 升级(或 HMR 状态异常)后若 **设置里没有"MCP 管理"** 或 **`mcp_manager_*` 工具消失**,运行一次修复命令:重新部署 → 递增 loader 行 `config.version`(触发 HMR 重应用)→ 轮询 API 直到 `{ok:true}`(默认 30 秒)。
96
+ <summary><b>卸载</b></summary>
122
97
 
123
98
  ```sh
124
- node dsh-mcp-manager/install.mjs --repair --port 3080
125
- # PowerShell: .\install.ps1 -Repair -Port 3080 bash: ./install.sh --repair --port 3080
99
+ dsh plugin --profile web remove @xxxyz/dsh-mcp-manager
126
100
  ```
127
101
 
128
- 仍不恢复则重启一次 DSH(loader 启动时重新导入)。
129
-
130
- </details>
131
-
132
- <details>
133
- <summary><b>常见问题</b></summary>
134
-
135
- | 现象 | 原因与解决 |
136
- |---|---|
137
- | 装完设置里没有"MCP 管理" | 硬刷新(Cmd/Ctrl+Shift+R);仍没有就重启 DSH 一次。 |
138
- | 页面出现**两个 MCP 页签 / 工具重复** | 双挂载:同时用了 install.mjs 与 `dsh plugin add` 两种方式。卸载其中一种(`dsh-mcp-manager-uninstall` 或删掉对应的 loader 行 / `dsh.profile.bundles` 条目)。 |
139
- | DSH 升级后工具消失 | 跑一次 `--repair`(见上)。 |
140
- | `npx` / `npm view` 报 404 | 国内镜像(npmmirror)同步有延迟:加 `--registry=https://registry.npmjs.org` 或稍等再试。 |
141
- | 安装脚本报错 `EPERM` | DSH 正在运行占用了文件:先退出 DSH 再装。 |
142
- | 修改配置后未生效 | 所有修改走 HMR 热应用,等 1–2 秒自动刷新;页面会自动轮询。 |
102
+ 然后重启 DSH
143
103
 
144
104
  </details>
145
105
 
@@ -158,24 +118,23 @@ node dsh-mcp-manager/install.mjs --repair --port 3080
158
118
 
159
119
  | 字段 | 说明 |
160
120
  |---|---|
161
- | `version` | loader 行 `config.version`。`--repair` 会将其递增以强制 HMR 重应用,无需手动修改。 |
121
+ | `version` | loader 行 `config.version`,仅用于触发 HMR 重应用;官方通道安装下由 bundle 自动管理,无需手动修改。 |
162
122
 
163
123
  loader 行必须为 **`insert` 块**形式(DSH patch 方言中普通 `- id:` 行只是对已存在条目的覆盖,无法新增插件):
164
124
 
165
125
  ```yaml
166
126
  - insert:
167
127
  - id: dsh-mcp-manager
168
- name: dsh-mcp-manager
169
- config:
170
- version: 1
128
+ name: '@xxxyz/dsh-mcp-manager'
171
129
  ```
172
130
 
131
+ > 无需手动写这行——`dsh plugin add` 的 bundle patch 会自动插入(见 `cordis.patch.yml`)。
132
+
173
133
  ## 🏗️ 架构
174
134
 
175
135
  - **宿主端**(`src/index.ts` → `lib/index.js`,对象形态 Cordis 插件 `{name, inject, apply}`):`inject` 声明 `timer/fs/settings/sandboxPolicy/webServer/tools`,框架保证就绪并在依赖消失时自动重载——这是插件跨 DSH 升级存活的机制。注册 4 个模型工具(`ctx.tools.register(defineTool(...))`)与精确路由 `POST /dsh-mcp-manager/api`(`ctx.effect` 作用域化清理);对 `cordis.patch.yml` 做行级 CRUD(迷你 YAML 解析 + 按文件写锁)。
176
136
  - **浏览器端**(`lib/client.js`,ModuleLoader CJS bundle):注册 设置 → MCP 管理 页(`settings.section` 槽位,order 16),经同源 `fetch('/dsh-mcp-manager/api')` 与宿主通信,不直接访问文件系统。
177
- - **loader 行**:写入 profile`cordis.patch.yml`,client-modules 服务扫描启用的条目并下发客户端 bundle。
178
- - **安装器**:`install.mjs`(跨平台核心)/ `install.ps1` / `install.sh` + 对应的 `uninstall.*`;npm 包 `@xxxyz/dsh-mcp-manager` 的 bin 直接执行安装器(`dsh-mcp-manager` / `dsh-mcp-manager-uninstall`)。
137
+ - **loader 行**:由 `dsh plugin add` bundle patch 自动插入,client-modules 服务扫描启用的条目并下发客户端 bundle。
179
138
 
180
139
  ## 🛠️ 开发
181
140
 
package/README_EN.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <div align="center">
5
5
  <b style="font-size: 1.15em;">Manage every MCP server in DeepSeek Harness from one settings page — install, configure, monitor.</b><br /><br />
6
6
  <code>server list</code> <code>add / edit / delete</code> <code>enable / disable</code> <code>restart</code> <code>tool-count health</code> <code>JSON export / import</code><br />
7
- <code>4 model tools</code> <code>HTTP API</code> <code>npx / npm / dsh plugin / scripts</code><br /><br />
7
+ <code>4 model tools</code> <code>HTTP API</code> <code>dsh plugin one-command</code><br /><br />
8
8
  <b>Settings → MCP 管理</b> manages <code>@deepseek-ai/dsh-mcp-client</code> rows in your project-level and
9
9
  global <code>cordis.patch.yml</code> — no hand-editing, every change applies live via HMR, survives restarts and upgrades.
10
10
  </div>
@@ -15,9 +15,15 @@
15
15
  [![License](https://img.shields.io/github/license/xxxyz/DeepSeekHarness-MCP-Manager?color=blue)](LICENSE)
16
16
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js)](package.json)
17
17
  [![GitHub](https://img.shields.io/badge/GitHub-xxxyz%2FDeepSeekHarness--MCP--Manager-181717?logo=github)](https://github.com/xxxyz/DeepSeekHarness-MCP-Manager)
18
+ [![dsh.market](https://img.shields.io/badge/dsh.market-%E2%9C%93-3fb950)](https://dsh.market)
19
+ [![awesome-dsh-plugin](https://img.shields.io/badge/awesome--dsh--plugin-pending-ffd93d)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/2078)
18
20
 
19
21
  </div>
20
22
 
23
+ <div align="center">
24
+ 🛒 Listed on <a href="https://dsh.market"><b>dsh.market</b></a> · submitted to <a href="https://awesome-dsh-plugin.com"><b>awesome-dsh-plugin.com</b></a> (<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/2078"><b>PR #2078</b></a> pending merge)
25
+ </div>
26
+
21
27
  <div align="center">
22
28
  🌏 <a href="./README.md">中文</a> · <a href="./README_EN.md"><b>English</b></a>
23
29
  </div>
@@ -36,110 +42,64 @@
36
42
  - **🩺 Health check** — live tool counts and loader phase per server, problems visible at a glance
37
43
  - **📦 Backup / Restore** — JSON export / import; merges new entries, skips existing ones
38
44
  - **🤖 Model tools** — four `mcp_manager_*` tools registered on the host, so the model can query and manage MCP servers directly
39
- - **🌐 HTTP API** — `POST /dsh-mcp-manager/api` (JSON `{op, args}` → `{ok, ...}`) for the client and scripts
40
- - **📦 Cross-platform install** — one command on Windows / macOS / Linux (npx / npm / `dsh plugin` / scripts)
45
+ - **🌐 HTTP API** — `POST /dsh-mcp-manager/api` (JSON `{op, args}` → `{ok, ...}`) for the client and scripts. Cross-site (CSRF) protected: POST-only, requires the `x-dsh-plugin: dsh-mcp-manager` request header, and checks the Origin is same-origin (local scripts without an Origin are fine)
46
+ - **📦 One-command install** — `dsh plugin --profile web add` installs and mounts automatically (Windows / macOS / Linux)
41
47
 
42
48
  ## 🚀 Install
43
49
 
44
- **Prerequisite**: DSH installed and running (`dsh web` works), Node.js ≥ 18.
45
-
46
- ### Method 1 · One-shot npx (recommended)
47
-
48
- ```sh
49
- npx -y @xxxyz/dsh-mcp-manager
50
- ```
51
-
52
- All flags pass through: `npx -y @xxxyz/dsh-mcp-manager --dsh-home /path/.dsh --profile web --repair --port 3080`.
53
-
54
- ### Method 2 · Global npm install (for frequent use)
55
-
56
- ```sh
57
- npm i -g @xxxyz/dsh-mcp-manager
58
- dsh-mcp-manager # install the plugin
59
- dsh-mcp-manager-uninstall # uninstall the plugin
60
- npm i -g @xxxyz/dsh-mcp-manager@latest # upgrade
61
- ```
62
-
63
- ### Method 3 · dsh command (bundle)
50
+ **Prerequisite**: DSH installed and running (`dsh web` works), Node.js ≥ 18, pnpm ≥ 9.
64
51
 
65
- ```sh
66
- dsh plugin --profile web add @xxxyz/dsh-mcp-manager
67
- # or from GitHub (build artifacts are committed, no local build needed)
68
- dsh plugin --profile web add github:xxxyz/DeepSeekHarness-MCP-Manager
69
- ```
52
+ ### Method 1 · dsh command (recommended)
70
53
 
71
- ### Method 4 · No npm account: straight from GitHub
54
+ One command installs the package and **auto-mounts** it (the `dsh.bundle.patch` mechanism — no manual config file edits needed):
72
55
 
73
56
  ```sh
74
- npx -y github:xxxyz/DeepSeekHarness-MCP-Manager
57
+ dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
75
58
  ```
76
59
 
77
- <details>
78
- <summary><b>Script install</b> (source checkout; idempotent)</summary>
60
+ After installing, **hard-refresh the browser** (Cmd/Ctrl+Shift+R) and open **Settings → MCP 管理** (DSH hot-reloads client changes; a full restart is only needed for host-half changes).
79
61
 
80
- **Windows (PowerShell)**:
62
+ ### Method 2 · Let DSH install it
81
63
 
82
- ```powershell
83
- .\dsh-mcp-manager\install.ps1 # default: ~/.dsh, web profile
84
- .\dsh-mcp-manager\install.ps1 -DshHome D:\path\.dsh -Profile web
85
- ```
64
+ Paste this prompt into any DSH conversation:
86
65
 
87
- **macOS / Linux (bash)** (run `chmod +x dsh-mcp-manager/install.sh` if needed):
88
-
89
- ```sh
90
- ./dsh-mcp-manager/install.sh # default: ~/.dsh, web profile
91
- ./dsh-mcp-manager/install.sh --dsh-home /path/.dsh --profile web
66
+ ```text
67
+ Install the dsh-mcp-manager plugin (DSH MCP server manager):
68
+ 1. Run dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
69
+ 2. When done, remind me to hard-refresh the browser (Cmd/Ctrl+Shift+R)
70
+ If you hit an error, check https://github.com/xxxyz/DeepSeekHarness-MCP-Manager README's FAQ table.
92
71
  ```
93
72
 
94
- **Any platform (direct)**:
73
+ **Update**
95
74
 
96
75
  ```sh
97
- node dsh-mcp-manager/install.mjs [--dsh-home <path>] [--profile <name>] [--port <n>] [--repair] [--skip-patch]
76
+ dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
98
77
  ```
99
78
 
100
- The installer copies the package to `local-packages/` (source of record, untouched by DSH upgrades) copies it into `profiles/node_modules/` (a plain copy, not a symlink, so ESM resolves `@deepseek-ai/dsh-tools`) appends the loader row (an `insert` block, idempotent).
101
-
102
- </details>
103
-
104
- After installing, **hard-refresh the browser** (Cmd/Ctrl+Shift+R) and open **Settings → MCP 管理**. If the page is missing, restart DSH once (the host half needs a first mount).
79
+ Or bump the version in `~/.dsh/profiles/web/package.json` and run `pnpm install`. Hard-refresh the browser afterwards (Cmd/Ctrl+Shift+R) client changes are hot-reloaded; only host-half changes need a restart.
105
80
 
106
81
  <details>
107
- <summary><b>Uninstall</b></summary>
108
-
109
- ```sh
110
- dsh-mcp-manager-uninstall # if installed via npm -g
111
- # or: .\uninstall.ps1 | ./uninstall.sh | node uninstall.mjs [--dsh-home <path>] [--profile <name>]
112
- ```
82
+ <summary><b>FAQ</b></summary>
113
83
 
114
- Then restart DSH. Uninstall removes the deployed copy, the `local-packages` source, and the loader row (the patch stays a valid array).
84
+ | Symptom | Cause & fix |
85
+ |---|---|
86
+ | No "MCP 管理" in Settings after install | Hard-refresh (Cmd/Ctrl+Shift+R); if still missing, restart DSH once. |
87
+ | **Two MCP tabs / duplicated tools** | Double mount: both a legacy loader row and the new bundle entry exist. Delete the old loader row from `cordis.patch.yml`, or the `dsh.profile.bundles` entry, then restart DSH. |
88
+ | Upgrading from a legacy script install | The new bundle has a built-in double-mount guard — `dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest` won't crash. To actually switch to the new code: remove the old `- id: mcp-manager` row from `~/.dsh/profiles/web/cordis.patch.yml`, delete `local-packages/dsh-mcp-manager` and `profiles/node_modules/dsh-mcp-manager`, then restart DSH. |
89
+ | `dsh: command not found` | Install DSH first, or use `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest`. |
90
+ | `npm view` reports 404 | Local mirror (npmmirror) sync lag: add `--registry=https://registry.npmjs.org` or wait a moment. |
91
+ | Config changes don't take effect | All changes apply via HMR within 1–2 s; the page auto-polls. |
115
92
 
116
93
  </details>
117
94
 
118
95
  <details>
119
- <summary><b>After a DSH upgrade: --repair</b></summary>
120
-
121
- If a DSH upgrade (or a broken HMR state) leaves the settings page or the `mcp_manager_*` tools missing, run one repair command: redeploy → bump the loader row's `config.version` (forces an HMR re-apply) → poll the API until `{ok:true}` (default 30 s).
96
+ <summary><b>Uninstall</b></summary>
122
97
 
123
98
  ```sh
124
- node dsh-mcp-manager/install.mjs --repair --port 3080
125
- # PowerShell: .\install.ps1 -Repair -Port 3080 bash: ./install.sh --repair --port 3080
99
+ dsh plugin --profile web remove @xxxyz/dsh-mcp-manager
126
100
  ```
127
101
 
128
- If the API still does not answer, restart DSH once (the loader re-imports at boot).
129
-
130
- </details>
131
-
132
- <details>
133
- <summary><b>FAQ</b></summary>
134
-
135
- | Symptom | Cause & fix |
136
- |---|---|
137
- | No "MCP 管理" in Settings after install | Hard-refresh (Cmd/Ctrl+Shift+R); if still missing, restart DSH once. |
138
- | **Two MCP tabs / duplicated tools** | Double mount: both install.mjs and `dsh plugin add` were used. Remove one (run `dsh-mcp-manager-uninstall`, or delete the extra loader row / `dsh.profile.bundles` entry). |
139
- | Tools missing after a DSH upgrade | Run `--repair` (see above). |
140
- | `npx` / `npm view` reports 404 | Local mirror (npmmirror) sync lag: add `--registry=https://registry.npmjs.org` or wait a moment. |
141
- | Installer fails with `EPERM` | DSH is running and holds the files: quit DSH before installing. |
142
- | Config changes don't take effect | All changes apply via HMR within 1–2 s; the page auto-polls. |
102
+ Then restart DSH.
143
103
 
144
104
  </details>
145
105
 
@@ -158,24 +118,23 @@ Configuration of the plugin itself on its loader row:
158
118
 
159
119
  | Field | Description |
160
120
  |---|---|
161
- | `version` | The loader row's `config.version`. `--repair` increments it to force an HMR re-apply; no manual edits needed. |
121
+ | `version` | The loader row's `config.version`, only used to trigger an HMR re-apply; auto-managed by the bundle channel — no manual edits needed. |
162
122
 
163
123
  The loader row must be an **`insert` block** (in DSH's patch dialect a plain `- id:` row only overrides existing entries and can never add a new plugin):
164
124
 
165
125
  ```yaml
166
126
  - insert:
167
127
  - id: dsh-mcp-manager
168
- name: dsh-mcp-manager
169
- config:
170
- version: 1
128
+ name: '@xxxyz/dsh-mcp-manager'
171
129
  ```
172
130
 
131
+ > You don't need to write this row manually — `dsh plugin add` inserts it automatically via the bundle patch (see `cordis.patch.yml`).
132
+
173
133
  ## 🏗️ Architecture
174
134
 
175
135
  - **Host half** (`src/index.ts` → `lib/index.js`, an object-form Cordis plugin `{name, inject, apply}`): `inject` declares `timer/fs/settings/sandboxPolicy/webServer/tools` — the framework guarantees they are ready and reloads the plugin if one disappears, which is how the plugin survives DSH upgrades. Registers four model tools (`ctx.tools.register(defineTool(...))`) and the exact route `POST /dsh-mcp-manager/api` (scoped cleanup via `ctx.effect`); line-level CRUD on `cordis.patch.yml` (mini YAML parser + per-file write lock).
176
136
  - **Browser half** (`lib/client.js`, ModuleLoader CJS bundle): registers the Settings → MCP 管理 page (`settings.section` slot, order 16) and talks to the host through same-origin `fetch('/dsh-mcp-manager/api')` — it never touches the filesystem directly.
177
- - **Loader row**: written into the profile's `cordis.patch.yml`; the client-modules service scans enabled entries and serves the client bundle.
178
- - **Installers**: `install.mjs` (cross-platform core) / `install.ps1` / `install.sh`, plus matching `uninstall.*`; the npm package `@xxxyz/dsh-mcp-manager` exposes them as bins (`dsh-mcp-manager` / `dsh-mcp-manager-uninstall`).
137
+ - **Loader row**: inserted automatically by `dsh plugin add`'s bundle patch; the client-modules service scans enabled entries and serves the client bundle.
179
138
 
180
139
  ## 🛠️ Development
181
140
 
package/cordis.patch.yml CHANGED
@@ -1,7 +1,32 @@
1
- # Bundle patch layer for `dsh plugin add` installs: pnpm installs the scoped
2
- # package as @xxxyz/dsh-mcp-manager, so the insert row names the scoped
3
- # package. The loader-entry installer (install.mjs) mounts the same plugin
4
- # under `dsh-mcp-manager` — both names are registered by the client bundle.
1
+ # Bundle patch layer for the official CLI install:
2
+ #
3
+ # dsh plugin --profile web add @xxxyz/dsh-mcp-manager@latest
4
+ #
5
+ # `dsh plugin add` reconciles `dsh.profile.bundles` against installed packages
6
+ # and, seeing the `dsh.bundle.patch` declaration in package.json, appends
7
+ # `@xxxyz/dsh-mcp-manager` to the bundle stack. The profile boot then merges
8
+ # THIS patch (a single `insert` of the plugin row). No profile file edits
9
+ # needed — one command installs and mounts.
10
+ #
11
+ # Double-mount guard: a legacy loader row (the removed install.mjs channel,
12
+ # id `mcp-manager`, name `dsh-mcp-manager`) or an aggregate bundle may already
13
+ # mount this plugin under a different entry id. Two mounts both register the
14
+ # /dsh-mcp-manager/api route and fail the whole plugin tree at boot
15
+ # ("duplicate prefix route"). The `!!js` disabled expression backs THIS row
16
+ # off when another *enabled* entry already mounts the plugin; the existing
17
+ # instance then owns the API. The loader evaluates the expression at entry
18
+ # activation — the full entry tree is composed by then, so user-layer
19
+ # (profile cordis.patch.yml) rows are visible too.
20
+ #
21
+ # ORDER MATTERS — do not move `!e.disabled` ahead of the id/name checks.
22
+ # `Entry.disabled` is an uncached getter that re-evaluates the expression,
23
+ # so touching `e.disabled` of THIS entry re-enters this very expression
24
+ # (infinite recursion → "Maximum call stack size exceeded" at boot). The
25
+ # `id !==` check short-circuits on our own row and the `name` check
26
+ # short-circuits on every unrelated entry (e.g. dsh-better-sidebar's own
27
+ # guard), so `!e.disabled` is only ever read on a genuinely matching
28
+ # legacy/aggregate row (a plain boolean, no re-entry).
5
29
  - insert:
6
30
  - id: dsh-mcp-manager
7
31
  name: '@xxxyz/dsh-mcp-manager'
32
+ disabled: !!js "[...ctx.loader.entries()].some((e) => e.options.id !== 'dsh-mcp-manager' && (e.options.name === 'dsh-mcp-manager' || e.options.name === '@xxxyz/dsh-mcp-manager') && !e.disabled)"
package/lib/client.js CHANGED
@@ -47,7 +47,10 @@ const factory = (require) => {
47
47
  '.mcpm-dialog{background:color-mix(in srgb, currentColor 8%, transparent);backdrop-filter:blur(14px) saturate(1.2);border:1px solid rgba(128,128,128,.5);border-radius:10px;padding:16px 18px;min-width:300px;max-width:440px;box-shadow:0 10px 34px rgba(0,0,0,.35);color:inherit}' +
48
48
  '.mcpm-dialog-title{font-size:14px;font-weight:600;margin-bottom:8px}' +
49
49
  '.mcpm-dialog-body{font-size:12px;opacity:.88;line-height:1.7;margin-bottom:16px;word-break:break-all}' +
50
- '.mcpm-dialog-actions{display:flex;justify-content:flex-end;gap:8px}'
50
+ '.mcpm-dialog-actions{display:flex;justify-content:flex-end;gap:8px}' +
51
+ '.skm-search{font-size:12px;padding:5px 8px;border-radius:6px;border:1px solid rgba(128,128,128,.5);background:transparent;color:inherit;width:100%;box-sizing:border-box}' +
52
+ '.skm-group-title{font-size:12px;font-weight:600;opacity:.85;margin-top:4px;padding-bottom:2px;border-bottom:1px solid rgba(128,128,128,.25)}' +
53
+ '.skm-provider-title{font-size:12px;font-weight:600;cursor:pointer;user-select:none;margin-top:4px;opacity:.9}'
51
54
 
52
55
  function ensureCss() {
53
56
  if (typeof document === 'undefined') return
@@ -297,15 +300,97 @@ const factory = (require) => {
297
300
  { name: 'settings.section', id: 'mcp-manager', order: 16, label: 'MCP 管理' },
298
301
  () => React.createElement(MCPPage)
299
302
  ))
303
+
304
+ // Grouping: the registry's built-in providers are "filesystem"
305
+ // (project/user/custom/bundled roots) and "runtime" (register());
306
+ // any other provider name means a plugin-provided skill
307
+ // (e.g. superpowers-dsh). skmLevelOf is mutually exclusive.
308
+ function skmLevelOf(s) {
309
+ if (s.source === 'project-dsh' || s.source === 'project-agents') return '项目级'
310
+ if (s.source === 'runtime') return '运行时'
311
+ if (s.source === 'user-dsh' || s.source === 'user-agents') return '用户级'
312
+ if (s.source === 'bundled') return '内置'
313
+ if (s.source === 'custom' && s.provider === 'filesystem') return '自定义'
314
+ return '插件自带'
315
+ }
316
+ const SKM_GROUPS = ['项目级', '运行时', '自定义', '用户级', '内置', '插件自带']
317
+ function SkillPage() {
318
+ const [state, setState] = React.useState({ loading: true, error: null, skills: [] })
319
+ const [q, setQ] = React.useState('')
320
+ const [busy, setBusy] = React.useState(null)
321
+ const [msg, setMsg] = React.useState(null)
322
+ const [hidden, setHidden] = React.useState({})
323
+ const toggleGroup = (key) => setHidden((h) => ({ ...h, [key]: !h[key] }))
324
+ const refresh = () => {
325
+ apiCall('skill-list', {}).then((res) => {
326
+ setState({ loading: false, error: res && res.ok ? null : ((res && res.error) || '加载失败'), skills: (res && res.skills) || [] })
327
+ }).catch((e) => setState({ loading: false, error: String((e && e.message) || e), skills: [] }))
328
+ }
329
+ React.useEffect(() => { refresh() }, [])
330
+ React.useEffect(() => ctx.interval(() => refresh(), 5000), [])
331
+ React.useEffect(() => { if (!msg) return; return ctx.timeout(() => setMsg(null), 3000) }, [msg])
332
+ const toggle = (skill) => {
333
+ const enabled = skill.provider === 'dsh-mcp-manager-override'
334
+ setBusy(skill.name)
335
+ apiCall('skill-toggle', { name: skill.name, enabled }).then((res) => {
336
+ if (res && res.ok) { setMsg({ kind: 'ok', text: (enabled ? '已启用 ' : '已禁用 ') + skill.name }); refresh() }
337
+ else setMsg({ kind: 'err', text: (res && res.error) || '操作失败' })
338
+ }).catch((e) => setMsg({ kind: 'err', text: String((e && e.message) || e) })).then(() => setBusy(null))
339
+ }
340
+ const query = q.trim().toLowerCase()
341
+ const visible = state.skills.filter((s) => !query || (s.name + ' ' + (s.description || '')).toLowerCase().includes(query))
342
+ return React.createElement('div', { className: 'mcpm-wrap' },
343
+ React.createElement('h2', null, 'Skills 管理'),
344
+ React.createElement('div', { className: 'mcpm-sub' }, '查看与启停 DSH 技能(按层级分组,禁用即时生效,无需重启)'),
345
+ msg && React.createElement('div', { className: 'mcpm-msg ' + msg.kind }, msg.text),
346
+ React.createElement('input', { className: 'skm-search', placeholder: '搜索技能名称或描述…', value: q, onChange: (e) => setQ(e.target.value) }),
347
+ state.loading ? React.createElement('div', { className: 'mcpm-sub' }, '加载中…') :
348
+ state.error ? React.createElement('div', { className: 'mcpm-msg err' }, state.error) :
349
+ SKM_GROUPS.map((key) => {
350
+ const items = visible.filter((s) => skmLevelOf(s) === key)
351
+ if (!items.length) return null
352
+ // within a level, sub-group by provider (collapsible)
353
+ const byProvider = {}
354
+ items.forEach((s) => { const p = s.provider || 'unknown'; (byProvider[p] = byProvider[p] || []).push(s) })
355
+ const providerKeys = Object.keys(byProvider).sort()
356
+ return React.createElement(React.Fragment, { key },
357
+ React.createElement('div', { className: 'skm-group-title' }, key + '(' + items.length + ')'),
358
+ providerKeys.map((pk) => {
359
+ const pItems = byProvider[pk]
360
+ const collapsed = !query && !!hidden[pk]
361
+ return React.createElement(React.Fragment, { key: pk },
362
+ React.createElement('div', { className: 'skm-provider-title', onClick: () => toggleGroup(pk), title: '点击折叠/展开' },
363
+ (collapsed ? '▸ ' : '▾ ') + pk + '(' + pItems.length + ')'),
364
+ collapsed ? null : pItems.map((s) => {
365
+ const overridden = s.provider === 'dsh-mcp-manager-override'
366
+ const available = !!(s.invocation && s.invocation.modelInvocable)
367
+ return React.createElement('div', { className: 'mcpm-row', key: s.name },
368
+ React.createElement('div', { className: 'mcpm-row-head' },
369
+ React.createElement('span', { className: 'mcpm-name' }, s.name),
370
+ React.createElement('span', { className: 'mcpm-chip ' + (available ? 'on' : 'off') }, available ? '可用' : '禁用'),
371
+ overridden && React.createElement('span', { className: 'mcpm-chip warn' }, '手动禁用'),
372
+ React.createElement('span', { className: 'mcpm-chip live' }, s.provider)),
373
+ React.createElement('div', { className: 'mcpm-sub' }, s.description || ''),
374
+ React.createElement('div', { className: 'mcpm-row-actions' },
375
+ React.createElement('button', { className: 'mcpm-btn', disabled: busy === s.name, onClick: () => toggle(s) }, overridden ? '启用' : '禁用')))
376
+ }))
377
+ }))
378
+ }))
379
+ }
380
+
381
+ slots.inject('settings.section', () => slots.register(
382
+ { name: 'settings.section', id: 'skill-manager', order: 17, label: 'Skills 管理' },
383
+ () => React.createElement(SkillPage)
384
+ ))
300
385
  },
301
386
  }
302
387
  return module.exports
303
388
  }
304
389
 
305
- // Register the client bundle under both names: the loader-entry install
306
- // (install.mjs) mounts it as `dsh-mcp-manager`, while `dsh plugin add`
307
- // (via the dsh.bundle patch) installs the scoped npm package and mounts it
308
- // as `@xxxyz/dsh-mcp-manager`. The boot graph row id must match the id the
309
- // bundle registers, so both are registered — the unused one is inert.
390
+ // Register the client bundle under both names: the official CLI mounts the
391
+ // bundle under the loader entry id `dsh-mcp-manager` (cordis.patch.yml
392
+ // insert row), and the scoped npm package is `@xxxyz/dsh-mcp-manager`. The
393
+ // boot graph row id must match the id the bundle registers, so both are
394
+ // registered — the unused one is inert.
310
395
  window.__ModuleLoader__.load({ id: 'dsh-mcp-manager', factory })
311
396
  window.__ModuleLoader__.load({ id: '@xxxyz/dsh-mcp-manager', factory })