按需连接 MCP 服务器,空闲自动断开并缓存工具列表
30.3/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/23 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add zhe97396-tech/dsh-lazy-mcp该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 的按需 MCP 网关:server 只在被用到时连接,空闲自动断开,工具列表缓存在磁盘,子进程环境自动脱敏。
README
由 DeepSeek 最新模型翻译生成dsh-lazy-mcp
一个面向 DeepSeek Harness(DSH)的惰性加载 MCP 网关。启动时不启动任何 MCP server:每个 server 在首次使用时才连接,并在空闲窗口过后被释放。工具和资源会被桥接进模型的工具目录,Web UI 则展示各 server 的实时状态。
MCP 状态面板
状态面板:每个 server 的状态、生命周期、工具数量,以及可调整的空闲窗口。
为什么需要它
@deepseek-ai/dsh-mcp-client 会在插件激活期间连接每一个已配置的 server,并等待初始连接与工具发现完成,因此:
- 每个 server 在 Harness 的第一轮之前就已经在线;
- 一个缓慢或故障的 server 会拖慢启动;
- server 会一直驻留到 Harness 重启(它唯一一个形似 idle 的旋钮 reconnect.maxDelayMs 管的是重连退避,而不是空闲关闭)。
本网关在启动时什么都不启动。
展开完整文档(README 剩余 1 节)
dsh-lazy-mcp
一个面向 DeepSeek Harness(DSH)的惰性加载 MCP 网关。启动时不启动任何 MCP server:每个 server 在首次使用时才连接,并在空闲窗口过后被释放。工具和资源会被桥接进模型的工具目录,Web UI 则展示各 server 的实时状态。
MCP 状态面板
状态面板:每个 server 的状态、生命周期、工具数量,以及可调整的空闲窗口。
为什么需要它
@deepseek-ai/dsh-mcp-client 会在插件激活期间连接每一个已配置的 server,并等待初始连接与工具发现完成,因此:
- 每个 server 在 Harness 的第一轮之前就已经在线;
- 一个缓慢或故障的 server 会拖慢启动;
- server 会一直驻留到 Harness 重启(它唯一一个形似 idle 的旋钮 reconnect.maxDelayMs 管的是重连退避,而不是空闲关闭)。
本网关在启动时什么都不启动。
| 行为 | dsh-mcp-client | 本插件 |
| Harness 启动时的 server 进程 | 已启动 | 未启动 |
| server 启动时机 | 插件激活时 | 首次需要它的工具调用时 |
| server 停止时机 | 仅在配置重载 / Harness 重启时 | 空闲 idleDisconnectMs 之后 |
| 常驻工具定义 | 每个 MCP 工具一个,每次请求都发送 | 3 个固定的网关工具 |
| 上下文中的工具 schema | 全部、始终 | 仅在调用 mcp_tools 时 |
| 描述处于休眠的 server | 需要建立连接 | 由缓存的元数据提供 |
| 每个 server 的生命周期 | 一种行为 | lazy / lazy-keep-alive / eager / keep-alive |
安装
要求:Node.js 20 或更高版本,以及一个 DSH profile(通常用 Web profile)。本包不依赖任何 @deepseek-ai/ 包,因此 Harness 的 peer-range 变化不会把它弄坏;它唯一的运行时依赖是 MCP TypeScript 客户端 SDK,也就是官方桥接所用的同一个 SDK。
方案 A — 从 GitHub 安装
dsh plugin --profile web add github:zhe97396-tech/dsh-lazy-mcp
然后重启 dsh web。Host 半边和浏览器半边都从已安装的包加载,而运行中的 Harness 不重启就无法拾取刚安装的模块。
方案 B — 安装 release tarball
从 Releases 页面下载 dsh-lazy-mcp-<version>.tgz,通过插件管理器安装(或 dsh plugin --profile web add ./dsh-lazy-mcp-<version>.tgz),然后重启 dsh web。
该插件由 profile 中的一行 - id: lazy-mcp 启用。仅配置的变更通常可以热重载,但当它随一个被替换的包一起到达时,管理器会把它推迟到重启(结果会显示 application: "restart-required")。把该行关掉再打开——即插件管理器对 include:lazy-mcp 执行 set_plugin——会强制重新组合(re-compose)并立即应用新配置,无需重启。
两个半边
| 半边 | 文件 | 由谁加载 |
| Host | index.js | cordis.patch.yml 中的 bundle 行 |
| 浏览器 | client.js | package.json 中的 dsh.client 声明,由 dsh-client-modules 提供 |
浏览器半边不导入任何 DSH Client 包:React 来自浏览器模块表,所有颜色都是主题 token,因此它跟随浅色与深色主题而无需第二份样式表。它的 __ModuleLoader__.load({ id }) 使用精确的包名,这就是为什么包名、Loader 行名和命名清单必须一起重命名。
两个半边仅通过 Host 半边在 /lazy-mcp/state 注册的只读 JSON 路由,以及共享的面板 id lazy-mcp 连接在一起。
它实际装在哪里
| 内容 | 位置 |
| profile 依赖 + bundle 列表 | ~/.dsh/profiles/web/package.json |
| 已安装的包——一个带自己 node_modules 的真实目录 | ~/.dsh/profiles/web/node_modules/dsh-lazy-mcp |
| 内置的 tarball(仅在从 tarball 安装时存在) | ~/.dsh/profiles/web/.local-packages/dsh-lazy-mcp-<version>.tgz |
| 插件管理器写入的启用覆盖 | ~/.dsh/profiles/web/cordis.patch.yml(- id: lazy-mcp) |
| 源码 | 本仓库——运行时不会被读取 |
插件的设置位于本包自己的 cordis.patch.yml 中,作为 bundle 的 patch 层加载——而不是 profile 的 cordis.patch.yml。若要从 profile 层覆盖某个设置,在那里添加一个带 config 的 - id: lazy-mcp 条目即可;profile 层优先。
代码改动后重新构建
cd dsh-lazy-mcp
pnpm install
pnpm run check # syntax check of both halves
pnpm run lint # package identity / manifest consistency
pnpm run verify # self-check harness (starts a real MCP reference server)
pnpm pack # dsh-lazy-mcp-<version>.tgz
先提升 version,好让新产物得到不同的名字、旧的可以被清理掉,然后通过插件管理器安装那个 tarball 并重启 dsh web。
配置
本包出厂时没有配置任何 server(servers: []),因此在向 lazy-mcp 行的 config 添加条目之前,什么都不会被启动:
- id: lazy-mcp
config:
idleDisconnectMs: 600000 # disconnect a lazy server after 10 minutes idle
connectTimeoutMs: 30000 # deadline for connect + tool discovery
callTimeoutMs: 60000 # deadline for one tools/call
cachePath: !!js dshHomePath('lazy-mcp/tool-cache.json') # default: <DSH_PROFILE_DIR>/lazy-mcp/tool-cache.json
servers:
- name: docs # ^[A-Za-z0-9_-]{1,32}$, unique
transport: stdio
lifecycle: lazy
description: Documentation lookup
command: npx
args: ['-y', 'some-mcp-server']
env: { SOME_TOKEN: '...' } # optional, merged over the scrubbed env
cwd: 'C:\path\to\project' # optional
idleDisconnectMs: 120000 # optional per-server override
- name: knowledge
transport: streamable-http
lifecycle: lazy
description: Internal knowledge base
url: https://mcp.example.com/rpc
headers: { Authorization: !!js 'Bearer ${process.env.MCP_TOKEN}' }
除 server 的 name、transport 以及该 transport 的目标(stdio 用 command,streamable-http 用 url)之外,每个字段都是可选的。无效配置会让激活失败,并准确指出出错的具体路径。
环境变量清洗
stdio server 的子进程环境等于父进程环境减去所有匹配 KEY|PASSWORD|SECRET|TOKEN(不区分大小写)的名字,再减去所有 DSH_ 名字,然后与 server 显式的 env 合并。这与随包提供的子进程 seam 的定义一致,因此子 server 永远不会继承本进程的模型凭据。需要密钥的 server 必须通过自己的 env 映射获得它。环境变量的值从不写入日志,也从不写入缓存——只写入键集合,且仅用于检测配置变更。
导入已有的 MCP 配置
把 configPaths 指向任何包含标准 mcpServers 对象的 JSON 文件,其中的 server 就会出现在这里。已有的 Pi(pi-mcp-adapter)、Claude Code、Cursor 或 .mcp.json 配置就是这样变为可复用的,无需手工复制命令——另一个工具仍然是事实来源。
- id: lazy-mcp
config:
configPaths:
- '~/.config/mcp/mcp.json' # Pi shared config
- '~/.pi/agent/mcp-adapter.json' # Pi adapter config
标准字段会被翻译:command/args/env/cwd 变成 stdio server,url/headers 变成 streamable-http server。路径开头的 ~/,以及 args、cwd、env、headers、url 中的 ${VAR} / $VAR,都会从环境中展开——未知的名字会按字面保留,而不是变成空字符串,这样错误就一直可见。
在存在等价物的地方,Pi 特有的调优会被沿用:
| Pi 字段 | 此处效果 |
| lifecycle | 直接使用(lazy / lazy-keep-alive / eager / keep-alive) |
| idleTimeout | Pi 中为分钟,会转换为 idleDisconnectMs |
| disabled: true / enabled: false | 跳过该 server |
| type: "sse" | 以明确的错误拒绝——不支持 SSE |
directTools、includeTools、excludeTools 和 searchKeywords 会被忽略;它们在这里没有等价物。
优先级与 overrides
名字冲突时,靠后的来源胜出:
1. 按顺序的每个 configPaths 文件
2. 行配置中的内联 mcpServers 映射
3. 原生的 servers 数组
4. 逐字段的 overrides
overrides 会修补某个已导入 server 的个别字段,因此你可以给它一个描述或一个生命周期,而无需重复它的命令:
overrides:
chrome-devtools:
description: Browser automation and debugging
lifecycle: lazy-keep-alive
带有 disabled: true 的条目会彻底移除该名字,这就是在不编辑另一个工具的文件的前提下关闭某个已导入 server 的方式。
容错规则
导入的文件是活的数据源,可能在你脚下发生变化,因此那里出问题不能阻止 Harness 启动:
| 情形 | 行为 |
| configPaths 文件缺失 | 警告并跳过 |
| overrides 的键什么都没匹配到 | 警告并忽略 |
| configPaths 文件是畸形 JSON | 激活大声失败 |
| server 条目无效(没有 command 或 url、名字不合法) | 激活大声失败 |
规则是:缺失可容忍,损坏要大声。
生命周期模式
按 server 用 lifecycle 设置。默认值是 lazy。
| 模式 | 何时连接 | 空闲断开 | 适用场景 |
| lazy | 首次使用时 | 是,idleDisconnectMs 之后 | 默认值:启动成本低、偶尔使用 |
| lazy-keep-alive | 首次使用时 | 首次使用之后永不 | 启动成本高——你不想每个空闲窗口都重付一次启动成本 |
| eager | 激活时开始 | 否,除非显式设置了 idleDisconnectMs | 你想让它保持热,但慢 server 不能拖慢启动 |
| keep-alive | 激活时开始 | 否,除非显式设置了 idleDisconnectMs | 需要始终可用的 server |
eager 和 keep-alive 在插件激活时开始连接,但激活从不等待它们,因此一个缓慢或故障的 server 无法拖延 Harness 启动。后台连接失败会被记录日志,而不会抛出。
首次成功连接之后,lazy-keep-alive server 会被报告为 pinned=yes,其空闲计时器不再被重新启动。
四种模式的恢复方式相同:如果连接断开,下一次调用会重连。没有后台健康检查,也没有重连循环。
元数据缓存
首次成功连接会把 server 的工具列表写入 <DSH_PROFILE_DIR>/lazy-mcp/tool-cache.json(原子性的"临时文件加重命名"写入)。当 server 处于休眠状态时,mcp_tools 从该缓存作答,且不会启动 server:
mcp_tools { server: "docs" } -> cached list, server stays dormant
mcp_tools { server: "docs", refresh: true } -> connects and re-reads live metadata
缓存以 server 的 transport、command、arguments、工作目录、URL 以及 env 键集合的指纹为键。改动其中任何一项都会让该条目不可用,因此过期的缓存永远无法描述一个此后已被重新配置的 server。缺失、不可读、畸形或版本不对的缓存永远不是错误;它只意味着还没有 server 缓存过元数据。
空闲断开之后,记录会回退到缓存,因此 mcp_servers 报告 tools=13(cache),而不是声称拥有它已经不再拥有的实时元数据。如果缓存写入尚未落地,它会改为报告 (memory)——此时仍然使用进程内的列表,而不是强制重连。
工具
MCP 暴露三种原语:tools(模型调用的函数)、resources(它能读取的文档和数据)以及 prompts。本网关桥接前两种。prompts 不做桥接。
| 工具 | 是否连接? | 用途 |
| mcp_servers | 从不 | 列出 server,含生命周期、状态、工具数量、元数据来源、最近错误、连接次数 |
| mcp_tools | 仅在 refresh: true 时 | 列出一个 server 的工具及其输入 schema;休眠时从缓存提供 |
| mcp_call | 是,按需 | 调用 server + tool + arguments,然后重置空闲计时器 |
| mcp_resources | 是,总是 | 列出一个 server 的资源和资源模板;报告"没有资源"而不是失败 |
| mcp_read_resource | 是,按需 | 按精确的 uri 读取一个资源,然后重置空闲计时器 |
典型流程:
mcp_servers -> which servers exist, which are dormant
mcp_tools { server: "docs" } -> cached list; server NOT started
mcp_tools { server: "docs", refresh: true } -> starts it; live list
mcp_call { server: "docs", tool: "search",
arguments: { query: "x" } } -> runs it; resets the idle timer
mcp_resources { server: "kb" } -> what it can READ; connects
mcp_read_resource { server: "kb",
uri: "kb://doc/42" } -> the body; resets the idle timer
并发:需要同一个休眠 server 的两个调用会共享一次连接尝试;对不同 server 的调用彼此独立。
资源
mcp_resources 是读取的那半边。它之所以存在,是因为有些 server 几乎把全部价值都放在资源里——文件系统 server 暴露文件,知识库暴露文档,数据库 server 暴露 schema 和行——而只桥接工具的网关连接到这些 server 之后什么也展示不出来。
它总是连接。 mcp_tools 可以在休眠时从缓存作答;资源列表不行,因为持有一份过期列表比付一次连接的代价更糟。因此休眠的 server 会在第一次 mcp_resources 调用时被唤醒,空闲计时器照常回收它。对 resources/list 回答"method not found"的 server 会得到一句说明这一点的话,而不是一个失败。
二进制正文从不内联。 资源携带 text 或 blob 之一。base64 正文会把上下文窗口花在模型无法使用的字节上,因此它会变成一条大小提示——与官方 dsh-mcp-resources 使用的同一种投影:
demo://resource/dynamic/blob/42 text/plain
[binary resource: 72 base64 characters; available to programmatic callers]
文本正文会完整返回,每个正文最多 8,000 个字符,其上方是 uri 和 mime 类型。
模板只列出,不展开。 像 demo://resource/dynamic/text/{resourceId} 这样的模板会按原样显示,并附注说明要替换其中的变量并读取结果 uri;网关从不猜测占位符里应该填什么。
这两个工具都通过工具侧所用的同一个 ensureReady 连接,因此资源读取共享惰性生命周期、空闲回收和投递语义。两者都不会被缓存到磁盘——只有工具元数据会被缓存。
状态面板
浏览器半边向侧边栏添加一个条目(sidebar.panellist,order 40,label MCP),以及它背后的面板(main,key lazy-mcp)。按钮由 shell 拥有:sidebar.panellist 的 id 会寻址同名的 main 面板,因此本插件不写任何导航代码,只提供一个图标(通过 slot 的 { size, active } props)和一个面板主体。
面板每 5 s 轮询一次 /lazy-mcp/state,并按 server 展示:状态徽章、lifecycle、带元数据来源的工具数量、最近使用时间以及连接次数。打开它无法唤醒 server——该路由读取内存中的记录,从不触碰 transport。
| 徽章 | 含义 | 依据 | Token |
| 正在使用 (in use) | socket 打开,120 秒内使用过 | status === 'ready' 且最近一次调用在 120 s 内 | --dsw-alias-state-success-primary |
| 已连接 (connected) | socket 打开,空闲 | status === 'ready',空闲时间超过该阈值 | --dsw-alias-brand-primary |
| 连接中 (connecting) | 连接进行中 | status === 'connecting' | --dsw-alias-state-warn-primary |
| 休眠 (dormant) | 无进程,无错误 | status === 'dormant',无已记录错误 | --dsw-alias-state-idle-primary |
| 连接失败 (failed) | 上次尝试失败 | lastError 存在——优先于 transport 状态 | --dsw-alias-state-error-primary |
状态标签在 UI 中是中文;上方的含义列是本地化改动时的参照(见 贡献指南)。
最后一行才是关键:连接抛错的 server 会保持 status: 'dormant',因此 Host 会在它旁边记录 lastError,面板报告失败,而不是一个让人安心的灰点。
这五个里只有三个是真正的 Host 状态。 Host 恰好只有 dormant、connecting 和 ready;面板按新近程度把 ready 拆成两种颜色。正在使用与已连接是同一种底层状态——两者中 socket 都是打开的、工具列表都是实时的——它们之间那条 120 秒的线纯粹是展示:它不改变任何行为,也不改变 server 何时被丢弃。唯一真正起作用的线是 idleDisconnectMs。
因此,在默认设置下,一次使用会产生这样的时间线:
a call ──┬─ 0–120 s ────── 正在使用 (ready) ├─ 120–600 s ──── 已连接 (ready) └─ > 600 s ────── 休眠 (dormant, process released)
倒计时在连接时启动,并被每次使用重置,因此窗口是"距上次活动 10 分钟",而不是"距连接 10 分钟"。活动指 mcp_call 或 mcp_tools { refresh: true }——两者都会触碰 server。mcp_servers 和走缓存的 mcp_tools 不重置它,因为两者都不触碰 server;正是这一点让"看一眼工具列表"是免费的。
要在几分钟内看全三种状态,把窗口设为 180:120 s 的正在使用、60 s 的已连接,然后休眠。
该路由只暴露名字、状态和计数——从不暴露 transport 目标(它可能是一条命令行),也从不暴露环境变量的值。GET 是只读的,无法唤醒 server。webServer 是通过 ctx.get 读取的软依赖,因此 headless 和 SDK profile 会加载插件并获得三个网关工具,只是没有面板。
会话内的小徽章
面板只有一次点击之遥,但"现在有任何东西连接着吗?"这个问题不该花一次点击。因此浏览器半边还注册进 conversation.session.header.utilities——右对齐的 Session 工具区,位于会话日志下载(order 0)与 dsh-better-sidebar 的底部切换(order 10)之间——作为一个小徽章。
会话内徽章
会话头部的徽章:绿点与已连接的 server 名称。
| 情形 | 徽章 |
| 所有 server 都休眠 | 什么都不渲染 |
| ≥1 个 server ready,最近 120 s 内无使用 | 强调色圆点 + 已连接的名字 |
| ≥1 个 server ready 且在 120 s 内使用过 | 绿点 + 已连接的名字 |
| ≥1 个 server connecting | 强调色圆点 + 名字 |
全部休眠时什么都不渲染是刻意为之。 缺席本身就是信号,而每个会话里都放一个徽章——包括大量从不接触 MCP server 的会话——会变成不携带任何信息的永久杂物。详细信息由 tooltip 承载:已连接的名字、失败数量以及生效的空闲窗口。
徽章与面板共享一个轮询器:一个模块级 store 加一个监听器集合,因此两个消费者每个间隔只产生一次请求而不是两次,并且当最后一个消费者卸载时轮询完全停止。
空闲窗口可从面板调整
面板带有一个设置:空闲窗口,以秒为单位输入。存储的值是毫秒,Host 会校验范围(MIN_IDLE_DISCONNECT_MS…MAX_TIMER_DELAY_MS),但秒才是人能想象的——600 是一个窗口,600000 是一个会被看错的数字。
| 动词 | 效果 |
| GET /lazy-mcp/state | 快照 |
| POST /lazy-mcp/state { "idleDisconnectMs": 1800000 } | 设置窗口,为每个已连接的 server 重新启动截止时间,并持久化 |
| DELETE /lazy-mcp/state | 丢弃覆盖值并恢复配置中的值 |
优先级:面板胜过行配置。 覆盖值持久化在 <cache directory>/settings.json——与元数据缓存相邻,这样插件只拥有一个目录,并复用缓存路径自身的回退链——它能经受重启。"恢复默认"会删除该文件,而不是写入一个空对象,因为文件不存在才是"没有覆盖"的规范表示。
两处刻意的限制:
- 配置中每个 server 的 idleDisconnectMs 不会被改动。 这是刻意的逐 server 决定,因此只有没有该字段的 server 才跟随面板。快照会按行报告 idleDisconnectMsExplicit。
- 重新启动只触及 ready 记录。 休眠的 server 没有可移动的截止时间,给它启动会显示一个并未运行的计时器。
两个动词都不会连接、断开或重新配置 server。请求体限制在 4 KiB 以内,并在写入任何内容之前完成校验。
投递类别
失败的 mcp_call 会报告请求走到了哪一步,因为重试一个可能已经执行过的调用,正是重复扣款或重复发布评论的由来。每条失败消息都带有 [delivery=…] 以及明确的指引。
| 类别 | 含义 | 给模型的指引 |
| not_sent | 请求从未到达 server;什么都没运行 | 修好原因后重试 |
| server_error | server 以 MCP 错误作答 | 工具可能已部分运行;先读错误 |
| may_have_run | 请求已发出,结果未知(超时、取消、传输丢失) | 不要自动重试——确认效果或询问用户 |
may_have_run 失败还会重置连接,因此下一次调用从干净的 transport 开始。
设计上偏保守:在派发之后发生的拒绝会被归为 may_have_run,即使它很可能无害。MCP SDK 的客户端参数校验也落在 may_have_run 而非 not_sent,因为本插件无法证明什么都没发出。宁可悲观是安全的方向。
空闲生命周期
1. 激活时注册三个工具,并把每个 server 标记为 dormant(eager / keep-alive 除外,它们会在后台开始连接)。不启动任何进程,也不打开任何连接。
2. 对某个 server 的第一次 mcp_tools { refresh: true } 或 mcp_call 会连接它并启动空闲计时器。
3. 之后的每次使用都会重新启动计时器。不带 refresh 的 mcp_tools 不会,因为它从不与 server 对话。
4. 计时器触发时,transport/client 被关闭,子进程被释放,server 回到 dormant,缓存的元数据保持完好。
5. 插件卸载或 HMR 会通过 ctx.effect 处置每一个存活连接,并等待任何进行中的缓存写入。
验证
verify.mjs 在一个全新的 Node 进程中用桩 Cordis context 驱动插件真实的 apply(),演练的是实现本身而不是它的副本:
pnpm run verify # or: node verify.mjs
它覆盖空配置;缓存往返(包括"提供缓存并没有重连");全部四种生命周期模式;全部三种投递类别;标准 mcpServers 导入及其优先级、overrides、${VAR} 展开和 Pi 的分钟到毫秒 idleTimeout 转换;容错规则;缓存路径回退;机器上存在真实 Pi 配置时的该配置(否则跳过);以及资源半边(列出、读取、模板替换和二进制投影)。当前结果:76 checks, 0 failures。它通过 npx 启动 MCP reference server,因此首次运行会下载那个包,并且需要网络访问。
CI 在 Linux 和 Windows 上运行 check、lint、pack 以及完整的验证 harness。
旁边还有两个一次性探针,用于回答"这个 server 究竟暴露了什么?":
node probe-capabilities.mjs # tools / resources / prompts capabilities, one server
node probe-resources.mjs # full resource + template surface, plus one read
这个 harness 之所以存在,是因为 DSH Loader 会锁定已导入模块的代号:运行中的 Harness 不重启就无法拾取被编辑过的插件 JavaScript,因此宿主内测试看不到代码改动。见开发说明。
已知限制
- 仅文本结果。 原始 MCP 块会被概括:文本透传,图像/音频/资源变成显式的 [image omitted: …] 标记。官方桥接还会通过附件服务做持久化的图像投影;这里刻意没有重新实现。
- 二进制资源只被报告,不被投递。 blob 正文会变成一条大小提示;字节留在 server 那边。需要它的程序化调用者得用官方桥接。
- 没有重新通知同步。 如果已连接的 server 在会话中途改动工具列表,缓存列表会在下次连接时刷新,而不是通过 list_changed 订阅。资源列表从不缓存,因此总是实时读取。
- 不支持 OAuth。 需要认证的 server 使用静态 headers。
- 没有 prompt 模板、资源订阅或基于任务的执行。
- 没有 directTools。 每个 MCP 工具都通过 mcp_call 到达;没有任何工具作为一等工具、带着自己的 schema 注册进工具目录。
- 没有 includeTools / excludeTools 过滤,也没有 searchKeywords。
- mcp_servers 报告缓存状态,从不探测休眠的 server。
- UI 状态标签是中文。 行为不受影响;欢迎提交本地化 PR。
开发说明
DSH 插件系统的两个行为决定了本包必须如何被开发。
1. 在 workspace 中编写的 bundle 以符号链接方式安装,因此它的依赖必须与源码相邻。 install_bundle 把 workspace 目录链接进 profile 的 node_modules,而 Node 解析的是 realpath,所以它会到源文件旁边去找 node_modules。因此 .npmrc 里要有 node-linker=hoisted:pnpm 默认的隔离布局把依赖放在 node_modules/.pnpm 下,从被符号链接的路径无法到达那里。失败表现是 failed to import / Cannot find package …。安装打包好的 tarball 或从 GitHub 安装可以绕开整个问题。
2. 被编辑的 JavaScript 需要重启 dsh web。 Loader 会锁定已解析的模块代号,因此把该行关掉再打开只会重新应用配置(配置确实会热重载),但会重新导入缓存的模块。改动入口文件路径或包名也打不破这一点。改完就重启。
三个值得记住的缺陷
1. 元数据缓存在生产环境里静默地从未写入。 resolveCachePath 信任 $DSH_PROFILE_DIR / $DSH_HOME,但 Host 进程并不继承它们——那些变量是被注入到shell 工具调用中的,而不是注入到 Host。harness 之所以通过,是因为它显式提供了 cachePath。已通过 ~/.dsh 回退修复,现在由一个先删掉这些变量的测试覆盖。只有实机测试才可能抓到这个问题。
2. 一条空闲断开的记录仍声称拥有实时元数据,于是缓存从未被查询,mcp_tools 会重连而不是做描述。harness 抓到了这一个,但只是因为缓存测试断言的是连接次数,而不只是返回的文本。
3. 一个计时器断言不稳定。 为 6s 的空闲窗口等 8s 在大多数运行中通过,但在负载下偶尔失败。现在每个空闲断言等待的时间都超过其窗口的两倍。与计时器赛跑的测试需要余量,而不是猜测。
这个插件带来的教训:对状态机断言,而不是对渲染文本断言;永远不要信任 Host 可能没有的环境变量;给每个计时器断言留出充裕的余量。
贡献
欢迎 bug 报告、修复和文档补丁——开发环境搭建和仓库不变量见 CONTRIBUTING.md。
许可证
MIT © 2026 zhe97396-tech预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
zhe97396-tech/dsh-lazy-mcp
按需连接 MCP 服务器,空闲自动断开并缓存工具列表
相关插件 · 其他
模型破甲清洗HunYiSage/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 16,993已汉化DeepSeek 客户端
73
女娲蒸馏alchaincyf/nuwa-skill
输入名人名字,提炼其思维模型与决策方式供你调用
★ 37,308已汉化DSH
72
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 888已汉化DSH
71.6
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,759已汉化DSH
71.4
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,270已汉化DSH
70.8
这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。