npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-mcp-workspace-scope

v0.1.1

Published

Per-workspace MCP scoping for the DeepSeek Harness — decide which MCP servers a session may see from the directory it was opened in.

Readme

dsh-mcp-workspace-scope

English | 简体中文

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__<serverName>__<toolName>。于是一个只会碰 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 | "" | 规则文件路径,空 = <DSH home>/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 允许下划线,所以 foofoo__bar 可以并存, 而 mcp__foo__bar__baz 是两者都合法的名字——按第一个 __ 切分会把它判给 foo,导致放行/ 拦截判错。现在按最长匹配归属(有专门用例覆盖)。

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

权限与风险

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

| 触及面 | 具体做了什么 | |---|---| | ctx.tools | 读已注册工具的名字;给单个 agent 装 restrict() + guard()。从不调用任何工具。 | | ctx.loader | 只读遍历已配置的插件树,用来列出 MCP 服务器 | | ctx.reflect | 可选地读 sessionsworkspaceRegistry——会话 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