按需搜索启用并调用 MCP 服务器,常驻上下文省九成
30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/1 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add NeoXider/neoxider-mcp-hub该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 6 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(unknown · 2026/11/1) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意要求 Node:>=22.19(dsh 本身要求 Node ≥ 22.19)未发布到 npm registry,仅可从源码安装
展开查看检查明细 ↓
>=22.19(dsh 本身要求 Node ≥ 22.19)以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包@neoxider/mcp-hub(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22.19 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 06:05:38
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 55 处,展示前 53 条)
- 读取本地文件bench/accuracy.mjs:44
const { servers: FULL } = JSON.parse(readFileSync(path.join(benchDir, "snapshots", "full-tools.json"), "utf8")); - 发起网络请求bench/accuracy.mjs:122
const response = await fetch(`${BASE_URL}/chat/completions`, { - 写入本地文件bench/accuracy.mjs:327
writeFileSync(path.join(benchDir, "accuracy.json"), `${JSON.stringify(report, null, 2)}\n`, "utf8"); - 读取本地文件bench/capture-distractors.mjs:68
if (existsSync(outFile) && !process.argv.includes("--force")) { - 写入本地文件bench/capture-distractors.mjs:85
mkdirSync(path.dirname(outFile), { recursive: true }); - 写入本地文件bench/capture-distractors.mjs:86
writeFileSync(outFile, `${JSON.stringify({ servers, skipped }, null, 2)}\n`, "utf8"); - 读取本地文件bench/capture-schemas.mjs:49
if (existsSync(outFile) && !process.argv.includes("--force")) { - 写入本地文件bench/capture-schemas.mjs:59
mkdirSync(path.dirname(outFile), { recursive: true }); - 写入本地文件bench/capture-schemas.mjs:60
writeFileSync(outFile, `${JSON.stringify({ servers }, null, 2)}\n`, "utf8"); - 写入本地文件bench/dynamic-proof.mjs:134
writeFileSync( - 读取本地文件bench/head-to-head.mjs:46
const answering = JSON.parse(readFileSync(path.join(benchDir, "snapshots", "full-tools.json"), "utf8")).servers; - 读取本地文件bench/head-to-head.mjs:55
const distractors = existsSync(distractorFile) - 读取本地文件bench/head-to-head.mjs:57
Object.entries(JSON.parse(readFileSync(distractorFile, "utf8")).servers).filter( - 发起网络请求bench/head-to-head.mjs:128
const response = await fetch(`${BASE_URL}/chat/completions`, { - 发起网络请求bench/head-to-head.mjs:232
const response = await fetch(`${BASE_URL}/embeddings`, { - 读取本地文件bench/head-to-head.mjs:422
if (!existsSync(REPORT_PATH)) return null; - 读取本地文件bench/head-to-head.mjs:424
const saved = JSON.parse(readFileSync(REPORT_PATH, "utf8")); - 写入本地文件bench/head-to-head.mjs:444
const save = () => writeFileSync(REPORT_PATH, `${JSON.stringify(report, null, 2)}\n`, "utf8"); - 读取本地文件bench/measure.mjs:57
if (existsSync(snapshot)) { - 读取本地文件bench/measure.mjs:58
const cached = JSON.parse(readFileSync(snapshot, "utf8")); - 写入本地文件bench/measure.mjs:64
mkdirSync(snapshotDir, { recursive: true }); - 写入本地文件bench/measure.mjs:65
writeFileSync(snapshot, `${JSON.stringify(result, null, 2)}\n`, "utf8"); - 读取本地文件bench/measure.mjs:121
const template = JSON.parse(readFileSync(path.join(benchDir, "catalog.json"), "utf8")); - 写入本地文件bench/measure.mjs:128
mkdirSync(snapshotDir, { recursive: true }); - 写入本地文件bench/measure.mjs:129
writeFileSync(scalingCatalog, JSON.stringify({ version: 1, entries }), "utf8"); - 删除本地文件bench/measure.mjs:141
rmSync(scalingCatalog, { force: true }); - 写入本地文件bench/measure.mjs:226
writeFileSync(path.join(benchDir, "results.json"), `${JSON.stringify(report, null, 2)}\n`, "utf8"); - 读取本地文件src/catalog.ts:23
return JSON.parse(await readFile(filePath, "utf8")); - 写入本地文件src/catalog.ts:36
await mkdir(path.dirname(filePath), { recursive: true }); - 写入本地文件src/catalog.ts:38
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, "utf8"); - 写入本地文件src/catalog.ts:39
await rename(temporaryPath, filePath); - 读取本地文件src/catalog.ts:146
const candidate = await realpath(this.resolveTemplate(entry.skill.path, {})); - 读取本地文件src/catalog.ts:150
return await realpath(this.resolveTemplate(root, {})); - 读取本地文件src/catalog.ts:185
names = await readdir(directory); - 写入本地文件src/catalog.ts:220
await rename(pendingPath, path.join(this.stateDir, "pending", `${proposalId}.approved.json`)); - 读取本地文件src/client.ts:73
const raw = inputFile ? await readFile(path.resolve(inputFile), "utf8") : inline; - 执行外部命令来自配置/变量src/harness-smoke.ts:478
const child = spawn(command, args, { - 读取本地文件src/harness-smoke.ts:511
entries = await readdir(directory, { withFileTypes: true }); - 读取本地文件src/harness-smoke.ts:548
return decompressJsonLines(await readFile(file)); - 写入本地文件src/harness-smoke.ts:584
await mkdir(path.dirname(filePath), { recursive: true }); - 写入本地文件src/harness-smoke.ts:586
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, "utf8"); - 写入本地文件src/harness-smoke.ts:587
await rename(temporary, filePath); - 读取本地文件src/harness-smoke.ts:594
JSON.parse(await readFile(path.join(root, "examples", "catalog", `${name}.proposal.json`), "utf8")), - 读取本地文件src/harness-smoke.ts:607
await access(playwrightCli); - 写入本地文件src/harness-smoke.ts:614
await mkdir(outputDir, { recursive: true }); - 读取密钥环境变量src/harness-smoke.ts:726
const explicitModelKey = process.env.CAPABILITY_HUB_SMOKE_MODEL_KEY; - 写入本地文件src/harness-smoke.ts:756
await mkdir(profileDir, { recursive: true }); - 写入本地文件src/harness-smoke.ts:757
await writeFile( - 读取密钥环境变量src/harness-smoke.ts:834
LMSTUDIO_API_KEY: process.env.LMSTUDIO_API_KEY ?? "lm-studio-local", - 删除本地文件递归src/harness-smoke.ts:1008
await rm(tempHome, { recursive: true, force: true }).catch(() => undefined); - 读取本地文件src/manager.ts:563
const { size } = await stat(skillPath); - 读取本地文件src/manager.ts:567
const content = await readFile(skillPath, "utf8"); - 读取本地文件src/server.ts:36
const { version } = JSON.parse(await readFile(path.join(packageDir, "package.json"), "utf8")) as { version: string };
扫描于 2026/11/4(0 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-tools@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 按需搜索MCP服务器
- 启用并调用MCP服务器
- 常驻上下文省九成
一个 MCP 工具顶掉你所有 schema——懒加载能力代理,常驻工具上下文实测砍掉 94.4%。按需搜索、查看、启用并调用 MCP 服务器和技能。
README
由 DeepSeek 最新模型翻译生成NeoXider MCP Hub
提示词里只放一个工具,不用塞进所有 schema。MCP 服务器和技能同处一个懒加载、需人工批准的目录——任何 MCP 客户端都能用。
快速开始 ·接入客户端 ·统一目录 ·实测结果 ·对比 ·更新日志
每配一个 MCP 服务器,它的全部工具 schema 都会进入每一次提示词,不管当前任务用不用得上。配十个服务器,模型还没读你的问题,就要先花掉大约 15,000 token;工具列表一拥挤,模型选得也更差。
NeoXider MCP Hub 是一个 stdio MCP 服务器,只暴露一个工具:capability_hub。模型先搜一份轻量目录,查看权限,唤醒需要的那一个服务器,调用它,再把它关掉。技能也放在同一份目录里,一次只加载一个,选中才加载。
| 常驻工具只有一个 | 不管配了多少服务器,宿主只占约 420 token。 |
| MCP 服务器和技能放在一起 | 一份目录、一个 search、一套信任模型。服务器按需启动;技能正文一次加载一个。 |
| 真正停掉,不只是藏起来 | 一项能力就是一个进程,模型不请求就一直关着。disable 会把它停掉。 |
| 人工批准 | 模型可以提议某个服务器或技能。只有模型之外的人才能批准。
展开完整文档(README 剩余 1 节)
NeoXider MCP Hub
提示词里只放一个工具,不用塞进所有 schema。MCP 服务器和技能同处一个懒加载、需人工批准的目录——任何 MCP 客户端都能用。
快速开始 ·接入客户端 ·统一目录 ·实测结果 ·对比 ·更新日志
每配一个 MCP 服务器,它的全部工具 schema 都会进入每一次提示词,不管当前任务用不用得上。配十个服务器,模型还没读你的问题,就要先花掉大约 15,000 token;工具列表一拥挤,模型选得也更差。
NeoXider MCP Hub 是一个 stdio MCP 服务器,只暴露一个工具:capability_hub。模型先搜一份轻量目录,查看权限,唤醒需要的那一个服务器,调用它,再把它关掉。技能也放在同一份目录里,一次只加载一个,选中才加载。
| 常驻工具只有一个 | 不管配了多少服务器,宿主只占约 420 token。 |
| MCP 服务器和技能放在一起 | 一份目录、一个 search、一套信任模型。服务器按需启动;技能正文一次加载一个。 |
| 真正停掉,不只是藏起来 | 一项能力就是一个进程,模型不请求就一直关着。disable 会把它停掉。 |
| 人工批准 | 模型可以提议某个服务器或技能。只有模型之外的人才能批准。 |
| 任何 MCP 客户端 | 就是个普通 stdio 服务器:OpenCode、Claude Code、Claude Desktop、Cursor、DeepSeek Harness。 |
| 实测,含损耗 | 上下文、准确率、正面对比和动态验证都是 bench/ 里可复现的脚本——Tool Search 胜出的那一项也一并公开。 |
左:每轮对话里,所有 schema 都常驻。右:只留一个工具,其余按需唤醒。
工作原理
flowchart LR
client["MCP clientOpenCode · Claude Code · Cursor · DSH"] -- "one tool: capability_hub" --> hub{{"Capability Hub"}}
hub --> catalog[("CatalogMCP servers + skills")]
hub -. "enable on demand" .-> s1["MCP server A"]
hub -. "enable on demand" .-> s2["MCP server B"]
catalog -- "skill.load" --> skill["SKILL.md"]
sequenceDiagram
participant M as Model
participant H as capability_hub
participant S as MCP server
M->>H: search "web research"
H-->>M: web-search-neo (stopped)
M->>H: tools web-search-neo, query "navigate"
H->>S: start process
S-->>H: tools/list
H-->>M: names and descriptions, no schemas
M->>H: call web_action {...}
H->>S: tools/call
S-->>H: result
H-->>M: result (paged when long)
M->>H: disable web-search-neo
H->>S: stop process
search → inspect → enable → tools → call → disable
- 提示词里只留一份固定 schema。子工具的 schema 在请求之前不进模型上下文。
- tools 默认返回名称和描述;完整 schema 需要显式开启。它会自己启动服务,所以 enable 可选。
- MCP 进程懒启动,生命周期只跟 hub 进程一致。
- 技能靠元数据发现,一次加载一个。
- 第三方新增内容进入人工审批队列;模型无法批准可执行代码。
快速开始
要求:Node.js 22.19+ 和 pnpm。
git clone https://github.com/NeoXider/neoxider-mcp-hub.git
cd neoxider-mcp-hub
pnpm install --frozen-lockfile
pnpm test
pnpm client -- --action search --query demo
默认目录只含一个内置的 echo MCP 和一个示例 ML 技能。测试不会下载或执行第三方包。
客户端把简单请求当普通参数处理(--action、--query、--kind、--name、--tool)。带参数的请求要写进文件,用 pnpm client -- --input request.json 提交,因为 Windows PowerShell 5.1 会剥掉 --json 内联 JSON 里的双引号。
接入你的客户端
hub 是普通的 stdio 服务。从 npm 启动即可,不用克隆也不用构建:
npx -y @neoxider/mcp-hub --catalog /abs/path/catalog.json --state /abs/path/state
把自己的目录和状态放在长期保留的文件夹里。包内自带的 data/ 只放演示条目,在 npx 下它位于缓存中,缓存可能被清掉,你的审批记录也会一起没。新目录可以从空开始:
{ "version": 1, "entries": [] }
从本地检出运行(先执行 pnpm build),把 npx -y @neoxider/mcp-hub 换成 node /abs/path/neoxider-mcp-hub/dist/src/server.js;此时不带 --catalog 和 --state,就会使用检出目录自己的 data/ 文件夹。两种方式都要用绝对路径。
OpenCode
opencode.json:
{
"mcp": {
"capability_hub": {
"type": "local",
"command": [
"npx", "-y", "@neoxider/mcp-hub",
"--catalog", "/abs/path/catalog.json",
"--state", "/abs/path/state"
],
"timeout": 360000,
"enabled": true
}
}
}
启动子服务器的调用可能要等一会儿;如果客户端会中断这类调用,就调大 timeout。
Claude Code
claude mcp add capability_hub -- npx -y @neoxider/mcp-hub --catalog /abs/path/catalog.json --state /abs/path/state
每个示例都把服务器命名为 capability_hub,用下划线。客户端会给 MCP 工具加上服务器名前缀,这样各处显示的名字就统一了;模型在某个客户端见过这个工具,换到另一个客户端也不会去找 capability-hub。
Claude Desktop、Cursor 及其他 mcpServers 客户端
{
"mcpServers": {
"capability_hub": {
"command": "npx",
"args": ["-y", "@neoxider/mcp-hub", "--catalog", "/abs/path/catalog.json", "--state", "/abs/path/state"]
}
}
}
在 Windows 上,如果客户端无法直接启动 npx,就要写成 "command": "cmd",并配上 "args": ["/c", "npx", "-y", "@neoxider/mcp-hub", …]。
用同一个包、同样的 --catalog 和 --state 批准提案:
npx -y -p @neoxider/mcp-hub capability-hub-admin approve <proposal-id> --catalog /abs/path/catalog.json --state /abs/path/state --yes
DeepSeek Harness
安装已发布的 bundle——不用改路径,也不用在本机构建:
dsh plugin --profile web add https://github.com/NeoXider/neoxider-mcp-hub/releases/download/v0.10.3/neoxider-mcp-hub-0.10.3.tgz
重启 Harness。hub 在宿主进程内运行,对外是一个原生工具 capability_hub,直接面向模型。catalog 和 state 默认使用安装包的 data/ 目录;想放到别处,就在配置档的 cordis.patch.yml 里设置该行的 catalogPath / stateDir 配置。toolCallTimeoutMs 把单次调用的上限从默认的 300,000 ms 调高,pageChars 设置分页大小。
已构建的检出不用打包也能同样加载:按路径插入编译后的入口。插件运行时不会从 Harness 导入任何东西,所以放在配置档的 node_modules 之外也能用。
- insert:
- id: capability-hub
name: /abs/path/neoxider-mcp-hub/dist/src/dsh-plugin.js
config:
catalogPath: /abs/path/catalog.json
stateDir: /abs/path/state
优先用原生工具搭配本地模型。另一种方案是走内置 MCP 客户端的 stdio 子进程:先构建 hub,再把 examples/dsh/cordis.patch.yml 合并进当前生效的 Harness Web 配置档,并调整仓库的绝对路径。之后 Harness 会把工具暴露为 mcp__capability_hub__capability_hub,这个名字很长,而且重复了自己。小模型有时会拼错,比如写成 mcp__capability-hub__…,而只认已声明工具名的服务器会把整次调用当成纯文本返回,于是调用永远不会执行。
原生插件也能挽回这类调用。当模型回复里没有工具调用,而文本结尾是一段对 hub 的书面调用,且拼写属于以下任一种(<tool_call>,Qwen 的 XML 或 JSON 形式),插件就会把它转成真正的 capability_hub 调用。因此,记得 MCP 名字的旧会话照样能用,提示词里也不用加别名工具。引用的示例、被截断的调用、对其他工具的调用,都原样保留为文本。
在多个客户端之间共享同一份目录
每个客户端都会启动自己的 hub 进程,但目录和审批记录都是普通文件。给每个客户端传相同的参数,它们看到的能力和审批就一致:
node dist/src/server.js --catalog /abs/path/catalog.json --state /abs/path/state
审批一次服务器,所有使用该 --state 的客户端都能启动它。想让某个客户端的审批单独存放,就给它单独的 --state。
然后这样发起:
{"action":"search","query":"web research"}
一份目录,同时管 MCP 服务器和技能
One search, two kinds of capability.
目录条目要么是 MCP 服务器,要么是技能。两者都用同一个 search 查找,用同一个 inspect 查看,也走同一个审批步骤。
{
"kind": "mcp",
"name": "web-search-neo",
"description": "Web research and visible Chrome automation without API keys.",
"tags": ["web", "search", "browser"],
"trusted": true,
"transport": { "type": "stdio", "command": "node", "args": ["server.js"] }
}
{
"kind": "skill",
"name": "ml-experiment-review",
"description": "Review an ML experiment as a falsifiable, reproducible evidence package.",
"tags": ["ml", "review"],
"trusted": true,
"skill": { "type": "file", "path": "${packageDir}/data/skills/ml-experiment-review/SKILL.md" }
}
技能不需要 enable。找到它,读它的权限,再加载:
{"action":"search","kind":"skill","query":"review"}
{"action":"inspect","name":"ml-experiment-review"}
{"action":"skill.load","name":"ml-experiment-review"}
正文返回为 <skill_content name="…"><skill_instructions>…,模型据此工作。它从不进入系统提示词,所以没用到的技能不占开销。
- MCP 传输方式:stdio 和 streamable-http。
- 模板:${catalogDir}、${packageDir},以及显式加入允许列表的 ${config:key} 值。
- 密钥:只接受环境变量引用。形似密钥的模型配置键会被拒绝。
- 技能:经批准的本地 Markdown 文件,上限 256 KiB。解析后的路径必须留在 catalog 或 package 目录下;外部目录需要显式审核过的 skill.allowedRoots 条目。
- 状态:运行时配置和提案由 Git 忽略。
人工把关的安装
模型提出提案。由人在模型之外批准。
模型创建的提案存放在 data/state/pending 下,无法执行。要从另一条由人操作的命令批准:
node dist/src/admin.js approve <proposal-id> --catalog .\data\catalog.json --state .\data\state --yes
不带 --catalog 和 --state 时,admin 使用与服务端相同的默认值(包的 data/ 文件夹),所以在任何目录下输入的批准都会落到服务端读取的位置。然后调用 catalog.reload 和 enable。优先使用固定版本的包或不可变的 Git 修订;批准的条目里避免用浮动的 latest 安装器。删除条目,或清除它的 trusted 标记,再调用 catalog.reload,也能停掉已在运行的服务端。
已附上可直接审核的提案:Web Search Neo(动态网络调研与浏览器工具)和 Unity CLI MCP(官方 Unity CLI 传输方式;只有连接了带 Unity Pipeline 的 Unity Editor 时,它的工具列表才会填充)。它们是示例,不是默认静默信任的条目:批准前先审核路径、版本和权限。
面向模型的契约
公开 schema 刻意保持扁平,好让 LM Studio 这类受限解码引擎能可靠编译。它只用每个客户端都接受的关键字:type、properties、required、enum、布尔型 additionalProperties 和描述。长度限制在输入时检查,不通过 maxLength 声明。调用参数以结构化的 arguments 对象传递;其余载荷(配置、提案)以 JSON 字符串传递。
{"action":"tools","name":"web-search-neo","query":"search"}
{
"action": "call",
"name": "web-search-neo",
"tool": "web_info",
"arguments": {"topic": "search_status"}
}
| Action | 用途 |
| search | 搜索精简的能力元数据;kind 可筛选为 mcp 或 skill |
| inspect | 查看单个能力,包括它的权限、配置和环境状态 |
| configure | 通过 payloadJson 设置白名单内的非敏感值 |
| enable / disable | 启动或停止一个受信任的 MCP |
| tools | 列出子工具;query 缩小列表范围,schema 仍为可选 |
| call | 通过 arguments 对象代理一次子工具调用(payloadJson 也可以,但两者不能同时使用) |
| skill.load | 加载一个已批准的本地技能正文 |
| propose | 从 payloadJson 保存一条不受信任的提议 |
| proposals | 列出待处理的提议 |
| catalog.reload | 重新加载已批准的目录状态 |
| page | 读取长调用结果的下一页,cursor 会打印在最后一行的位置 |
| status | 列出正在运行的能力 |
两个细节能帮你省一次往返:
- call 里的工具名写错时,会返回该服务器上最接近的名字(did you mean "web_info", "web_action"?)。
- tools 查询按词匹配,不匹配整句:"browser click" 能找到 browser_click。如果没有工具包含全部词,包含部分词的工具会按匹配度从高到低返回,并附上说明。
实测结果
下面的内容全部由 bench/ 里的脚本生成,原始输出也已提交,所以每张表都能在没有网络的情况下重新推导:逐工具测量结果在 bench/snapshots/,token 报告在 bench/results.json,准确率报告在 bench/accuracy.json,对比结果在 bench/head-to-head.json,生命周期回执在 bench/dynamic-proof.json。完整说明、失败分析和先前工作在 docs/context-economy.md。
上下文节省
bench/measure.mjs 通过 stdio 启动每个真实、已发布的 MCP 服务器,请求 tools/list,统计宿主为每个工具注入的那段 JSON(name + description + inputSchema)的 token 数(o200k_base)。hub 用同样的方式测量:启动它,读取它自己的 tools/list。
pnpm bench
| 服务器 | 用途 | 工具数 | 上下文 token |
| @modelcontextprotocol/server-everything | MCP 参考服务器 | 13 | 1,075 |
| @modelcontextprotocol/server-memory | 知识图谱记忆 | 9 | 891 |
| @modelcontextprotocol/server-sequential-thinking | 结构化推理 | 1 | 851 |
| @playwright/mcp | 浏览器自动化 | 24 | 3,383 |
| 合计 — 经典 MCP | 四个服务器,常驻 | 47 | 6,200 |
| 合计 — Capability Hub | 一个代理工具 | 1 | 422 |
常驻开销下降 93.2%,即 14.7 倍。 这部分提示词开销每一轮都要付,不管任务是否用到工具。
目录越大,节省越多无论你配置多少服务器,hub 都只发布一份 schema。所以传统方案那一侧随服务器数量线性增长,而 hub 这一侧每新增一条只多一行目录说明,一旦列表退化成只有名字,就不再增长。下表中每个 hub 数字都是拿真实服务器跑对应规模的目录测出来的,不是估算:
| 配置的服务器数 | 传统方案 token | hub 常驻 | 节省 |
| 4 | 6,200 | 430 | 93.1% |
| 10 | 15,500 | 584 | 96.2% |
| 20 | 31,000 | 826 | 97.3% |
| 30 | 46,500 | 468 | 99.0% |
| 60 | 93,000 | 595 | 99.4% |
30 台那一行掉下来,是降级机制生效了:完整描述已经塞不进预算,列表退回只留名字,常驻开销大约减半。
诚实的另一半:hub 在运行时花多少
hub 不是免费的:传统方案一次性预付的成本,hub 改在运行时付,也就是智能体真正打开某项能力的时候。不存在单一的「每任务」数字——这份 README 的早期版本发布过一个,而那是把最贵的路径当成了典型情况。三种场景:
| 场景 | 路径 | hub token | 对比 6,200 |
| 空闲——任务不需要任何能力 | 只有常驻 schema | 422 | 节省 93.2% |
| 直接——任务打开一项能力 | search + 带查询的 tools | 554 | 节省 91.1% |
| 谨慎——还要查看权限、读完整列表 | search + inspect + enable + tools | 1,195 | 节省 80.7% |
「直接」这一行仍然算了一次 search,而内联的目录列表往往让这次调用没必要——这是保守选择,因为偏差应该偏向对我们自己不利的方向。
「直接」路径便宜有两个原因,它们本来就在代码里,只是跑基准时被忽略了:tools 会自己启动服务器,所以 enable 不在关键路径上;tools 接受查询,智能体已经知道要什么时,只花 60 token 而不是 558:
{"action":"tools","name":"playwright","query":"click"}
盈亏平衡点大约是单个会话内 47 次直接发现,如果智能体每次都走谨慎路径,则是 8 次。低于这个数 hub 更划算,高于它静态配置更便宜。所以当你有很多服务器、而每个任务只碰其中少数几个时,hub 是合适的取舍;每个任务都要用上你所有工具时,它就不合适。
有三项设计决策直接来自这些测量:
- enable 以前会返回完整工具列表,而文档写明的下一步是 tools——于是工作流为同一份列表付了两次钱,1,567 token 而不是 780。现在 enable 只返回一个计数和指向下一步动作的提示。
- 面向模型的 JSON 采用紧凑序列化。缩进不是信息,同样的载荷做美化打印实测多出 31% token。
- 能力列表放在工具描述里,而不是藏在一次 search 调用后面——这样换来了什么,见下面的准确率表。
工具选择准确率
模型要是挑错工具,省再多上下文也没用。所以这一项也测了:跑本地 Qwen3.8-27B,temperature 设为 0,共 28 个任务,答案已知。其中 6 个根本不需要调用工具,调了就算失败。
pnpm bench:accuracy
| 条件 | 常驻 token | 总体 | 无工具任务 | 误调用 | 平均轮数 | 平均提示词 |
| classic — 47 个 schema 常驻 | 6,200 | 96.4% | 83.3% | 1 | 1 | 7,269 |
| hub,目录写得含糊 | 422 | 82.1% | 83.3% | 1 | 3.00 | 2,818 |
| hub,列表不内联 | 328 | 85.7% | 100% | 0 | 3.54 | 3,047 |
| hub,出厂配置 | 533 | 96.4% | 100% | 0 | 1.96 | 1,873 |
常驻上下文只有十二分之一,准确率却一样,总 token 还更少。 每个任务 1,873 个提示词 token,classic 是 7,269,统计的是多轮协议里每一轮的总和。按说中间加一层代理要多跑几个来回,该吃点亏,结果没出现。
classic 配置唯一那次失败值得点名:问它 "Is 97 a prime number?",常驻 47 个工具的模型伸手去调了 sequentialthinking。hub 这边只要目录能用,无工具任务全是 100%。
「目录写得含糊」那行,代码一样,服务端一样,只有描述不同。目录要写得让人找得到,这一项值大约 14 个百分点。
hub 那几行是多轮跑真实子进程,边缘任务每次跑会浮动几分。跑了三轮,classic 每次都稳定在 96.4%,出厂的 hub 在 96.4%–100% 之间;两个消融行始终低于这两者,从没高过。
和 Tool Search 正面比
上面比的都是静态配置,这证明不了它比别的懒加载方案更好。所以那些也测了:同样 28 个任务,同一个模型,另加一个 tool_search 条件,按 Anthropic 的 Tool Search Tool 和 Claude Code 的 MCP Tool Search 的做法搭,带真正的语义检索。
pnpm bench:head-to-head
| 98 个工具 | 常驻 token | 总体 | 无工具 | 误调用 | 平均提示词 |
| classic | 12,422 | 92.9% | 83.3% | 1 | 14,701 |
| toolSearch | 75 | 92.9% | 100% | 0 | 1,384 |
| hub | 709 | 92.9% | 100% | 0 | 2,125 |
实话实说:准确率打平,Tool Search 的设计更紧凑。 常驻 75 个 token,hub 是 709,而且它不随目录变大,因为常驻面就是一个查询字符串。本项目的常驻面带一个 action 枚举、一个 payload 字段,还有内联的能力列表。
两种懒加载方案都赢过静态列表的地方在于:提示词 token 大约只有五分之一,6 个「正确答案是什么都不调」的任务全对,classic 是 83.3%。
这个 broker 仍有立足之地,靠的是这些数字都没覆盖的一个维度——它让能力保持停止状态,而不只是藏起来;它还承载权限、配置和人工审批,这些是搜索工具不关心的。这个对比还有一处对我们有利的不公平,文章里已经写明:Tool Search 索引假定每个服务器都已被枚举,而且它的嵌入模型不计成本。
它确实是动态的证据
表格展示的是模型不必背负的东西。下面展示另一半——一个启动时没人加载的能力,可以按意图找到、打开、用于真实的工具调用,然后再关掉。其中没有任何模拟:子进程就是已发布的 @playwright/mcp 包。
pnpm proof
host-visible tools capability_hub
search (by intent) 72 tokens playwright found, enabled=false
inspect (permissions) 121 tokens permissions listed, still stopped
enable (starts process) 22 tokens real child process, 24 tools live
tools (schemas withheld) 558 tokens names + descriptions, schemasIncluded=false
tools (narrowed by query) 60 tokens matched 1 of 24
tools (one schema, opt-in) 144 tokens schema returned only when asked
call (real child tool) 107 tokens browser_navigate executed
disable (stops process) 11 tokens wasEnabled=true
search (after disable) 72 tokens enabled=false again
每一步都有断言,不只是打印:只要暴露给宿主的工具多于一个、能力在 enable 之前就报告自己在运行、默认 tools 列表里出现子进程的 schema、includeSchema 被忽略、查询没有收窄列表,或者 disable 之后能力仍标记为运行,这次运行就会失败。
与静态配置的对比才是重点:同样这 24 个 Playwright 工具,在每一轮的每个提示词里都要占 3,383 个常驻 token,不管任务是否碰浏览器。在这里,模型不开口就一分不花;24 个工具的名字只花一次 558 token——如果它已经知道自己要什么,就只花 60。
对比
懒加载工具的想法并不新鲜,好几个项目都做了这个 hub 的部分工作。下表依据各项目自己的 README 和仓库页面,整理于 2026-10-27;· 表示 README 没有提到,不等于「做不到」。
| 项目 | ★ | 是什么 | 懒加载工具 schema | 技能 | 新服务器需要人工 |
| NeoXider MCP Hub(本项目) | 1 | 一个 capability_hub 工具架在目录之上;stdio 服务器,外加原生 DeepSeek Harness 插件 | 是,只常驻一个工具 | 是,在同一个目录、同一个 search 里 | 是,提案队列在模型之外审批 |
| Docker MCP Gateway | 1.6k | 网关兼命令行插件;目录;容器内运行服务;密钥处理 | 动态工具发现 | · | ·(改用容器隔离) |
| Toolport | 233 | 桌面应用兼本地网关;一次配置,所有智能体通用;系统钥匙串 | 为发送全部 schema 的智能体提供工具搜索 | 管理每个智能体加载哪些技能和规则 | 执行破坏性调用前先询问;锁定工具定义 |
| 1MCP agent | 524 | 聚合器,对外只暴露一个端点 | 可选懒加载模式:tool_list、tool_schema、tool_invoke | · | · |
| ravitemer/mcp-hub | 524 | 管理器:启动、停止、监控服务 | · | · | · |
| lazy-mcp | 114 | 在分类树上提供两个元工具 | 支持 | · | · |
| mcp-tool-search | 7 | search_tools(模糊匹配)和 call_tool | 支持 | · | · |
| mcp-lazy-proxy | 2 | 两个元工具;用「收藏」让选中的 schema 常驻 | 支持 | · | · |
| skills-radar | 6 | search_skills 和 load_skill | 仅技能 | 支持,仅技能 | · |
| dsh-mcp-lazy-tools | 2 | DeepSeek Harness 插件:隐藏 schema,每个智能体解锁一个服务 | 支持 | · | · |
| Master-MCP | 9 | 代理,带 schema 编辑器和可选搜索 | 可选搜索 | 已在路线图上 | · |
| MetaMCP、IBM ContextForge | 2.7k、4.7k | 自托管聚合器和网关 | · | · | · |
| Anthropic Tool Search Tool | API | Claude API 上的服务端 defer_loading,最多支持 10,000 个工具 | 支持,限受支持的模型 | 独立功能(Agent Skills;名称和描述仍留在系统提示词里) | 不适用 |
这里的差异在于组合:懒加载 schema 和懒加载技能来自同一份目录,任何能执行的操作都有人工把关,且任何 MCP 客户端都能用。没有差异的地方:这个想法本身,而且论常驻体积的原始大小,Tool Search 更小(见正面对比)。
以下情况请另选方案:
- 你想要带钥匙串、按智能体划分访问集、能锁定工具定义的桌面应用 —— Toolport;
- 你想让每个服务都跑在自己的容器里,密钥由系统托管 —— Docker MCP Gateway;
- 你运行多用户、自托管的网关 —— MetaMCP 或 IBM ContextForge;
- 你只调用 Claude API,想要尽可能小的常驻体积 —— Tool Search Tool;
- 你只需要懒加载的技能,别的都不用——skills-radar。
这里如果有错或过时了?开个 issue;修正后的表格欢迎提 pull request。
设计上对提示词缓存友好
一个中枢,任意客户端,一份共享目录。
Strata、llama.cpp 这类本地服务器会复用提示词里最长的一段未变开头,所以只要改写了对话靠前的部分,它们就得把后面全部重读一遍。中枢让自己那部分提示词保持稳定:
- The resident description and schema hold no state. No list of running servers, no counts, no times — only the catalog, which changes when you edit it, never mid-session. status is an action, not part of the description.
- Long results are paged. DeepSeek Harness's tool-result pruner rewrites any tool result longer than 8,192 characters once the window fills up, which breaks the cache from that message on. The hub returns at most 7,500 characters per result and keeps the rest for {"action":"page","cursor":"r1:2"}. Paging is deterministic: the same text always splits the same way, and never between the halves of an emoji. Pages are held in memory for the last 32 paged results.
- Set the page size with --page-chars N (or CAPABILITY_HUB_PAGE_CHARS, or pageChars in the native bundle's config); 0 turns paging off.
Measured on Strata 0.2.3 (Qwen3.8-Flash-Next Coder IQ1_M, 128K, one slot) through DeepSeek Harness 0.2.1-rc.2 and this hub driving web-search-neo, with a long page that came back in two pages: every request after the first of a session reused the cached start and read only what was new — 7,701 of 7,774, 10,367 of 12,246, 16,442 of 18,328 tokens — and the next turn of the same session, a new process resuming it, reused 20,006 of 20,067.
Troubleshooting
A skill or a long result arrives as a bare {"name": …} or {"paged": true, …}. Some MCP clients, OpenCode among them, show a tool's structuredContent and ignore its text. From 0.10.0 the body travels in both: instructions for skill.load, text for paged results. On an older build, update the hub and restart the client's hub process.
tools says the server "published no tools". The server started but its backing application is not running (a Unity MCP with the editor closed, for example). Start the application and call tools again.
enable fails with "untrusted". Entries from propose are untrusted by design. A person approves them with capability-hub-admin approve, then call catalog.reload.
A child server stopped answering. Reconnect is explicit: disable, then enable.
The hub exits at start with --page-chars / CAPABILITY_HUB_PAGE_CHARS must be a non-negative integer. The value was not a number. 0 turns paging off; an empty value is ignored.
Current scope
- Tool calls are proxied; child MCP resources and prompts are not bridged yet.
- Reconnect is explicit: disable, then enable.
- Remote skill download, signature verification and sandboxed installers are future work.
- Distribution is the npm package @neoxider/mcp-hub, a Git checkout, or the Harness bundle from GitHub Releases.
- A model with unrestricted host shell access can bypass plugin-local policy; use Harness permissions as the outer boundary.
More
- docs/context-economy.md — the full measurement write-up and prior art.
- docs/harness-smoke.md — the model-in-the-loop smoke tests for DeepSeek Harness.
- SECURITY.md — the trust boundary and how to report a vulnerability.
- CHANGELOG.md — what changed in each version.
- Companion project: NeoXider Agent Deck, a compact animated desktop view of agents, context, models, reasoning and chat.
Contributing
Issues and focused pull requests are welcome. New integrations should include a pinned example, a narrow permission description and an end-to-end test.
MIT © NeoXider预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
NeoXider/neoxider-mcp-hub
按需搜索启用并调用 MCP 服务器,常驻上下文省九成
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,225未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,370已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13941 个插件含 PR 收录与…
★ 355已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这是你的仓库?认领条目