DeepSeek Harness Hub
← 返回列表

felix-lj-ct/dsh-mcp-workspace-scope

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

每个项目只注入它真正需要的 MCP——偶尔要破例时,在输入框里给这一个会话拨个开关。

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/31 · 已提供中文文档

在 DeepSeek Harness 中按工作区目录限定 MCP 工具注入:在某个项目中打开的会话只能看到该项目所需的 MCP 服务器——它们会从模型的工具列表中移除,并在调用时被拒绝。此外,输入框中还提供按会话切换的开关,可临时收窄或放宽你当前所在会话的范围。

综合分
29.5
GitHub 分
29.5
用户评分
★ Stars
2
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add felix-lj-ct/dsh-mcp-workspace-scope
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/19(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-mcp-workspace-scope

Listed on dsh-plugin.org
npm
license

每个项目只注入它真正需要的 MCP——偶尔要破例时,在输入框里给这一个会话拨个开关。

一个 DeepSeek Harness 插件,按会话打开时所在的目录收窄 MCP 工具注入,
并把「破例」做成对话输入框里的会话级开关。

输入框里的 MCP 作用域药丸,以及每台服务器的会话级开关

功能要点

- 按目录给 MCP server 白名单,子目录自动继承
- 既移除工具列表(省上下文),又在调用时拒绝(硬边界)
- 会话级开关就在输入框里:给当前会话临时收窄或放宽,不动规则文件
- 读数是诚实的:显示每台服务器的运行状态,「允许了但是死的」看得见
- 设置页里可视化编辑规则,保存后立即对正在运行的会话生效

为什么需要它

一个 profile 里的 MCP 服务器只会越攒越多,而它们的工具列表会进到每一个会话——
因为 DSH 里 MCP 是全局的:@deepseek-ai/dsh-mcp-client 把工具注册在根 ctx.tools 上,
名字形如 mcp____。于是一个只会碰 Jira 的会话,上下文里照样背着
三台数据库和一个浏览器驱动,而且随时可能误调。

这个插件按目录把它收窄:在 D:\work\proj-a 里开的会话只注入 atlassian,
在 D:\work\proj-b 里开的只注入 playwright,其余文件夹保持原样。真要破例的时候——
「接下来十分钟我得用一下 bigquery」——输入框上那个药丸本身就是开关,只管这一个会话。

边界

- 变不出没启用的 server:白名单里的 server 必须先在 profile 里是 enabled
(例如用 dsh-skill-mcp-panel 打开)。本插件只能减,不能加。
- 不省进程:被隐藏的 server 照样跑着、照样占内存。要做到「用不到就不启动」,
得把 MCP 行搬进 agent preset,那是另一条路。
- 子智能体是独立判定的:按它自己的工作目录算,而不是继承父会话的限制
(见工作原理)。

安装

dsh plugin --profile web add dsh-mcp-workspace-scope

不想走 npm 的话,直接从源码装:

dsh plugin --profile web add github:felix-lj-ct/dsh-mcp-workspace-scope

然后重启 profile —— 正在跑的实例内存里还是旧代码:

dsh --profile web

cordis.patch.yml 的 bundle 层会自动挂载宿主半区,不需要手改 profile 配置。装完也不会
立刻改变什么:没有规则文件时,所有会话照旧注入全部 MCP(见下)。

规则文件

默认路径 ~/.dsh/mcp-workspace-scope.json($DSH_HOME 生效时跟着走)。
文件不存在 = 插件不生效,所有会话照旧注入全部 MCP —— 装上插件不会改变任何现状。

{
"default": "",
"rules": [
{
"path": "D:/work/master-data-management",
"servers": ["atlassian", "bigquery"]
},
{
"path": "D:/work/frontend",
"servers": ["playwright", "context7"]
},
{
"path": "D:/scratch",
"servers": []
}
]
}

字段语义:

| 字段 | 取值 | 含义 |
| --- | --- | --- |
| default | "" | 未命中任何规则的文件夹:注入全部(默认值,最安全) |
| default | [] | 未命中的文件夹:一个 MCP 都不注入 |
| default | ["a","b"] | 未命中的文件夹:只注入这几台 |
| rules[].path | 目录路径 | 支持 ~/、$DSH_HOME;正反斜杠都行;Windows 上不区分大小写 |
| rules[].servers | 同 default | 该目录(及其子目录)的白名单 |

匹配规则:

- 子目录继承父目录的规则;边界按路径分隔符判断,所以 /ws/proj 不会误匹配 /ws/project。
- 最长路径优先:可以用 /ws 定基线、再用 /ws/proj 覆盖。
- 长度相同的重复路径后写的赢。
- 会话没有 cwd(少数情况)时走 default。
- 规则改动立即对正在运行的会话生效(设置页保存、或直接改文件都会触发重算)。
这是刻意的:一个工作区只有一个可复用的空白会话,DSH 在它没被用过时不让你再建一个,
所以「加完工作区 → 设作用域 → 开始干活」要求规则能落到你正看着的这个会话上。
DSH 本身也是这个语义——在 MCP 面板停用一台 server,HMR 会立刻把它的工具从所有
运行中的会话里卸掉。想要旧的冻结行为,把 applyToRunningSessions 设为 false。

插件配置(可选)

只在需要挪文件位置或改失败策略时才写;加在 profile cordis.patch.yml 对应行的 config: 下:

| 键 | 默认 | 说明 |
| --- | --- | --- |
| rulesPath | "" | 规则文件路径,空 = /mcp-workspace-scope.json |
| enforceGuard | true | 除了隐藏,还在调用时拒绝。建议保持开启(见下) |
| onRulesError | "open" | 规则文件坏了怎么办:open = 全放行(等于插件不存在),closed = 全拦 |
| applyToRunningSessions | true | 规则改动立即重算运行中的会话;false = 每个会话冻结在创建时的规则上 |
| logDecisions | true | 每个会话打一行日志,记录命中了哪条规则、放行了哪些 server |

工作原理

会话在某目录下创建
↓ agent/created
读 session.header.cwd → 最长前缀匹配规则 → 得到白名单
↓
agent.ctx.tools.restrict({ deny: [...不在白名单的 mcp__* 工具] })   ← 从模型可见面移除
agent.ctx.tools.guard(...)                                        ← 调用时拒绝,硬边界
↓ tools/change(server 连上/重连/被卸载)
重算 deny 集合并重挂

两个机制并存不是冗余,而是因为它们的时机不同:

- restrict() 必须在 agent 作用域的 ctx 上调用(根 ctx 调用会被内核拒绝,因为那会屏蔽所有会话),
而且它会校验名字必须是该作用域当前继承到的工具——所以没法为还没连上的 server 预先写 deny。
可见性靠订阅 tools/change 重算来跟上。
- guard() 是调用时求值、不做名字校验,所以它对「刚注册就被调用」这种缝隙天然免疫。

一个已知边界:subagent 不继承父会话的限制。agentPresets.composeFrom() 把子 agent 的
作用域父节点绑到 preset 的 standing scope,而不是父 agent,所以父会话的 restrict() 到不了子 agent。
本插件对 subagent 会按它自己的 session.header.cwd 独立判一次(通常继承父会话目录,结果一致)。

界面

装上之后,Web UI 有两处体现:

1. 设置页「MCP 作用域」(在设置 → MCP 页下方)

规则编辑器:一条默认规则,一个目录整个禁掉,一个目录自定义勾选

- 顶部显示规则文件路径、失败策略(放行/拦下)、是否拦调用;文件不存在时给出提示。
- 默认(未匹配任何规则的目录):全部 / 无 / 自定义三档,自定义时勾选服务器。
- 目录规则:每条一行,目录可直接编辑;服务器选择器里列出 profile 里所有 MCP 服务器,
显示各自的实时工具数,已停用的会标注「已停用」(仍可勾,但它不会有工具)。
- 添加规则:从已有工作区下拉选一个,或手动填路径。
- 保存后由宿主原子写回规则文件;校验失败会把原因原样显示,不会写坏文件。
- 保存后立即重算正在运行的会话(applyToRunningSessions: false 时才需要新建会话,
此时徽标会明确标出「已冻结」以及当前规则会给什么)。

2. 对话页 composer 工具行的 MCP 徽标

无论有没有命中规则都会显示(这是刻意的:一个「没配置就消失」的能力读数无法用来判断
限制到底有没有生效):

- 未命中规则 → MCP 全部
- 命中规则 → MCP atlassian(多个显示 atlassian +1)
- 命中 [] → MCP 无(黄色)

点开后显示:会话目录、命中的是哪条规则、每台服务器的运行状态、以及可见/隐藏的工具数。
工具数读的是该会话 agent 作用域的真实视图,所以是测量值而不是按规则的推算;会话未运行时
会标注「按规则预测」。

在浮层里直接改本会话的作用域

浮层里每行服务器右侧都有一个开关,整行都是点击区域:拨一下即把这台服务器加入/移出当前
会话,也可以用全部 / 无 / 恢复为规则。写入的响应就是新的读数,所以画出来的一定是
宿主真正装上的。

- 临时的、只在内存里。 不写规则文件,随 agent 一起消失——新建会话(以及宿主重启后)
仍然按目录规则来。
- 设了之后规则改动不再影响本会话。 免得你刚拨过的开关被设置页一次保存悄悄撤销;
点「恢复为规则」即归队。
- 可以放宽,不只是收窄——上限是 profile 里已启用的服务器。这个功能存在的场景就是
「接下来十分钟我要用 bigquery」,只能减的控件解决不了。但仍然变不出停用的服务器
(restrict() 只能减,已启用集合是硬上限)。
- 处于覆盖状态时徽标变蓝色并带 :这不是警告,只是提醒你「设置页描述的已经不是本会话」。

会话未运行时没有可限制的 agent 作用域,所以那几行不可点,宿主也会直接拒绝写入(400),
而不是报告一个模型根本没拿到的作用域。

「允许了但用不了」

白名单里放 4 台、其中 2 台在 profile 里是停用的,这时作用域看着对、会话却干不了活。所以每台
服务器都带一个状态点(判定逻辑借鉴 dsh-mcp-live-status,同作者 MIT):

| 状态 | 含义 |
| --- | --- |
| 已连接 | 挂载正常且注册了工具——唯一真正可用的状态 |
| 已启动,未连接 | fiber 是 ACTIVE 但一个工具都没注册(握手没成功) |
| 启动中 / 挂载失败 / 未挂载 / 已停用 | 其余各态 |

为什么必须拿工具去联结:dsh-mcp-client 默认 failOnStartupError: false,连不上的
server 其 fiber 照样是 ACTIVE*,光看挂载状态分不出「活着」和「起来了但是死的」;而
mcp-client 只有在 connect() 与 listTools() 都成功后才注册工具,所以工具注册才是握手成功的证据。

于是徽标会在「已允许但当前不可用」时变黄并加 •,浮层里列出具体是哪几台;白名单里写了
profile 中不存在的名字(拼错、或该服务器已被删)时变红加 !。

顺带修了一个隐蔽的归属 bug:serverName 允许下划线,所以 foo 与 foo__bar 可以并存,
而 mcp__foo__bar__baz 是两者都合法的名字——按第一个 __ 切分会把它判给 foo,导致放行/
拦截判错。现在按最长匹配归属(有专门用例覆盖)。

注意与 dsh-mcp-live-status 的区别:那个插件读的是全局视图(进程里哪台 server 连上了),
所以它始终显示全部已启用的服务器;本插件在此之上叠加「本会话允许哪些」。两者测的不是同一件
事,同时装不冲突,本插件也不依赖它。

权限与风险

这个插件只会减少一个会话的能力,永远不会增加。它能放行的东西必须已经在 profile 里启用;
服务器的启停与配置仍然归设置页管,这里做不到。

| 触及面 | 具体做了什么 |
|---|---|
| ctx.tools | 读已注册工具的名字;给单个 agent 装 restrict() + guard()。从不调用任何工具。 |
| ctx.loader | 只读遍历已配置的插件树,用来列出 MCP 服务器 |
| ctx.reflect | 可选地读 sessions 和 workspaceRegistry——会话 cwd 与已知工作区路径,供读数和路径选择器用 |
| ctx.webServer | /dsh-mcp-workspace-scope 下三条本地 JSON 路由:读状态、读某会话作用域、写规则或会话级覆盖 |
| 网络 | 无任何外发。浏览器半区只 fetch 上面那几条本地路由。 |
| 存储 | 只有一个文件:规则文件(默认 ~/.dsh/mcp-workspace-scope.json),原子写入,且只在你点保存时写。 |

真正需要留意的失效方式是规则比你以为的更严:会话悄悄少了工具,而模型只会说「我做不到」,
不会说「我没被允许」。这正是输入框那个药丸存在的理由——它报的是会话实际拿到什么,
读自 agent 自己的视图。规则文件损坏时默认放行全部(onRulesError),所以一个拼写错误
不会把正在干活的会话废掉;想反过来就设成 closed。

不接触任何凭据。 插件全程只处理服务器名字和工具名字,
从不读 MCP 服务器的命令行、参数或环境变量。

开发

npm install
npm run build     # tsc → dist/(dist 随仓库提交,见 .gitignore 里的原因)
npm test          # 24 个冒烟用例,用假 harness 跑,不需要 DSH

冒烟测试复刻了 ToolRuntime 的三个关键行为(全局视图不受作用域限制影响、restrict()
会对未知名字抛错、restrict() 及其 disposer 都会触发 tools/change),这三条任何一条搞错,
在生产里都是静默失效。测试里还假了一个 webServer,因此 JSON 路由(包括会话级覆盖)是
端到端跑通的;另有一个用例在无 web server 的情况下运行,确保收窄本身从不依赖它。

License

MIT

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

同作者(felix-lj-ct)的其他插件

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群