tianmingwan/dsh-balanced-search
Balanced web search plugin/MCP server for DeepSeek Harness: Keenable / Exa / Tavily round-robin with failover.
Project Overview项目介绍
dsh-balanced-search is a DeepSeek Harness plugin and MCP server that balances web search across Keenable, Exa, and Tavily. It round-robins between providers with automatic failover, returning normalized titles, links, and summaries, and fetches URLs as clean markdown. Use it when an agent needs reliable web search and page fetching. Note: at least one provider API key must be configured via environment variables, otherwise no service is available.
dsh-balanced-search 是 DeepSeek Harness 的均衡搜索插件,同时提供 MCP 服务器形态。它在 Keenable、Exa、Tavily 三个搜索 API 之间轮流调用,某个失败时自动切换下一个,统一返回标题、链接和摘要,并支持抓取 URL 转为 Markdown。需要联网搜索或抓取网页时即可启用。注意:需通过环境变量至少配置一个 API key,否则无可用服务。
请帮我了解并安装插件:【dsh-balanced-search】【https://github.com/tianmingwan/dsh-balanced-search】
Send this message to DSH in your current session. CLI install commands may not be accurate across systems — DSH will figure it out for you.把上面这条消息直接发给当前会话里的 DSH,让它帮你了解并安装。安装命令不一定准确,发给 DSH 更稳。
Or use CLI install (for developers)或使用命令行安装(适合开发者)
CLI Install命令行安装
dsh plugin --profile web add github:tianmingwan/dsh-balanced-search
把 tianmingwan/dsh-balanced-search 加入你的 DSH 配置(web profile)即可启用。
READMEREADME
dsh-balanced-search
English: A balanced web search plugin / MCP server that round-robins across Keenable (Keen Search) / Exa / Tavily and automatically fails over to the next provider. Returns normalized titles, links, and content summaries.
中文: 均衡搜索插件 / MCP 服务器:把 Keenable(Keen Search)/ Exa / Tavily 三个搜索 API 轮流调用(round-robin),某个服务失败时自动切换下一个,统一返回标题 / 链接 / 摘要。
This repository provides two forms / 本仓库同时提供两种形态:
- DeepSeek Harness native plugin (recommended) / dsh 原生插件(推荐) — registers
balanced_search/balanced_fetchas dsh tools and automatically takes over the built-inweb_search/web_fetch, no Python required. / 既注册balanced_search/balanced_fetch两个 dsh 工具,同时自动接管内置的web_search/web_fetch,无需 Python。 - Generic MCP server / 通用 MCP 服务器 — exposes
search/fetchover stdio viaserver.pyfor any MCP client. / 通过server.py以 stdio 方式暴露search/fetch,可供任意 MCP 客户端使用。
Features / 功能
- Search the web and return titles, links, and content summaries. / 搜索网页,返回标题、链接和内容摘要。
- Fetch a URL and return clean markdown text. / 抓取指定 URL 的网页正文,返回 clean markdown。
- Round-robin across providers with automatic failover. / 三个服务轮流调用,单个服务失败时自动切换下一个。
- Configure API keys via environment variables; providers without a key are skipped. / 通过环境变量配置 API key;未配置的服务不会启用。
- Takes over the built-in
web_search/web_fetchautomatically — the bundle pins dsh'swebrow to this plugin'sbalancedprovider, so no manual profile edit is needed. / 自动接管内置的web_search/web_fetch:bundle 层把 dsh 的web行钉到本插件的balancedprovider,无需手工改任何配置。 - Fetch happens server-side, at the vendor — so it also works where the shipped local
httpfetch provider cannot. See Configuration Notes. / 抓取在厂商服务端完成,因此在 dsh 自带本地抓取 provider 无法工作的环境下依然可用,见配置说明。
Environment Variables / 环境变量
Configure at least one search provider API key / 至少配置一个搜索服务的 API key:
KEENABLE_API_KEY=...
EXA_API_KEY=...
TAVILY_API_KEY=...
👉 Where to register and get each key — sign-up links, where the key lives in each dashboard, free quotas, and what one call costs:
API_KEYS.md. 三家的注册入口、key 在各家后台的哪个页面、免费额度、以及单次调用消耗,见API_KEYS.md。
| Provider | Sign up / 注册 | Env var | Free tier / 免费额度 |
|---|---|---|---|
| Keenable | https://app.keenable.ai/login | KEENABLE_API_KEY |
100,000 requests / month |
| Exa | https://dashboard.exa.ai/onboarding-guest | EXA_API_KEY |
$20 on sign-up + $10 / month |
| Tavily | https://app.tavily.com | TAVILY_API_KEY |
1,000 credits / month |
The dsh native plugin reads process environment variables directly. The Python MCP server also loads a .env file in the same directory. / dsh 原生插件直接读取进程环境变量;Python MCP 服务器还会自动读取同目录下的 .env 文件。
Directory Structure / 目录结构
| File / 文件 | Description / 说明 |
|---|---|
index.js |
dsh native plugin entry; registers balanced_search / balanced_fetch and the ctx.web provider balanced / dsh 原生插件入口:注册两个工具,并向 ctx.web 注册 balanced provider |
cordis.patch.yml |
dsh bundle config layer; inserts the plugin and pins the web row to the balanced provider / dsh bundle 配置层:插入插件,并把 web 行钉到 balanced provider |
package.json |
dsh bundle manifest / dsh bundle 声明 |
server.py |
Generic MCP server (stdio); exposes search / fetch / 通用 MCP server |
providers.py |
Python providers + round-robin / failover / Python 版 API 客户端与轮换 |
requirements.txt |
Python MCP server dependencies / Python MCP 服务器依赖 |
.env.example |
API key template (copy to .env) / API key 配置模板 |
API_KEYS.md |
Where to register the three APIs and get keys / 三个 API 的注册与 key 领取说明 |
.gitignore |
Excludes .env, virtualenvs, caches / 排除本地敏感与缓存文件 |
Install as a dsh Plugin / 安装为 dsh 插件
Requirements / 要求:DeepSeek Harness (dsh) installed, Node.js ≥ 20.
dsh plugin --profile web add github:tianmingwan/dsh-balanced-search
After restarting dsh --profile web / 重启 dsh --profile web 之后:
- Two extra tools appear / 新增两个工具:
balanced_search、balanced_fetch - The built-in
web_search/web_fetchare taken over automatically — no manual profile edit / 内置的web_search/web_fetch被自动接管,无需手工修改 profile 配置
No Python dependencies required / 无需安装 Python 依赖。
How the takeover works / 接管是怎么实现的
package.json declares dsh.bundle.patch, which makes this package a bundle layer. Installing it composes cordis.patch.yml on top of the bundles listed before it — notably @deepseek-ai/dsh-base, which mounts the web row — and that layer pins the row's providers:
- id: web
name: '@deepseek-ai/dsh-web'
config:
searchProvider: balanced
fetchProvider: balanced
index.js registers a single provider with id balanced into both the seam's search and fetch registries, so the pin covers both capabilities. (A patch replaces the targeted row's whole config rather than merging, which is why both fields are restated.)
Key requirement / key 要求
The takeover needs at least one of KEENABLE_API_KEY / EXA_API_KEY / TAVILY_API_KEY. With none set, the balanced provider reports itself unavailable and the built-in tools fail with WEB_PROVIDER_CONFIGURED_UNAVAILABLE naming balanced. / 接管需要至少配置一个 key;一个都没有时,balanced provider 会报告不可用,内置工具会以 WEB_PROVIDER_CONFIGURED_UNAVAILABLE 失败。
Opting out / 取消接管
A user's own ~/.dsh/profiles/<profile>/cordis.patch.yml is applied after every bundle layer, so the pin can be overridden or removed there — restoring dsh's shipped deepseek-official search / http fetch while keeping the two extra tools. / 用户自己的 cordis.patch.yml 在所有 bundle 层之后应用,因此可以在那里覆盖或删除这两行,恢复 dsh 自带的 provider,同时保留两个额外工具。
Use as a Generic MCP Server / 作为通用 MCP 服务器使用
Install / 安装
python -m venv .venv
# Windows
.venv\Scripts\python.exe -m pip install -r requirements.txt
# Linux / macOS
.venv/bin/python -m pip install -r requirements.txt
Run / 运行
# stdio mode for MCP clients / stdio 模式,供 MCP 客户端连接
python server.py
# or use the virtualenv Python / 或使用虚拟环境中的 Python
.venv\Scripts\python.exe server.py # Windows
.venv/bin/python server.py # Linux / macOS
MCP Client Example / MCP 客户端接入示例
{
"mcpServers": {
"balanced-search": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["/absolute/path/to/server.py"],
"env": {
"KEENABLE_API_KEY": "...",
"EXA_API_KEY": "...",
"TAVILY_API_KEY": "..."
}
}
}
}
Tool Usage / 工具用法
dsh Native Tools / dsh 原生工具
balanced_search— parameters / 参数:query/max_results/time_rangebalanced_fetch— parameters / 参数:url/max_chars/live
Built-in Tools after Takeover / 接管后的内置工具
These keep dsh's own schemas — this plugin supplies the retrieval backend only, so their parameters are fixed by dsh, not by this plugin. / 它们沿用 dsh 自己的工具签名:本插件只提供检索后端,参数由 dsh 决定。
web_search |
balanced_search |
|
|---|---|---|
| Query / 查询 | queries: string[] (up to 4 per call / 一次最多 4 条) |
query: string |
| Result count / 条数 | deployment config (default 8) / 部署配置,默认 8 | max_results 1–20 |
| Time range / 时间范围 | ✗ (deferred by the seam / seam 明确暂不支持) | time_range |
web_fetch— parameter / 参数:urlonly / 仅url。Nomax_chars/live; timeout and output cap are deployment policy / 没有这两个参数,超时与输出上限属部署策略。balanced_fetch— keeps / 保留max_chars/live.
The two surfaces complement each other / 两套接口互补:use web_search / web_fetch for dsh-native naming and multi-query, and balanced_search / balanced_fetch when you need time_range, live, or max_chars.
MCP Tools / MCP 工具
search— parameters / 参数:query/max_results/time_rangefetch— parameters / 参数:url/max_chars/live
Parameter Reference / 参数说明
query(required / 必填): Search keyword or natural language question / 搜索关键词或自然语言问题max_results: 1–20, default 8 / 1–20,默认 8time_range:day/week/month/year(native for Tavily; Exa maps tostartPublishedDate; Keenable maps topublished_after) / (Tavily 原生;Exa 映射为startPublishedDate;Keenable 映射为published_after)max_chars: Maximum characters to return, default 30000, max 50000 / 抓取内容最大字符数,默认 30000,上限 50000live: Fetch live from the source (bypass index/cache), defaultfalse/ 是否实时从源站抓取(绕过索引/缓存),默认false
Search response / 搜索返回 JSON:
{
"provider": "keenable|exa|tavily",
"count": 1,
"results": [
{"title": "...", "url": "...", "content": "...", "published_at": "...", "score": 0.5}
]
}
Fetch response / 抓取返回 JSON:
{
"provider": "keenable|exa|tavily",
"result": {"url": "...", "title": "...", "content": "..."}
}
Configuration Notes / 配置说明
- Change keys / 换 key:dsh plugin uses environment variables; MCP server uses
.envor clientenvinjection. - Failover strategy / 轮换策略:
Balancer(currently round-robin + failover; can be changed to weighted or health-aware). Search and fetch advance independent cursors, so a fetch does not shift the next search's starting provider. / 搜索与抓取各自独立推进游标,一次抓取不会改变下次搜索的起点。 - Add a provider / 新增服务:add a
SearchProvidersubclass inproviders.pyand register it inbuild_balancer(); or add a Provider class inindex.js. - Server-side fetch / 服务端抓取:the fetch call is made by Keenable / Exa / Tavily, so the page is retrieved from their network, not yours. This is why fetching still works when the local machine's DNS maps hosts into private or benchmark ranges — for example a Clash / mihomo TUN in
fake-ipmode answering with198.18.0.0/15addresses — where dsh's shipped localhttpfetch provider refuses withresolves to a non-public IP address. / 抓取由 Keenable / Exa / Tavily 发起,网页是在它们的网络里取的。因此当本机 DNS 把域名解析到私有或保留网段(例如 Clash / mihomo TUN 的fake-ip模式返回198.18.0.0/15地址)时,抓取依然可用;而 dsh 自带的本地httpprovider 会以resolves to a non-public IP address拒绝。 - Reporting a fetch status / 关于抓取状态码:the seam's
WebFetchResultrequires astatusCode, and every vendor extracts server-side without exposing the origin page's status, so the provider reports200for a successful extraction and inferstruncatedfrom whether the body reached its cap. / seam 要求返回statusCode,而三家厂商都是服务端抽取、不暴露原页面状态码,因此抽取成功即记为200,truncated按正文是否触顶推断。
License
MIT
bowenliang123/dsh-context
liustack/modsearch
omdsh-dev/dsh-genui
anysearch-team/anysearch-dsh
csyangwen/dsh-memory-evolve
e2mcc/dsh-popout-sidebar
See-Sol-Lab/DeepSeekGUI