@guowenzhang/dsh-mcp-manager
Manage MCP servers, load them on demand, and filter tools to reduce context usage.
Install
dsh plugin --profile web add @guowenzhang/dsh-mcp-manager
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
description: "在 DeepSeek Harness 中便捷管理 MCP,按需加载并筛选工具,减少上下文占用"
kind: "plugin-readme"
@guowenzhang/dsh-mcp-manager
中文 | [English](<README.md>)
解决什么问题
DeepSeek Harness 添加 MCP 不够方便,配置多个 MCP 后默认全部加载工具,占用大量上下文。
本插件在设置页提供 MCP 新增、编辑与开关。使用按需加载时,初始上下文只保留 MCP 名称和描述,模型在运行过程中按需启用;同时支持工具过滤,只加载选中的部分工具。
插件列表的显示名称与介绍支持英文和中文,随 Harness 语言设置显示,英文为默认回退;英文名称为去掉 npm scope 的原包名,中文名称说明用途,安装仍使用不变的真实包名。
截图
MCP 管理页:服务器
MCP 管理页:设置
一页两个页签:「服务器」是名册,「设置」是加载方式与一次把多少工具内容交给模型。服务器列表独立滚动,上方页面标题、页签、新增按钮与筛选栏保持固定。Agent 预设名称与新建会话使用同一套宿主翻译,随界面语言更新;自定义名称保持原样。编辑器里配置、密钥与工具列表分为独立标签页;新增行的写入落点由弹窗里的「配置范围」一个下拉决定,选项就是全局与全部已声明预设;工具列表逐行标出该工具估算的 token 量,并汇总当前加载与取消勾选各是多少。开关失败会在该行内提示并可重试。
安装
需已安装 DeepSeek Harness、pnpm,并使用 Node.js ≥22.18 或 ≥24.2。
npx @deepseek-ai/dsh plugin --profile web add @guowenzhang/dsh-mcp-manager@^1.1.0
安装后重启宿主并刷新页面,打开 设置 → MCP 管理,新增 MCP 行并填写用途描述。这一页分两个页签:服务器(名册:一个列表,全局行在最前并带「全局」标签,下面是通过预设下拉选中的那个 Agent 预设的行;点卡片本身打开编辑器,编辑器分 配置 / 密钥 / 工具列表 三页——有环境变量或请求头的行直接落在密钥页)与 设置(MCP 加载方式:默认「动态插入」;下面是一次有多少工具内容进上下文)。新增时「配置范围」一个下拉就选好落点:全局,或某个 Agent 预设。新增表单里带 * 的两个字段(服务器名、JSON 配置)是必填;粘贴 JSON 后服务器名会按它自动填好(npx -y @upstash/context7-mcp → context7),你改过它之后就不再自动写。
其他安装方式与开发说明见 AGENTS.md。
进一步省上下文:检索、分页、只带名称
设置 → MCP 管理 → 设置 控制一次调用有多少工具内容进入上下文,加载方式也在这一页。MCP 规范本身没有给 tools/list 查询参数,也没有「只列名称」的形态;官方客户端最佳实践把渐进式发现放在宿主侧,这一页做的就是这件事:
设置 · 作用
工具检索(search) · 注册 mcp_search:模型用关键词在所有已发现的服务器里找工具,只拿到工具名和一行描述。
检索时索引未缓存的服务器(searchIndex) · 允许检索为还没有工具目录的服务器临时连一次(最多同时 3 台、单台 8 秒)。默认关闭,保证检索是一次廉价读取。
每次列出条数(pageSize) · mcp_load / mcp_search 一次最多列出这么多条,其余用 nextCursor 翻页。
只列名称与描述(namesOnly) · mcp_load 默认不带参数 schema;按名字要某一个工具时仍会返回它的完整参数。
单台服务器最多拉取(maxTools) · 给 tools/list 翻页设上限,超大服务器不会一次灌满请求。
列出工具注解(annotations) · 把服务器声明的 readOnlyHint / destructiveHint / idempotentHint / openWorldHint 带进列表。
隐藏破坏性工具(hideDestructive) · 把 destructiveHint 明确为 true 的工具从会话可见集合中完全去掉。
「设置」页签还会显示工具目录缓存(服务器数、工具数、缓存时间),清空按钮同时清除内存与磁盘目录。所有开关都是活设置:保存后对下一次加载、检索或重建索引生效,不需要重启。
工具目录数据库: 全部 MCP(本地 stdio 与 HTTP/SSE/Streamable HTTP 服务器)统一保存到 <DSH_HOME>/cache/dsh-mcp-manager/<profile哈希>/catalog.sqlite。本地目录长期保留,不参与刷新按钮的连接操作;服务器声明 ttlMs 时,按它的有效期在下一次检索重新发现,未声明时保留到手动刷新。「工具目录缓存」标题下直接显示目录状态,「刷新」只重新连接当前启用的 HTTP/SSE 服务器,失败保留原目录并逐台提示;「清空缓存」清除全部目录。没有手动设置的缓存有效期。新增探测成功会立即保存工具目录,但不保存 MCP 配置,探测失败不影响直接保存。命令、参数、实际工作目录、环境、URL 或请求头变化后按配置摘要区分目录。数据库只保存公开工具定义与摘要,不保存原始配置或凭据;工具定义自身仍可能含服务器发布的敏感文本。实际调用工具仍需真实连接。
注意事项
- 按需加载覆盖两个平面,而且启动时一个进程都不起。 「动态插入」(
dynamic,默认)与「延迟加载」(lazy)下,允许的行在它的插件真正运行之前就被抑制,所以宿主启动与 Agent 预设重建都不会启动任何 MCP 进程;连接在第一次mcp_load时才建立。全局组合声明行同样跟随加载方式:「全部加载」(eager)下由组合直接挂载,「动态插入」「延迟加载」下保持被拦住的状态,直到mcp_load按需拉起并与其他会话共用同一条连接。工具过滤对全局行仍不生效。 - 按需加载,加载后复用。 连接放在宿主级常驻池里,键是「行 + 连接配置」:同一台服务器再次加载——同一会话、别的会话、子代理——都复用同一条连接(stdio 就是同一个子进程)。
mcp_unload只释放调用方那个会话的引用;最后一个会话释放后服务器才停止,会话结束同样只释放引用。连接随宿主进程存活——提交不同的加载模式会重建工具面并释放该模式的引用,宿主重启后需要重新mcp_load。「启动时不启动任何进程」与之前是否加载过无关。 - 动态插入(
dynamic,默认)把该服务器的工具加入加载它的那个会话,只注册你的过滤规则与本次加载请求留下的工具;延迟加载(lazy)通过固定代理调用工具,不改变工具列表。模型使用mcp_load/mcp_unload,mcp_load还支持query(只加载命中的工具)、tool(只加载一个工具并取回它的参数)和cursor/limit(长列表翻页)。 - 名册是一个列表,没有分组标题:全局行恒在最前,并带「全局」标签;下面的行属于筛选栏里选中的那个 Agent 预设(预设下拉列出全部已声明预设,含还没有行的,同时决定新增时写到哪个预设)。改名册筛选不会隐藏任何平面——选中某个预设时全局行照样列出,因为会话本来就带着它们。编辑某一行则写回该行自己的平面。
- 工具过滤与优化开关在下一次加载时生效,已加载的 MCP 不会自动更新。加载或读取工具列表需要连接服务器,可能等待子进程启动。
- 按需加载的服务器不提供服务器 instructions 与资源工具:它们的工具走同一个 harness 适配器、绑定质量一致,但提供 instructions 与共享资源的那条挂载路径已经没有了;需要这两样就把行放进 Agent 预设并选「全部加载」。
- 全局平面开箱即用:首次打开「MCP 管理」时自动创建。 全新安装的 profile 只挂宿主自己的 root Include(每次启动会被重写),所以「全局」原本不可写。插件会在每个宿主进程第一次读取名册时自动创建一次:在 profile 目录新建
mcp.cordis.yml(空行清单),并向cordis.patch.yml追加一行cordis:include(initial: []),随后重载让 Include 立刻挂载——不需要点任何按钮,也不碰宿主的 root Include 或其他配置。自动创建失败(或宿主较老)时,名册上方会显示原因与「启用全局(自动创建)」重试按钮;在新增弹窗里把「配置范围」切到「全局」也会触发同一次创建。写成功但没挂载时提示重启后生效。 - 全局行写在自己的 Include 文件里时,设置页能直接开关它。 写入目标是你自己的那个 file-backed Include 的文件——宿主的 root anchor 被排除,所以"profile 自己声明了一个 Include"的组合不再被判成只读。由组合本身(或补丁层)声明的行在那个文件里没有对应行,因此仍然只读:开关与行内按钮置灰、卡片也不再打开编辑器,原因写在控件提示里,服务端也会以
mcp/read-only拒绝写入;这类行照旧能按需加载、复用池、在「全部加载」下挂载。一条已知限制:全局写入是整表重写那个文件,文件里的注释与你自己的引号风格会丢,启用后还会留下显式的disabled: false——功能正确,但不等于你写下的原文。 - 验证状态: 行为有三层验证。
pnpm test通过 31 个文件 / 332 项,pnpm run test:harness通过 7 个文件 / 27 项。全局平面自动创建这一轮在隔离实例上实测(@deepseek-ai/dsh@0.2.1-rc.2,最终产物lib/index.mjs2A4A2AF8…、lib/client.js9BB47956…):打开「MCP 管理」即自动创建可写 Include、无需任何点击(11 项断言:首次读取即创建、只写一次、刷新后不重复、随后可直接新增全局行),1280px 与 390px、浅色与深色均无横向溢出,控制台无报错;更早构建上的 19 项主流程、8 项弹窗创建与 10 项空态断言见第十八、十九节;本轮读数见 [验证记录](<docs/testing.md>) 第二十节。更早一轮读数见 [验证记录](<docs/testing.md>) 第十一至十五节:名册对齐与页签全宽、新增/编辑弹窗的落点下拉与必填标记、工具列表的 token 与加载态、设置页描述清理;导入 Claude 配置的整条链路已按需求删除,见 D29);此前的名册布局改版(去掉分组标题、按行标注、点卡片编辑、密钥页)另外在隔离实例里用真实浏览器跑过 22 项断言:七档窗口(320/360/430/601/760/1024/1440px)无横向溢出、浅色与深色均正常、控制台无报错,产物lib/index.mjsCB726D57…、lib/client.jsE3067B1F…。同日的页签改名与加载方式移位(第二个页签「上下文」→「设置」,加载方式移入该页签,删掉名册那句开关说明与计数里的「筛选:」后缀)也在隔离实例上复跑:两枚页签为服务器/设置、加载方式在「设置」页签最前、名册页签h3只剩服务器列表、11 档窗口(320…1440px)页标题/tabpanel/首行左右边界差为 0、深浅色正常、控制台无报错,产物lib/client.jsE6F79864…,读数见验证记录第九节。三层验证分别是:pnpm run typecheck、pnpm run build与pnpm test全部通过;pnpm run test:harness在真实 Loader 上覆盖(dynamic下 mcp-client 回调从不执行;全局行同样适用,含"本插件加载前就被组合挂载"的行;两个会话复用同一条连接、mcp_unload每次只释放一个引用;写在自己 Include 文件里的全局行能在设置页开关,被用户停用的行开关立即显示为关;一份目录跨注册代次共享;超时未连上的连接会被关闭而不是泄漏进程;池的 claim 按次计数,同一服务器的并行加载收敛为一个视图;发布点被接管的 file-backed 行不改写它的文件)。隔离实例用当天的产物(lib/index.mjs121B8C0A…、lib/client.jsE1B1051A…)+@deepseek-ai/dsh@0.2.0-rc.2实测——该版本就是 n
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::13,最后更新 2026-11-05