技能与 MCP 管理面板 Fishquito7/dsh-skill-mcp-panel
DeepSeek Harnessv2.3.6DSH Web UI 插件:技能与 MCP 管理(Web 界面的技能/MCP 管理工具)
64/100
综合分
218
GitHub 星标
1,825
npm 周下载
—/5
用户评分
数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-skill-mcp-panelnpm 源 · dsh-skill-mcp-panel 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · ui
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 14 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-skill-mcp-panel @ 2.3.6
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖要求 >=0.2.0-rc.1 <0.3.0-0 · 最新 0.2.1-rc.5 兼容
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 17:38:49
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件写入剪贴板(不计风险)读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 101 处,展示前 96 条)
- 读取本地文件lib/index.js:573
const info = await stat(resolvePath(path)).catch(() => undefined); - 读取本地文件lib/index.js:651
const scopedRaw = await readFile(entry.file, "utf8"); - 读取本地文件lib/index.js:663
const raw = await readFile(located.entry.file, "utf8"); - 写入本地文件lib/index.js:696
await rename(entry.file, target); - 写入本地文件lib/index.js:726
await rename(skill.path, target); - 删除本地文件递归lib/index.js:736
await rm(dirname(entry.file), { recursive: true, force: true }); - 删除本地文件lib/index.js:738
await rm(entry.file, { force: true }); - 删除本地文件递归lib/index.js:762
await rm(dirname(skill.path), { recursive: true, force: true }); - 删除本地文件lib/index.js:764
await rm(skill.path, { force: true }); - 写入本地文件lib/index.js:979
await mkdir(dirname(filePath), { recursive: true }); - 写入本地文件lib/index.js:980
await writeFile(filePath, write.relative.toLowerCase().endsWith(".md") ? normalizeSkillText(write.data) : write.data); - 写入本地文件lib/index.js:983
await rename(staging, skill.target); - 写入本地文件lib/index.js:986
await rename(join(staging, skill.writes[0].relative), skill.target); - 删除本地文件递归lib/index.js:987
await rm(staging, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/index.js:992
await rm(staging, { recursive: true, force: true }).catch(() => { }); - 写入剪贴板lib/client.js:111
return Promise.resolve(clipboard.writeText(text)).then(() => true, () => false); - 读取本地文件lib/tool-filter.js:52
const parsed = JSON.parse(await readFile(toolFilterFile(dshHome), "utf8")); - 写入本地文件lib/tool-filter.js:65
await mkdir(dirname(path), { recursive: true }); - 写入本地文件lib/tool-filter.js:67
await writeFile(temp, JSON.stringify({ version: TOOL_FILTER_VERSION, deny }, null, 2) + "\n", "utf8"); - 写入本地文件lib/tool-filter.js:69
await rename(temp, path); - 写入本地文件lib/tool-filter.js:74
await writeFile(path, JSON.stringify({ version: TOOL_FILTER_VERSION, deny }, null, 2) + "\n", "utf8"); - 读取本地文件lib/reveal.js:64
info = await stat(target); - 写入本地文件lib/reveal.js:70
await mkdir(target, { recursive: true }); - 读取本地文件lib/reveal.js:71
info = await stat(target); - 读取本地文件lib/skill-files.js:34
await access(path); - 读取本地文件lib/skill-files.js:225
items = await readdir(dir, { withFileTypes: true }); - 读取本地文件lib/skill-files.js:235
|| (item.isSymbolicLink() && (await stat(full).catch(() => undefined))?.isDirectory() === true); - 读取本地文件lib/skill-files.js:243
const parsed = parseFrontmatter(await readFile(file, "utf8").catch(() => "")); - 读取本地文件lib/skill-files.js:265
const parsed = parseFrontmatter(await readFile(full, "utf8").catch(() => "")); - 读取本地文件lib/skill-files.js:269
const parsed = parseFrontmatter(await readFile(full, "utf8")); - 读取本地文件lib/scope.js:45
const info = await stat(absolute).catch(() => undefined); - 读取本地文件lib/scope.js:75
const raw = readFileSync(join(dshHome, "storages", "workspace.json"), "utf8"); - 删除本地文件递归lib/scope.js:105
await rm(path, { recursive: true, force: true }); - 写入本地文件lib/scope.js:142
await mkdir(targetRoot, { recursive: true }); - 写入本地文件lib/scope.js:146
await rename(sourceDir, target); - 写入本地文件lib/scope.js:160
await rename(staging, target); - 写入本地文件lib/scope.js:163
await mkdir(staging, { recursive: true }); - 写入本地文件lib/scope.js:166
await rename(stagedFile, target); - 删除本地文件递归lib/scope.js:167
await rm(staging, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/scope.js:171
await rm(staging, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/scope.js:181
await rm(target, { recursive: true, force: true }).catch(() => { }); - 读取本地文件lib/groups.js:37
const parsed = JSON.parse(await readFile(groupsFile(dshHome), "utf8")); - 写入本地文件lib/groups.js:62
await mkdir(groupsRoot(dshHome), { recursive: true }); - 写入本地文件lib/groups.js:65
await writeFile(tmp, JSON.stringify({ version: GROUPS_VERSION, groups }, void 0, 2) + "\n", "utf8"); - 写入本地文件lib/groups.js:66
await rename(tmp, target); - 读取本地文件lib/version.js:17
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8")); - 读取密钥环境变量lib/version.js:37
const token = process.env.GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim(); - 发起网络请求lib/version.js:38
const response = await fetch(RELEASES_LATEST_URL, { - 执行外部命令固定命令lib/global-shim.js:22
return execFileSync("npm", ["prefix", "-g"], { encoding: "utf8", windowsHide: true }).trim(); - 读取本地文件lib/global-shim.js:29
const old = existsSync(path) ? readFileSync(path, "utf8") : undefined; - 读取本地文件lib/global-shim.js:29
const old = existsSync(path) ? readFileSync(path, "utf8") : undefined; - 写入本地文件lib/global-shim.js:32
writeFileSync(path, content, mode !== undefined ? { mode } : "utf8"); - 写入本地文件lib/global-shim.js:34
chmodSync(path, mode); - 写入本地文件lib/global-shim.js:74
mkdirSync(binDir, { recursive: true }); - 读取本地文件lib/provider.js:81
const raw = await readFile(candidate.locator.path, { encoding: "utf8", signal: options.signal }); - 读取本地文件lib/cli-skill.js:84
return Buffer.from((await readFile(fullPath, "utf8")).replace(/^ FEFF/, "").replace(/ /g, "\n").replace(/ /g, "\n"), "utf8"); - 读取本地文件lib/cli-skill.js:91
const items = await readdir(dir, { withFileTypes: true }); - 读取本地文件lib/cli-skill.js:102
const target = await stat(full).catch(() => undefined); - 读取本地文件lib/cli-skill.js:127
const data = await readFile(source); - 读取本地文件lib/cli-skill.js:132
return entry.data !== undefined ? entry.data.toString("utf8") : await readFile(entry.full, "utf8"); - 读取本地文件lib/cli-skill.js:140
const info = await stat(source).catch(() => undefined); - 写入本地文件lib/cli-skill.js:232
await mkdir(destRoot, { recursive: true }); - 写入本地文件lib/cli-skill.js:241
await mkdir(dirname(dest), { recursive: true }); - 写入本地文件lib/cli-skill.js:243
await writeFile(dest, file.relative.toLowerCase().endsWith(".md") ? normalizeMdText(file.entry.data) : file.entry.data); - 写入本地文件lib/cli-skill.js:245
await writeFile(dest, await readNormalizedMd(file.entry.full)); - 写入本地文件lib/cli-skill.js:247
await copyFile(file.entry.full, dest); - 写入本地文件lib/cli-skill.js:250
await rename(staging, skill.target); - 删除本地文件递归lib/cli-skill.js:254
await rm(staging, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/cli-skill.js:259
await rm(staging, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/cli-skill.js:263
await rm(stagingBase, { recursive: true, force: true }).catch(() => { }); - 执行外部命令固定命令lib/cli-skill.js:304
const child = spawn("dsh", ["plugin", "--profile", profile, "add", spec], { - 删除本地文件递归lib/cli-skill.js:712
await rm(dirname(entry.file), { recursive: true, force: true }); - 删除本地文件lib/cli-skill.js:714
await rm(entry.file, { force: true }); - 执行外部命令来自配置/变量lib/cli-mcp.js:349
const child = spawn(opener[0], opener[1], { detached: true, stdio: "ignore" }); - 读取本地文件lib/cli-mcp.js:420
const text = await readFile(resolve(flags.set), "utf8"); - 读取本地文件lib/cli-mcp.js:487
const data = JSON.parse(await readFile(resolve(path), "utf8")); - 读取本地文件lib/profiles.js:37
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件lib/profiles.js:78
entries = readdirSync(root, { withFileTypes: true }); - 读取本地文件lib/profiles.js:89
if (!existsSync(join(root, entry.name, "package.json"))) - 读取本地文件lib/profiles.js:103
if (!existsSync(join(profilesRoot(home), name, "package.json"))) - 读取本地文件lib/oauth.js:37
const parsed = JSON.parse(await readFile(oauthFile(dshHome), "utf8")); - 写入本地文件lib/oauth.js:52
await mkdir(dirname(path), { recursive: true }); - 写入本地文件lib/oauth.js:55
await writeFile(temp, body, "utf8"); - 写入本地文件lib/oauth.js:57
await rename(temp, path); - 写入本地文件lib/oauth.js:62
await writeFile(path, body, "utf8"); - 写入本地文件lib/oauth.js:72
await mkdir(dirname(path), { recursive: true }); - 写入本地文件lib/oauth.js:73
await writeFile(path, JSON.stringify({ version: 1, servers: store }, null, 2) + "\n", "utf8"); - 读取本地文件lib/patch-editor.js:60
return await readFile(path, "utf8"); - 写入本地文件lib/patch-editor.js:313
await writeFile(temp, content, "utf8"); - 写入本地文件lib/patch-editor.js:315
await rename(temp, path); - 删除本地文件lib/patch-editor.js:320
await rm(path, { force: true }); - 写入本地文件lib/patch-editor.js:321
await rename(temp, path); - 删除本地文件lib/patch-editor.js:325
await rm(temp, { force: true }).catch(() => { }); - 读取本地文件lib/patch-editor.js:344
const info = await stat(lockPath); - 删除本地文件lib/patch-editor.js:346
await rm(lockPath, { force: true }).catch(() => { }); - 删除本地文件lib/patch-editor.js:362
await rm(lockPath, { force: true }).catch(() => { });
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-typert-protocol@deepseek-ai/cordis@deepseek-ai/dsh给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
界面预览
6 张 · 点击查看大图功能亮点
- Web界面管理skill
- 管理MCP
最近更新
在 GitHub 查看全部 →v2.3.62026-11-02
· v2.3.6 — MCP 面板接管全部受管行 + 原文编辑;OAuth 发现修复
这个版本把两条社区 PR 合并进来,并修掉了合并后真机实测发现的问题。感谢两位贡献者。
· 受管块:每一条 MCP 行都能在面板里操作(PR #70 by @jiangzhenguo,close #67)
· cordis.patch.yml 里每一条 @deepseek-ai/dsh-mcp-client 行都一视同仁:启停、表单编辑、删除、OAuth 不再看 panel-mcp-* id,也不看它在不在受管块里。
· 新增原文编辑:卡片上的「原文」(CLI:dsh-panel mcp raw / --set)按字节改这一条;表单继续保留没建模的键(例如 maxInstructionBytes)和手写 id。
· 已有配置不需要迁移;不属于 MCP 的外来行仍逐字节保留(ADR-0003)。
· 合并后我们又补齐了几处实测问题:
- 删掉受管块最后一行时 begin/end 标记成对收掉,不再把块外内容卷进去;条目后面的注释不会被表单保存 / 删除吞掉。
- 没有 id 的手写行也能启停(按这一行自己的缩进插 disabled),表单保存不再凭空补 id。
- 重复 id / 重复 serverName 不再改错行:按 serverName + entryId 双键定位,CLI 新增 --id 与 list --ids,可用原文逐条改掉重名;只拒绝新增重名。
- 原文编辑不允许改缩进/位置;非布尔 disabled 原样保留;!!js 的 disabled 卡片标「由表达式控制」并禁用开关;CRLF 文件不再掺 LF 行。
· MCP OAuth:发现顺序与 resource(PR #72 by @dj-jack001)
· 发现顺序改为 RFC 9728 路径形式优先、origin 回退,修好带路径的远程 MCP 端点(例如
· 补上 RFC 8707 resource 并贯穿 authorize / token / refresh;PRM 声明的 resource 同源才采用。
· PRM 驱动的授权服务器 metadata 要求 issuer 精确匹配;失败时保留每条候选 URL 与原因,错误信息可读。
· 原有的 !!js 表达式、注释与手写字节仍原样保留。
· 测试
· 24 个 test-*.mjs 全绿:OAuth 27、patch-editor 35、CLI 14、model 28、gateway 13。
· 合并后在本地 desktop profile 实测:安装、启停、原文编辑、OAuth 登录、重复行修复均通过。
· 升级
dsh-panel update # 已安装本插件的 profile
· 或
dsh plugin --profile add
· 致谢
· @jiangzhenguo — PR #70:全接管所有 mcp-client 行 + 原文编辑入口,这一版 MCP 页的新形态来自这里。
· @dj-jack001 — PR #72:OAuth 发现顺序与 resource 传递的根因定位和修复。
两条 PR 的方向都保留了,剩下的细节问题由维护者在合并后补齐。
v2.3.52026-10-20
· 修了什么
禁用任意一个 MCP 工具后,宿主主线程 100% 卡死(#59、#62 是同一个 bug)。
根因在我们自己的监听器:宿主的 ScopedLayers.effect() 在限制生效之后和撤销(undo)时都会同步广播 tools/change,而插件既监听这个事件、又在回调里 undo() / restrict() —— 自己触发自己,同步无限递归。递归到栈上限抛出的 RangeError 被空 catch 吞掉,于是进程不崩溃、只是主线程持续 100% 打转,也没有任何日志。
用宿主真实的 @deepseek-ai/dsh-scope 复现:旧代码一次 tools/change 放大成 887 次 restrict / 1774 次 view / 递归 458 层(真机报告是 727 万次);修复后同一次事件是 1 次 restrict / 2 次 view / 深度 2。
修复要点:
· tools/change 只由控制器监听一次,syncAll() 带重入守卫;
· 每个插件实例的 sync() 有自己的重入守卫;
· 记住上次真正施加的禁用集合签名——集合没变就只多一次 view(),不 undo、不 restrict(直接掐掉 #62 里 81% 采样的热点);
· applyTo 用 mounting 集合挡住「挂载中 → emit → 再挂载」;
· 原本静默的 catch 全部接到 logger([tool-filter] 前缀),这类故障以后至少留得下日志。
· 顺带
· 「显示插件技能」复选框不再每次回到默认勾选(#59 附带问题),勾选状态记在浏览器里。
· test/mcp-preview-server.mjs 从 4 个工具扩到 10 个(本机验证单工具禁用用,不进 npm 包)。
· 回归守卫:test-tool-filter.mjs 新用例对旧 lib 会 FAIL、对新 lib PASS;test-panel-render.mjs 守复选框记忆。
· 升级
dsh plugin --profile web add
桌面端用户在内置插件安装器里装 add 后面的内容;装完重启 DSH Desktop。
npm:npm i -g dsh-skill-mcp-panel@2.3.5 或 dsh-panel update。
v2.3.42026-10-19
· 修了什么
「打开目录」不再只弹「文档」。 2.3.3 把这一步搬到了宿主侧、也真的会拉起资源管理器,但每次落点都是「文档」——因为 Windows 的 explorer.exe 不认正斜杠:面板为了跨平台比较把技能根规范化成了 C:/Users/.../.dsh/skills,explorer 收到这种参数会当成无效目标,然后默默打开自己的默认位置。
现在宿主先把路径换成本机分隔符(nativePath())再交给系统,stat / mkdir / spawn 用的是同一份形态;提示条里显示的也是宿主回传的那份路径,复制出来直接能粘进资源管理器。顺手修了两个边界:盘符根 C:/ 不再被裁成 C:,末尾多余的 \ 会收敛掉。
对照实验(同一台机器、同样的 spawn(..., {detached:true, stdio:"ignore"}),用 Shell COM 的 LocationURL 核对窗口落点):
| 传给 explorer.exe | 实际打开 |
|---|---|
| C:/Users/fishq/Desktop/skillmcp | 文档 ❌ |
| C:\Users\fishq\Desktop\skillmcp | 正确目录 ✅ |
· 另外
README 中英两份的「界面预览」换成新截图:三栏布局与简洁布局各一张(共 6 张预览图,入口 / 技能面板 ×2 / 分组 / 迁移 / MCP ×2)。
· 升级
dsh plugin --profile web add
桌面端用户在内置插件安装器里装 add 后面的内容;装完重启 DSH Desktop(宿主代码变了才生效)。
npm:npm i -g dsh-skill-mcp-panel@2.3.4 或直接 dsh-panel update。
· 验证
24 个 test-*.mjs 全绿;test-reveal.mjs 新增 nativePath 边界与「正斜杠入参必须换成本机分隔符」守卫,test-panel-render.mjs 断言提示条显示宿主回传的路径。真机实测 C:/Users/fishq/Desktop/skillmcp/.dsh/skills → 资源管理器落在该目录。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-skill-mcp-panel
在 DSH Web 侧边栏管理「技能」与「MCP」服务器,并附统一终端命令 dsh-panel
简体中文 · English
DSH 插件:把技能与 MCP 服务器的管理搬进 DSH Web 的主页侧边栏——点左栏的「Skill / MCP」,中央主区直接切到整页面板(不是弹窗,也不是设置页里的 tab),改完即生效。随包附带统一终端命令 dsh-panel:面板能做的事命令行都能做,另外多一个跨 profile 升级。
- 🗂️ 技能管理 —— 列表预览、搜索、工作区分栏与分组筛选、卡片展开查看全文、热启停 / 删除,支持 .md / .zip / 技能文件夹的添加与批量迁移
- 🔌 MCP 服务器管理 —— 可视化维护 profile cordis.patch.yml 中的 MCP 受管块,Stdio / HTTP 两种调用方式,支持测试连接,保存后由 DSH HMR 热加载
- 🧩 整页面板 —— 与宿主内置「插件」页同一套槽位机制,点左栏即在中央主区切页,页面左上角另有「← 返回会话」
- ⌨️ dsh-panel 命令行 —— dsh-panel skill … 与 dsh-panel mcp …
展开完整文档(README 剩余 1 节)
dsh-skill-mcp-panel
在 DSH Web 侧边栏管理「技能」与「MCP」服务器,并附统一终端命令 dsh-panel
简体中文 · English
DSH 插件:把技能与 MCP 服务器的管理搬进 DSH Web 的主页侧边栏——点左栏的「Skill / MCP」,中央主区直接切到整页面板(不是弹窗,也不是设置页里的 tab),改完即生效。随包附带统一终端命令 dsh-panel:面板能做的事命令行都能做,另外多一个跨 profile 升级。
- 🗂️ 技能管理 —— 列表预览、搜索、工作区分栏与分组筛选、卡片展开查看全文、热启停 / 删除,支持 .md / .zip / 技能文件夹的添加与批量迁移
- 🔌 MCP 服务器管理 —— 可视化维护 profile cordis.patch.yml 中的 MCP 受管块,Stdio / HTTP 两种调用方式,支持测试连接,保存后由 DSH HMR 热加载
- 🧩 整页面板 —— 与宿主内置「插件」页同一套槽位机制,点左栏即在中央主区切页,页面左上角另有「← 返回会话」
- ⌨️ dsh-panel 命令行 —— dsh-panel skill … 与 dsh-panel mcp … 覆盖两个面板的全部能力
- 📦 装上就能用 —— npm 与 Release tarball 安装的都是预构建产物
profile 提示:mcp 子命令必须显式写 --profile <name>,且名字必须已存在——打错会被拒绝,不会新建 profile。skill 不按 profile 分家(技能按用户根 / 工作区存放),不需要该参数。不带 --profile 的 dsh-panel update 升级全部已安装本插件的 profile(desktop 由桌面应用独占,自动跳过)。
目录:界面预览 · 安装 · 功能 · 命令行 · 工作原理 · 开发 · 卸载 · 更新日志 · 链接 · License
界面预览
面板入口在主页左侧栏「插件」下方(v2.1.0 起由设置页迁移到侧边栏),点「技能」/「MCP」即在中央主区打开;两个页面左上角都有「← 返回会话」,可直接回到进面板前那个会话。
入口 · 主页侧边栏中的「Skill / MCP」(v2.1.5 起两个面板合并为一行,标签固定用英文写法)
技能面板 · 三栏布局(工作区 / 分组 / 内容)· 搜索 / 全部启停 / 显示插件技能开关
技能面板 · 简洁布局(工具栏最右按钮切换)· 收起左右两栏 / 工作区折叠下拉 / 一行两个技能
技能分组 · 新建 / 重命名 / 批量勾选成员
批量迁移 · 源工作区 / 多选目标 / 复制或移动
MCP 面板 · 注册机制说明 / 服务器卡片 / 工具数 / 启停 / 测试连接
添加 MCP 服务器 · STDIO / HTTP 两种调用方式
安装
1. 安装本包,二选一(桌面端用户请在内置插件安装器中输入'...add'后面的内容):
方式一:GitHub Release tarball(推荐,恒定指向最新版)
dsh plugin --profile web add https://github.com/Fishquito7/dsh-skill-mcp-panel/releases/latest/download/dsh-skill-mcp-panel.tgz
这是无版本号的稳定链接:每次发版都同时上传别名 dsh-skill-mcp-panel.tgz(恒定指向最新版)与带版本号的 dsh-skill-mcp-panel-<版本>.tgz,所以这条命令不随版本改。需要锁定具体版本时,把 latest/download/dsh-skill-mcp-panel.tgz 换成 download/v<版本>/dsh-skill-mcp-panel-<版本>.tgz 即可。
方式二:npm(预构建,插件市场同款通道)
dsh plugin --profile web add dsh-skill-mcp-panel
两种方式都安装预构建产物,无需本地构建。也可以从 Git 安装(Git 来源的依赖默认禁止运行 prepare 构建脚本;若报 git-hosted plugins build on install...,把 pnpm 在上面打印的 key 加到 profile 目录 pnpm-workspace.yaml 的 allowBuilds 下再重跑):
dsh plugin --profile web add github:Fishquito7/dsh-skill-mcp-panel
2. 重启网关
重启后刷新页面:左侧栏「插件」下面就是合并后的「Skill / MCP」一行——点它把中央主区切成面板,面板内部再用页签切换「技能」与「MCP」。
功能
面板和宿主自带的「插件」页是同一套机制:左栏一行「Skill / MCP」,点它把中央主区切过去——不是设置页的 tab,也不是弹窗。面板内用页签切换「技能」与「MCP」,并记住你上次停在哪一页;左上角「← 返回会话」随时退回进面板前的会话。
技能面板
- 列表与卡片 —— 按名称搜索,点开卡片看全文;按工作区(全局 / 各工作区)分栏,可按分组或按来源筛选
- 启停与删除 —— 开关热生效(改的是磁盘上的 SKILL.md),删除可撤销;插件自带的技能可一键隐藏
- 添加 —— 点「+」或直接把文件 / 压缩包 / 技能文件夹拖进页面:.md、.zip、整个文件夹都认,含多个技能的文件夹一次导入;不合法、目标作用域内重名的逐条跳过并说明原因
- 迁移 —— 批量复制或移动到多个工作区,单条失败不影响其余
- 分组 —— 新建 / 重命名 / 删除、批量勾选成员;只写插件自己的显示配置,不动技能目录
MCP 面板
- 服务器管理 —— 支持 Stdio 与 HTTP 两种方式:新增、编辑、启停、删除、测试连接;保存后由 DSH HMR 热加载,不用重启网关。只改面板自己的受管块,cordis.patch.yml 其它内容(含块内别人的条目)逐字节保留
- 密钥 —— env / headers 在页面与 RPC 里脱敏,编辑时留空即保留旧值;值里写 ${NAME} 就是读该环境变量
- 单工具禁用 —— 按工具名关掉其中一部分,被禁的工具会从模型看到的工具表里消失(不是调用时才拒绝),上下文立刻省下来
- OAuth 登录 —— 远程 streamable-http 服务器支持时一键登录(自动发现授权服务器、动态注册客户端、授权码 + PKCE),令牌写回该行配置并热加载;可随时断开
DSH 版本兼容
只支持 DSH 0.2.x(peer 声明 >=0.2.0-rc.1 <0.3.0-0)。DSH 会在安装时和 profile 启动时校验这条声明,不满足就拒绝加载并打印精确豁免命令——不会静默崩。上界写成 <0.3.0-0 是因为该校验带 includePrerelease,<0.3.0 会把 0.3.0-rc.1 也放进来。
确需在老宿主上使用,用宿主打印的精确版本豁免:
dsh plugin --profile web allow-version dsh-skill-mcp-panel@2.3.5 --dsh-version <宿主版本> --accept-risk
命令行
统一父命令为 dsh-panel。
全局 shim:宿主启动时会在 npm 全局 bin 目录写入 dsh-panel。Windows 下写三份——dsh-panel.cmd(CMD)、dsh-panel.ps1(PowerShell)、以及无扩展名的 dsh-panel sh 脚本(Git Bash / MSYS2 / Cygwin 靠它,v2.1.5 之前缺这一份,bash 里会报 command not found);macOS / Linux 写一份可执行的 dsh-panel。三份都指向「最后启动的那个 profile」装的插件副本。
mcp 子命令必须用 --profile <name> 指定目标 profile,名字必须已存在(判据是 $DSH_HOME/profiles/<name>/package.json)。打错名字会以退出码 2 被拒绝,不会像 dsh plugin 那样顺手新建一个 profile。skill 子命令不需要 --profile——技能按用户根 / 工作区存放,不按 profile 分家。dsh-panel update 不带 --profile 时升级全部已安装本插件的 profile。
profile 概览
dsh-panel profiles # 每个 profile 的已装版本 / bundles 挂载情况 / 安装 spec
输出的第一行是当前 dsh-panel 入口的路径与版本。这条信息是必要的:dsh-panel 是全局单例 shim,由最后启动的那个 profile 写入(src/global-shim.ts),因此它与表中任何一行都没有必然关系。
技能子命令
dsh-panel skill --help
dsh-panel skill list # 列出技能(含工作区:全局 / 工作区) dsh-panel skill add <path> # 添加到全局(.md 文件、目录束或 .zip 压缩包) dsh-panel skill add <path> --workspace D:\项目A # 直接添加到指定工作区 dsh-panel skill scope <name> --global # 迁移单个技能到全局 dsh-panel skill scope <name> --workspace D:\项目A # 迁移到指定工作区(--copy 复制) dsh-panel skill migrate <name...|--all> --from <全局|路径> --to <全局|路径> [--copy] [--yes] dsh-panel skill disable <name> # 停用 dsh-panel skill enable <name> # 启用 dsh-panel skill delete <name> # 删除(需确认)
技能文件全局共享(~/.dsh/skills 与 <工作区>/.dsh/skills),不按 profile 分家,所以 skill 子命令不需要 --profile。可选地加上它,会顺便确认该 profile 存在,并在它没把本插件挂进 dsh.profile.bundles 时提醒你「这个 profile 看不到面板」。
MCP 子命令
dsh-panel mcp list --profile web
dsh-panel mcp add --name <serverName> --stdio --command <cmd> [--args <arg> ...] [--env KEY=VALUE ...] [--cwd <path>] --profile web
dsh-panel mcp add --name <serverName> --http --url <url> [--header KEY=VALUE ...] --profile web
dsh-panel mcp enable|disable <serverName> --profile web
dsh-panel mcp remove <serverName> [--yes] --profile web
dsh-panel mcp test <serverName> --profile web
环境变量取值直接写 ${NAME}(保存时格式化成 cordis.patch.yml 的 !!js 表达式,由 DSH 装载时求值);以 !!js 开头的值按进阶写法原样透传:
dsh-panel mcp add --name github --stdio --command npx --args -y --args @modelcontextprotocol/server-github \
--env 'GITHUB_TOKEN=${GITHUB_TOKEN}' --profile web
dsh-panel mcp add --name web --http --url https://example.com/mcp \
--header 'Authorization=Bearer ${MCP_TOKEN}' --profile web
进阶:整段 JS 表达式原样写进 !!js 标签
dsh-panel mcp add --name web2 --http --url https://example.com/mcp \
--header 'Authorization=!!js Bearer ${process.env.MCP_TOKEN}' --profile web
dsh-panel mcp add --name github --stdio --command npx --args -y --args @modelcontextprotocol/server-github \
--env 'GITHUB_TOKEN=!!js process.env.GITHUB_TOKEN' --profile web
dsh-panel mcp add --name web --http --url https://example.com/mcp \bash
--header 'Authorization=!!js Bearer ${process.env.MCP_TOKEN}' --profile web
MCP 配置写入目标配置档的 cordis.patch.yml 受管块;网关在线时自动热加载。面板块由 dsh-skill-mcp-panel:mcp:begin / # <<< ...end 标记,请勿手改块内内容。
更新插件 bash dsh-panel update # 升级全部已安装本插件的 profile dsh-panel update --yes # 同上,不询问 dsh-panel update --profile web # 只更新 web dsh-panel mcp update # 等价于 dsh-panel update
- 对比基准是每个配置档自己 node_modules 里已装的版本,不是正在执行的那份命令行工具的版本。
- 更新保持该配置档原本的安装渠道:tarball 装的仍走 tarball(换成对应版本的版本化 URL),npm 装的仍走 npm 并钉到该版本, Git(或来源未知)装的仍用 github:Fishquito7/dsh-skill-mcp-panel#v<版本>。
- 已经是最新的配置档会被跳过,不会被重装。
- desktop 由 DSH 桌面应用独占,宿主的 dsh plugin --profile desktop 会直接报错;自动枚举时跳过并说明,显式 --profile desktop 时以退出码 2 报错。
- 更新完成后:客户端 bundle 热更新(刷新页面即可),服务端改动需要重启对应配置档的网关。
命令行工具只扫描当前目录锚定的项目根与用户根;管理其他工作区的技能请加 --cwd <工作区路径>。
同名技能存在于多个作用域时,enable/disable/delete 需加 --global/--project/--workspace 指定操作哪一份。
add 不在此列:目标作用域内没有同名就照常添加,跨作用域同名只打印一条提示(v2.1.7)。
工作原理
技能部分
页面和 dsh-panel skill 命令的每次操作,最终都是对磁盘上技能文件(SKILL.md)的改动,DSH 自带的文件监听器立刻发现变化——所以启用/停用、增删、迁移都热生效,无需重启网关。
- 技能实体直接存放在其工作区的技能文件夹:全局 = ~/.dsh/skills,工作区 = <工作区>/.dsh/skills,没有隐藏存储或联接点——卸载插件后技能仍是普通文件,照常被 DSH 发现
- 停用 = 把 SKILL.md 改名为 SKILL.md.disabled,启用 = 改回来
- 改变所属位置 = 真实地把文件复制/移动到目标位置的文件夹(先校验、失败回滚)
- 随部署附带的技能(bundled)为只读,不可停用或删除
MCP 部分
负责把 MCP 服务器配置写进配置档的 cordis.patch.yml 受管块;真正连接和注册工具的是 DSH 官方插件 @deepseek-ai/dsh-mcp-client,由 DSH 的 HMR 自动加载。
开发
源码为 TypeScript,位于 src/;编译产物 lib/*.js 随仓库一起提交(保证 Git 直装可用)。
改完源码后运行 pnpm build:tsc 编译到 lib/ 并剥离浏览器束的多余模块标记。
发布时 npm pack 会通过 prepack 自动重新构建,无需手工编译。
卸载 bash
dsh plugin --profile web remove dsh-skill-mcp-panel
更新日志
只列会改变使用方式的版本要点;完整改动见 Releases。
- v2.3.5 —— 修 禁用任一 MCP 工具后宿主卡死(#59 / #62)。根因在我们自己的监听器:宿主的 ScopedLayers.effect() 在限制生效后和撤销时都会同步广播 tools/change,而插件既监听这个事件、又在回调里 undo() / restrict(),等于自己触发自己——同步无限递归,爆栈抛出的 RangeError 又被空 catch 吞掉,进程不崩、只是主线程持续 100% 烧 CPU。用宿主真实的 @deepseek-ai/dsh-scope 复现:一次事件放大成 887 次 restrict / 1774 次 view / 递归 458 层。现在 tools/change 只由控制器统一监听并带重入守卫,每个插件的 sync() 只在禁用集合真的变化时才撤销重施(集合没变就只多一次 view),挂载途中的智能体用 mounting 挡住;原本静默的 catch 全部接上 logger,这类故障以后至少留得下日志;顺便修了同一 issue 里「显示插件技能」复选框不记忆的问题(现在勾选状态记在浏览器里)
- v2.3.4 —— 修「打开目录」只弹出「文档」的那个 bug(2.3.3 的回归)。Windows 的 explorer.exe 不认正斜杠:面板把技能根路径规范化成了 C:/Users/.../.dsh/skills,explorer 收到这种参数会当成无效目标、默默打开自己的默认位置,于是点哪个工作区都只看到「文档」。现在宿主先把路径换成本机分隔符再交给系统,提示条里显示的也是同一份路径,复制出来直接能粘进资源管理器;顺手修了两个边界:盘符根 C:/ 不再被裁成 C:,末尾多余的 \ 会收敛掉。界面预览换成新版截图(三栏 / 简洁两种布局各一张)
- v2.3.3 —— 技能页新增 「三栏 / 简洁」布局开关(#50):简洁模式收起左右两栏,工作区变成折叠式下拉、分组变成横排 chip,技能一行两个,想回到旧版那种清爽的单栏就点工具栏最右那个按钮(选择记在浏览器里)。MCP 页那条「受管块里有外来行」的提醒现在能真正解决:[移出受管块] 把不属于面板的行原样搬到标记之外(语义不变),[查看机制] 说明它为什么会在块里、留在块里为什么没有影响;「打开目录」修好了,而且真的会弹出资源管理器(宿主侧调用系统文件管理器;技能根还不存在时会先建出来)。侧栏入口标签改为 Skill / MCP。另合并两条社区 PR:@deepseek-ai/cordis 移到 peerDependencies(dshmarket 不再报 shared-host-package-dependency)、落在受管块外的面板行标成「受管块外」
- v2.3.1 —— 修 #51:单工具禁用面板点不开(报 remote[method] is not a function)。原因很直给:浏览器束里另有一份 Remote 方法清单,客户端只认清单里挂过的方法,而 2.2.0 加 listTools / getToolFilter / setToolFilter 与 OAuth 三连时只补了宿主侧清单、漏了浏览器束那份,于是点「工具」按钮必然抛错;卡片上的 OAuth 登录 / 断开 按钮也是同一个病因,这次一并修好。新增 test-client-contribution.mjs 守两份清单双向全量比对,杜绝再漏
- v2.3.0 —— 只支持 DSH 0.2.x(peer 改为 >=0.2.0-rc.1 <0.3.0-0,0.1.x 的历史兼容分支全部删除:图标不再回退像素后缀旧名、strict codec 去掉 schema 字段);适配 DSH 0.2.1:协议依赖钉到 @deepseek-ai/dsh-typert-protocol@^0.2.1-rc.1 并显式带上它的 peer @deepseek-ai/cordis(此前 ^0.1.0-rc.6 会在 profile 里装出旧协议,宿主 0.2.1 判「strict definition withdrawn」→ 技能页与 MCP 页全挂;peer 缺 cordis 时连导入都会失败,见 Issue #48);dsh-home-paths / dsh-subprocess 里我们只用到的两个纯函数已内联,不再拖它们的 peer 链;MCP 卡片新增 OAuth 登录 / 重新登录 / 断开 按钮(--disconnect 同款能力搬进面板,轮询式,不再只能用 CLI)
- v2.2.0 —— 前端按新设计稿重构(三栏:工作区 / 分组 / 内容;批量选择 + 迁移;删除可撤销);修 #46 受管块改写外来行(会连带把 dsh-market 的更新全部回滚,见 ADR-0003);MCP 新增单工具禁用(真省上下文,见 ADR-0005)与 OAuth 登录(dsh-panel mcp oauth);技能页新增按来源分组与工作区栏(#42:工作区全列出、放不下就滚动);dsh-panel skill add 的同名判定收窄到目标作用域(ADR-0002)
- v2.1.7 —— 「添加技能」的冲突判定收窄到目标作用域内:以前只要这台机器上任何地方(别的 profile、别的工作区、全局根)存在同名技能就拒绝添加,现在只有目标作用域里已有同名才跳过;跨作用域同名照常落盘并回报一条提示(dsh-panel skill add 同步)。理由见 ADR-0002:DSH 的重名由注册表按根优先级裁决,工作区发现根按会话 cwd 解析,别的工作区根本不在本会话的发现链上,且面板的行级操作本就按(名称 + 作用域)定位
- v2.1.6 —— 修好「添加技能」:文件夹选择器不再丢掉被选中文件夹自己的名字(此前直接选中任何合法技能包都会被误报「缺少顶层 SKILL.md」);三个入口(单文件 / 文件夹·zip / 拖放)统一按同一套规则识别——选中范围内直接有 SKILL.md 就是一个技能包(顶层平铺的依赖 .md 与 references/、scripts/ 都是载荷,不再被当成技能逐个校验),否则递归发现其中的技能并拍平到技能根第一层,于是「含多个技能的文件夹」也能一次导入(.zip 同理);不合法 / 重名 / 冲突的逐条跳过并回报,一个都没成功才整体报错。dsh-panel skill add 与面板行为一致
- v2.1.5 —— 侧栏「技能」「MCP」合并成一行「Skill / MCP」(换用新的合并入口图标)+ 面板内页签(记住上次停留);新增「显示插件技能」开关;MCP 环境变量支持 ${NAME} 直觉写法(面板/CLI 自动格式化成 !!js,读回来仍是 ${NAME},不再被写坏成字面量)、MCP 页新增「注册机制」说明按钮,并为空字段补灰字示例;Windows 补上无扩展名的 bash shim;dsh-panel update 保持各 profile 原本的安装渠道(tarball / npm / Git),README 安装指引改用无版本号的稳定 tarball 链接
- v2.1.3 —— 适配 DSH 0.2.0(peer 上界抬到 <0.3.0-0,否则宿主会在 profile 启动时禁用整行);MCP 表单里非法的服务器名不再只报一句网关「天书」
- v2.1.2 —— mcp 子命令必须显式 --profile,错名一律拒绝且不会新建 profile;dsh-panel update 不带 --profile 时升级全部 profile;新增 dsh-panel profiles
- v2.1.1 —— 适配 DSH 0.1.7 重命名的图标导出,技能页不再一片空白
- v2.1.0 —— 面板从设置页迁移到主页侧边栏,每个面板带「← 返回会话」
- v2.0.0 —— MCP 面板上线
链接
- npm 包:dsh-skill-mcp-panel
- 版本发布:Releases
- 问题反馈:Issues
- 英文文档:README.en.md
许可证
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Fishquito7/dsh-skill-mcp-panel
DSH Web UI 插件:技能与 MCP 管理(Web 界面的技能/MCP 管理工具)
相关插件 · UI / 主题
破甲增强版JeffKeeping/dsh-purge-plus
dsh-purge 的非官方分支fork:适配 DeepSeek Harness 0.2.1-rc.2、破甲内核…
★ 387已汉化DeepSeek 客户端
71.9
无代码业务系统平台nocobase/nocobase
NocoBase 是什么
★ 24,804已汉化DSH
71.8
1Panel 服务器面板1Panel-dev/1Panel
全球 250 万+ 自托管用户共同喜爱。
★ 37,344已汉化DSH
71.5
终端编码代理esengine/DeepSeek-Reasonix
· 指南 · ACP · 扩展开发 · 规格 · 官方网站 · Discord · QQ
★ 36,045已汉化DSH
71.2
工作状态行动态ccch1mneyyy/working-activity
作者:chimney@ccch1mneyyy。社区项目,非 DeepSeek 官方项目。
★ 664已汉化DSH
71.2
多模型配置管家farion1231/cc-switch
跨平台桌面一体化助手,支持 Claude Code、Codex、OpenCode、DeepSeek Harness 等工具,唯一官网 ccswitch.io
★ 151,202已汉化DSH
71.1