前言¶
DeepSeek Harness(dsh)把联网能力拆成两层:模型侧始终看到名为 web_search 的工具,真正发请求的是挂在 ctx.web 上的搜索 provider。默认那一层是内置的 web-search-deepseek,走 DeepSeek 官方搜索接口,需要能解析 DEEPSEEK_API_KEY,而且每次搜索都会消耗一次模型调用。
这在只用官方账号时没问题。一旦聊天走了别的网关、搜索额度想单独结算,或者手头已经有 Tavily、Brave、Exa 这类搜索 MCP,默认后端就会变成约束:工具名改不了,provider 却绑在官方搜索上。官方仓库的讨论里也有人碰到过类似情况——会话模型换了,web_search 仍打原来的 DeepSeek 搜索端点。
dsh-search-mcp 做的是替换这一层 provider,而不是再注册一套 mcp__tavily__* 之类的新工具。模型还是调用 web_search,界面展示不变,请求改走你在 Web 设置页配好的搜索 MCP 服务器。本文按社区目录页、GitHub 仓库 README、package.json / cordis.patch.yml 源码,以及 DeepSeek Harness 官方的 Web 能力说明核对后整理。
这是什么¶
dsh-search-mcp 是由 gxpppp 维护的社区插件,许可证 MIT,主要语言 JavaScript,当前 package.json 版本为 0.1.0。GitHub 仓库截至 2026 年 8 月 18 日为 10 星;社区目录页当时显示 7 星,以仓库页面为准。
它在目录里归在「界面增强」,原因是插件会向 Web 设置页注入 search-mcp 配置卡片,并且 package.json 里声明了 dsh.client.platform 为 web。真正替换的能力是搜索 provider:启用期间把 web.searchProvider 切到 search-mcp,同时禁用内置的 web-search-deepseek。卸载后这一层 bundle 消失,内置搜索按原样恢复。
需要先分清两件事。DeepSeek Harness 官方的口号是「一切皆插件」,模型、工具、会话、UI 都可以在配置层替换,不必改核心源码。社区插件目录 deepseek-harness-plugin.com 是独立站点,和 DeepSeek / 幻方没有官方从属关系,不能当成官方应用商店。
核心功能¶
工具名不变,后端整段换掉¶
插件通过 ctx.web.registerSearchProvider() 注册 id 固定为 search-mcp 的 provider。返回形状与内置 provider 一致({ sources, truncated, content? }),由原生 web_search 工具负责格式化。因此模型侧工具名、参数和展示都不必改,执行通道换成 MCP。
安装时由包内的 cordis.patch.yml 自动做三件事:
- insert:
- id: search-mcp
- id: web
config:
searchProvider: search-mcp
- id: web-search-deepseek
disabled: true
README 写明:这一层加在 dsh-base / dsh-web-app 之后、用户自己的 cordis.patch.yml 之前;删掉插件后整层消失,内置搜索还原。
范围只覆盖搜索。web_fetch 保持 dsh 默认关闭,插件不会顺手打开网页抓取。
一套设置页切换多家搜索 MCP¶
打开 Web 界面 → 设置 → Plugins → search-mcp,维护 servers 列表。仓库为这些 kind 写了预设:
| kind | 默认端点 / 启动方式 | 鉴权 | 默认工具名 |
|---|---|---|---|
tavily |
https://mcp.tavily.com/mcp/ |
query tavilyApiKey |
tavily_search |
brave |
https://mcp.brave.com/mcp/ |
query braveApiKey |
brave_web_search |
exa |
https://mcp.exa.ai/mcp |
header x-api-key |
web_search_exa |
perplexity |
https://mcp.perplexity.ai/mcp/ |
query pplx_api_key |
pplx_search |
duckduckgo |
npx -y duckduckgo-mcp-server(stdio) |
无需 key | ddg_web_search |
custom |
自己填 | 自己选 | 自己填 |
传输方式支持 http(streamable-http,默认)和 stdio(本机命令,DuckDuckGo 走这条)。全局字段包括 defaultServer、maxResults(默认 8)、searchTimeoutMs(默认 30000)。单条服务器还可以覆盖自己的 maxResults。
设置写入 $DSH_HOME/settings.yaml 的 search-mcp: 段,优先于行配置。provider 每次搜索重新读快照,改完立即生效,不必为改配置再重启。
默认 bundle 会插入一条 Tavily 服务器:defaultServer 为 tavily,apiKeyEnv 为 TAVILY_API_KEY。也就是说,装上插件并不等于搜索立刻可用,还要把对应 key 配好,或改成免 key 的 DuckDuckGo。
密钥不进仓库,结果按 URL 归一化¶
密钥解析顺序在 README 和 lib/index.js 里一致:字面量 apiKey → dsh 凭证服务(apiKeyEnv,例如 $DSH_HOME/.credentials.yaml)→ 启动环境变量。设置页里 apiKey 按密码框显示、落盘脱敏;仓库的 cordis.patch.yml 明确写了不提交任何 API key。
MCP 客户端用 @modelcontextprotocol/sdk。每次搜索新建连接(http 或 stdio),结束时关闭;调用方的取消信号与 searchTimeoutMs 超时竞速,中止时抛 WEB_ABORTED。结果侧会递归扫描 MCP 返回的 JSON,凡带 url 的对象当作 source(title / snippet / 发布日期取常见字段名),answer 当作摘要,不绑死某一家厂商的字段名。
安装与启用¶
社区目录页给出的安装命令是:
dsh plugin add github:gxpppp/dsh-search-mcp
dsh CLI 会从 GitHub 解析插件并装进当前配置。插件声明运行在 web 平台,README 里的本地开发示例也是装进 web profile。如果当前默认 profile 不是 web,可以按 README 写成:
dsh plugin --profile web add github:gxpppp/dsh-search-mcp
需要可复现安装时,按目录页说明固定 commit 哈希:
dsh plugin add github:gxpppp/dsh-search-mcp#<commit>
把 <commit> 换成仓库里的具体哈希,不要留空。
本地改源码时,README 的步骤是:先在仓库目录执行 npm install,再用 dsh plugin --profile web add link:<本仓库路径> 以 link 方式挂上,然后重启 dsh web。Web bundle 未启用 HMR,首次安装或改插件代码后必须重启进程;之后只改设置页里的服务器列表,则不必重启。
目录页和仓库都提醒:插件以当前 dsh 进程的权限运行,安装时可能执行代码。装之前应检查源代码仓库和许可证。
配置与验证¶
在设置页补上服务器和密钥¶
- 启动
dsh web,打开网页界面。 - 进入 设置 → Plugins → search-mcp。
- 确认
servers里至少有一条记录。默认是 Tavily;也可以改成 Brave / Exa / Perplexity,或加一条kind: duckduckgo的 stdio 服务器。 - 需要密钥的服务,任选一种方式:
- 在卡片里直接填apiKey;
- 或填apiKeyEnv(如TAVILY_API_KEY),并在$DSH_HOME/.credentials.yaml或启动环境里提供同名凭证。 - 把
defaultServer设成某条servers[].id,保存。
README 记录的申请入口:Tavily(tavily.com)、Brave Search API(brave.com/search/api;README 写明有免费 2000 次/月额度,具体以 Brave 当前说明为准)、Exa(exa.ai)、Perplexity(perplexity.ai)。DuckDuckGo 这条预设不需要 key,但本机要能执行 npx。
如果以前在 cordis.patch.yml 里直接插过 Tavily MCP,README 建议删掉那一段 insert,避免工具目录里再出现 mcp__tavily__*,和 web_search 重复。
确认 provider 已经切过去¶
README 用 web profile 做组合层检查。PowerShell 示例是:
dsh --profile web --dump-config | Select-String -Pattern "searchProvider|search-mcp|web-search-deepseek"
在 bash 下可以对同一份输出做过滤:
dsh --profile web --dump-config | grep -E "searchProvider|search-mcp|web-search-deepseek"
预期能看到 searchProvider 已是 search-mcp,且 web-search-deepseek 处于 disabled。然后开一个新会话,让智能体调用 web_search(README 的例子是「搜索 MCP 服务器列表」)。返回结果应来自当前 defaultServer 对应的 MCP,而不是内置 DeepSeek 搜索。
卸载¶
dsh plugin --profile web remove dsh-search-mcp
之后按 README:清掉 settings.yaml 里的 search-mcp: 段(如果改过设置页),重启 dsh web。内置 web-search-deepseek 会随 bundle 层一起恢复。
只在 cordis.patch.yml 里给 search-mcp 行加 disabled: true 并不足够:README 写明,此时内置搜索仍处于被替换状态,要同时还原 web 和 web-search-deepseek 两行,或者直接卸载插件。
适用场景与注意事项¶
比较适合这几类用法:
- 希望保留
web_search这个模型工具,但搜索流量走 Tavily / Brave / Exa / Perplexity 等专业搜索 API。 - 已经在用搜索类 MCP,不想再让同一套能力以
mcp__*__*工具名暴露一份。 - 需要在设置页里切换多家搜索后端,而不改 dsh 源码。
- 暂时没有 DeepSeek 官方搜索额度,但有 DuckDuckGo MCP 或其它免 key / 自建搜索 MCP。
使用前注意这些边界:
- 这是 Web 插件。
package.json的 client 注入指向@deepseek-ai/dsh-client-ui-settings,平台为web。headless 流程不在仓库说明范围内。 - 依赖版本写死在 0.1.0-rc.6。
package.json里@deepseek-ai/dsh-web、dsh-settings、dsh-credentials、dsh-launch-environment均为0.1.0-rc.6,MCP SDK 为^1.30.0。dsh 若已升到不兼容的版本,需要自己核对。 - 空列表不能搜。
servers为空会报configured web provider "search-mcp" is registered but unavailable或no search MCP servers configured。缺 key、defaultServer对不上 id、URL 不通、stdio 的npx不在 PATH,都会在搜索时以WEB_PROVIDER_ERROR一类错误冒出来。 - 只换搜索,不换抓取。
cordis.patch.yml把tool-web的fetch维持为false,并把searchMaxResults提到 50,让插件自己的maxResults决定实际条数。 - 第三方搜索各有条款和费用。 插件本身 MIT、可免费安装;Tavily / Brave / Exa / Perplexity 的配额以各家控制台为准,不要把 README 里的免费额度理解成永久保证。
- 权限与来源。 插件与当前 dsh 进程同权,能读凭证、能拉起 stdio 子进程。安装前看仓库源码和 MIT 许可证;需要可复现环境时固定 commit。
小结¶
dsh-search-mcp 把 dsh 的搜索 provider 从内置 DeepSeek 换成可配置的搜索 MCP,同时保住 web_search 这个工具名。配置集中在 Web 设置页,卸载即可还原。它解决的是「后端想换、模型侧不想改」这一类需求,并不是再做一套并行的搜索工具。
目录页:https://deepseek-harness-plugin.com/zh-CN/plugins/dsh-search-mcp/
GitHub:https://github.com/gxpppp/dsh-search-mcp