ai-search-mcp
# AI Search MCP
> 专为 Agent 打造的上网搜索 MCP 服务器:**开箱即用的免费搜索引擎**、可插拔的高质量引擎、内置缓存、时间过滤,以及一次调用完成整轮调研的 `research` 组合工具。
>
> An agent-friendly **web search** MCP server — zero-config free engines, region-aware failover, freshness filtering, and a one-call `research` tool.
[](https://www.npmjs.com/package/ai-search-mcp)
[](LICENSE)
[](package.json)
---
## 为什么做这个(Why)
搜索是 Agent 最高频的工具,但现有方案都有硬伤:
| 现有方案 | 问题 |
|---|---|
| 官方 `fetch` MCP | 只能抓**已知 URL**,不会搜索 |
| Tavily / Brave / Exa 官方 MCP | 质量高,但**全部要付费 API Key** |
| 社区 DuckDuckGo MCP | 免费但脆弱,输出非结构化,无缓存/重试 |
**ai-search-mcp** 填补了这个空档:**零配置**就能用(DuckDuckGo / Bing / 百度 / 搜狗),自动按区域选择最合适的引擎并故障转移,支持时效过滤,还能把「搜→选→抓→总结」的整轮调研压成一次 `research` 调用。
## 特性(Features)
- 🆓 **零配置开箱即用**:`npx ai-search-mcp` 直接跑,不需要任何 API Key
- 🔌 **多引擎可插拔**:免费引擎 `duckduckgo` · `bing` · `baidu` · `sogou` `360`+ 付费引擎 `brave` · `tavily`
- 🌏 **区域感知自动选引擎**:`SEARCH_ENGINE=auto`(默认)→ 中国区域走 `bing→baidu→sogou`,其他区域走 `duckduckgo→bing`;**429/超时自动故障转移**,支持逗号分隔引擎链
- 🕒 **时间过滤**:`freshness=day|week|month|year` 或 `timeRange="YYYY-MM-DD..YYYY-MM-DD"`,按引擎自动映射
- 🧠 **Agent 友好输出**:结构化 JSON(`{ id, title, url, snippet, domain }`),不是原始 HTML;每条结果带稳定 `id`,可直接引用
- 🧭 **`research` 组合工具**:一次调用 = 搜索 + 抓取 Top 页 → 证据简报(消灭 5~10 次工具往返)
- ⚡ **内置缓存**(TTL,默认 10 分钟):减少重复网络请求,更快更省
- 🛡️ **工程兜底**:超时、指数退避重试、统一错误码,`RATE_LIMITED` 错误附带 `suggestedEngine` / `retryAfter` 建议
- 🐢 **礼貌爬虫内置**:按引擎限频(默认 8 次/分)+ 随机请求间隔,主动避免触发平台风控,不做任何反爬对抗(合规优先)
- 📄 **`fetch_page` 工具**:任意 URL(或搜索结果 `id`)转成可读 Markdown
- 🌏 **代理支持**:`SEARCH_HTTP_PROXY` / `HTTP(S)_PROXY`
- 🧪 **零构建纯 JS**(CommonJS),130 个单元测试 + 端到端冒烟测试
## 快速开始(Quick Start)
需要 Node.js **>= 18**。
```bash
# 直接用 npx 运行(无需安装)
npx -y ai-search-mcp
# 或全局/本地安装
npm i -g ai-search-mcp
ai-search-mcp
```
### Claude Desktop
在 `claude_desktop_config.json` 中加入:
```json
{
"mcpServers": {
"ai-search-mcp": {
"command": "npx",
"args": ["-y", "ai-search-mcp"]
}
}
}
```
### Cursor / Cline / 其他支持 MCP 的客户端
```json
{
"mcpServers": {
"ai-search-mcp": {
"command": "npx",
"args": ["-y", "ai-search-mcp"],
"env": {
"SEARCH_REGION": "cn-zh"
}
}
}
}
```
> 中国用户建议加上 `SEARCH_REGION=cn-zh`,会自动走国内可直连的引擎链(bing → baidu → sogou),免代理。
### 从源码运行
```bash
git clone https://github.com/zhulingyu666/ai-search-mcp.git
cd ai-search-mcp
npm install
npm start
```
## 工具说明(Tools)
### `search` — 搜索
```json
{
"query": "2026年 大模型 最新进展",
"maxResults": 10,
"site": "github.com",
"region": "us-en",
"freshness": "week"
}
```
返回(结构化 JSON):
```json
{
"query": "2026年 大模型 最新进展",
"queryRewritten": "大模型 最新进展",
"queryYear": 2026,
"engine": "bing",
"engineSwitched": false,
"cached": false,
"total": 10,
"deduped": 2,
"freshnessApplied": true,
"freshnessAutoInferred": true,
"timeRange": "2025-08-25..2026-08-25",
"results": [
{
"id": "a1b2c3d4",
"title": "...",
"url": "https://...",
"snippet": "...",
"domain": "example.com"
}
]
}
```
> **中文查询自动改写**:当查询含中文且出现「20xx年」年份词(如「2026年 大模型 最新进展」)时,工具会移除年份词并自动附加 `freshness=year` 补偿时间意图——因为 Bing 等引擎对中文分词弱,年份前缀会导致返回「2026年百科/日历」等跑题结果。改写通过 `queryRewritten` / `queryYear` / `freshnessAutoInferred` 透明返回,Agent 可自行判断是否接受。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `query` | string | — | 搜索关键词(必填) |
| `maxResults` | number | 10 | 返回条数(1–20) |
| `site` | string | — | 限定单个域名,如 `github.com`(`includeDomains` 的兼容别名) |
| `includeDomains` | string[] | — | 只返回这些域名的结果,如 `["github.com","arxiv.org"]`(引擎 `site:` 无效时结果侧兜底过滤) |
| `excludeDomains` | string[] | — | 排除这些域名的结果,如 `["baidu.com"]` |
| `region` | string | — | 区域提示:DDG `kl` 码(如 `cn-zh`、`us-en`)/ Bing `setlang` / Brave `country`;同时参与引擎自动选择 |
| `freshness` | string | — | `day` \| `week` \| `month` \| `year`(映射到各引擎的时间过滤) |
| `timeRange` | string | — | 精确时间窗 `"YYYY-MM-DD..YYYY-MM-DD"`(优先级高于 `freshness`) |
> 每条结果带稳定 `id`(sha1 前缀)。把这个 `id` 传给 `fetch_page` 即可深读该页,无需再手拼 URL。
### `research` — 一次调用完成整轮调研
替代 Agent「搜索 → 选页 → 抓取 → 总结」的循环,**一次调用**搞定:
```json
{
"query": "2026 LLM open source models comparison",
"maxResults": 5,
"fetchTopN": 2,
"freshness": "month"
}
```
返回证据简报(evidence brief):
```json
{
"query": "...",
"engine": "bing",
"overview": { "total": 5, "results": [ { "id": "...", "title": "...", "url": "...", "snippet": "...", "domain": "..." } ] },
"pages": [ { "id": "...", "url": "...", "title": "...", "headings": [ { "level": 1, "text": "..." } ], "content": "...", "length": 4000, "truncated": false } ],
"note": "证据简报已就绪,请基于页面内容总结并标注来源 URL。"
}
```
- 多页面**并行抓取**;单页失败记作 `{ url, error }`,**不影响整体结果**
- **智能选页**:pages 按**域名多样性**选取(先每个域名取排名最高的一条,再按排名补足),避免单站垄断——某个站反爬 403 时其他域名的页面不受影响;`rank` 字段标注该结果在搜索结果中的原始排名
- `extractMode`(默认 `summary`):`summary` 返回去噪后的正文要点(省 token),`full` 返回整页 Markdown
- 总结由宿主模型完成——MCP 内部**不依赖任何 LLM**,保持零外部依赖、厂商无关
### `fetch_page` — 抓取网页转结构化内容
```json
{
"url": "https://example.com/article",
"extractMode": "summary",
"maxLength": 8000
}
```
- `extractMode`:`summary`(默认省 token,返回去噪正文 `mainText`)/ `full`(返回整页 `markdown`)
- 返回 `{ url, title, description, headings, mainText, markdown, length, truncated, cached }`——`headings` 是页面标题大纲,Agent 可先看结构再决定是否深读
也可以用结果 id:`{ "id": "a1b2c3d4", "maxLength": 8000 }`。
### `status` — 透明状态页(诊断用)
搜索失败时用 `status` 看**为什么**——是全部引擎挂掉、某个引擎被限频,还是你自己的 IP 被临时 Ban:
```json
{
"server": "ai-search-mcp",
"uptimeSec": 1234,
"config": { "engine": "auto", "region": "cn-zh", "ratePerMinute": 8, "cacheTtlMs": 600000, "langRouting": false, "selectorOverride": false },
"cache": { "size": 12, "hits": 34, "misses": 5 },
"engines": [
{ "engine": "bing", "status": "cooling", "lastCode": "RATE_LIMITED", "consecutiveFailures": 3,
"lastOkAt": "2026-08-26T05:00:00.000Z", "lastFailAt": "2026-08-26T05:05:00.000Z", "cooldownUntil": "2026-08-26T05:06:00.000Z" },
{ "engine": "baidu", "status": "healthy", "lastCode": null, "consecutiveFailures": 0, ... }
]
}
```
- 引擎状态:`untested`(未用过)→ `healthy` / `degraded`(连续失败)→ `cooling`(冷却中,链会跳过)
- **优雅降级**:`RATE_LIMITED` 立即触发冷却(按 `retryAfter`,默认 60s),其他失败连续 2 次进入冷却——冷却期的引擎会被引擎链**跳过**,不再浪费请求
- 全部引擎失败时,`search` 的错误响应带 `engineStatus` 快照 + 归因摘要(如 `bing=RATE_LIMITED (冷却至 13:45), baidu=验证码...`),并区分"全部冷却"(疑似 IP 被 Ban)与普通失败
## 配置(环境变量)
| 变量 | 默认 | 说明 |
|---|---|---|
| `SEARCH_ENGINE` | `auto` | 单个引擎或**逗号分隔引擎链**(如 `brave,tavily`);`auto` = 按区域自动选择 |
| `SEARCH_REGION` | — | 引擎自动选择的默认区域,如 `cn-zh`(同时也是 `search` 的默认 region) |
| `SEARCH_MAX_RESULTS` | `10` | 默认结果条数(1–20) |
| `SEARCH_CACHE_TTL` | `600` | 缓存 TTL(秒,0 表示关闭) |
| `SEARCH_CACHE_FILE` | — | 缓存持久化文件路径,如 `./.cache/search.json`;设置后重启不丢缓存(仅限单进程使用) |
| `SEARCH_SELECTOR_FILE` | — | 本地解析规则覆盖文件(JSON),引擎改版时免发版修复 |
| `SEARCH_SELECTOR_OVERRIDE_URL` | — | 远程解析规则覆盖 URL(如 GitHub Gist),启动时拉取;作者更新 Gist 即可修复所有用户 |
| `SEARCH_TIMEOUT_MS` | `10000` | 单次请求超时(毫秒) |
| `SEARCH_RATE_PER_MINUTE` | `8` | 每个引擎每分钟最大请求数(0 关闭限频) |
| `SEARCH_MIN_DELAY_MS` / `SEARCH_MAX_DELAY_MS` | `500` / `2000` | 请求间随机延迟范围(毫秒,0 关闭) |
| `SEARCH_LANG_ROUTING` | `0` | `1` = 中文查询优先百度/搜狗(需在百度/搜狗可达的网络,见下文) |
| `SEARCH_LOG_LEVEL` | `info` | `debug` \| `info` \| `warn` \| `error` \| `off`(结构化日志输出到 stderr,不污染 MCP 协议) |
| `SEARCH_LOG_QUERY` | `1` | `0` = 日志中打码查询词(隐私) |
| `FETCH_MAX_LENGTH` | `8000` | `fetch_page` / `research` 单页 Markdown 长度上限(字符) |
| `BRAVE_API_KEY` | — | 使用 `brave` 引擎必需 |
| `TAVILY_API_KEY` | — | 使用 `tavily` 引擎必需 |
| `SEARCH_HTTP_PROXY` | — | 代理地址,如 `http://127.0.0.1:7890`(回退到 `HTTPS_PROXY`/`HTTP_PROXY`) |
| `SEARCH_USER_AGENT` | 内置 | 自定义 User-Agent |
## 引擎对比
| 引擎 | 免费 | API Key | 时间过滤 | 说明 |
|---|---|---|---|---|
| `google` | ✅ | ❌ | ❌ | **海外专用**(墙内不可达);索引质量全球最佳,反爬严,需海外 IP + Cookie 会话 |
| `bing` | ✅ | ❌ | ✅(`qft`) | 全球通用;中国区域默认,中英文覆盖好,国内免代理直连 |
| `duckduckgo` | ✅ | ❌ | ✅(`df`) | 全球备选;中国大陆直连可能超时(需代理) |
| `yandex` | ✅ | ❌ | ❌ | **海外专用**;俄语/CIS 内容强,部分网络可达性好,反爬中等 |
| `so360` | ✅ | ❌ | ❌ | 360 搜索;**数据中心/国内 IP 均可直连**,反爬相对宽松,中文原生引擎 |
| `baidu` | ✅ | ❌ | ❌ | 仅限国内;中文索引强,但反爬严(数据中心 IP 下带 Cookie 仍会触发图形验证码) |
| `sogou` | ✅ | ❌ | ❌ | 仅限国内;微信内容覆盖好,但缺 JS 生成的 SNUID 会话会被 302 拦回 |
| `brave` | ❌ | ✅ | ✅(`freshness`) | 官方 API,质量高,有免费额度 |
| `tavily` | ❌ | ✅ | ✅(`time_range`) | 专为 LLM/RAG 检索设计 |
## 网络注意事项
- **中国大陆免代理**:默认 `SEARCH_ENGINE=auto` + `SEARCH_REGION=cn-zh` → `bing → so360 → baidu → sogou`,全部直连可用,无需任何配置。其中 360 搜索(so360)对数据中心/机房 IP 的容忍度最高,是最可靠的国内原生中文引擎。
- **海外部署**:不设 `SEARCH_REGION`(或设为非 `cn` 前缀)→ 默认链 `google → bing → duckduckgo → yandex`,Google 质量最优、Bing 最稳,任一失败自动转移。Google/Yandex 在墙内不可达/被验证码拦截属预期(当前网络会先超时/报 `RATE_LIMITED` 后自动切换)。
- 免费引擎会限流。**429 会以结构化错误返回**(`RATE_LIMITED`,带 `suggestedEngine` 和 `retryAfter`);引擎链会在 429/超时时**自动故障转移**。
- 部分站点(如百度百科、很多国内门户)会拒绝脚本抓取(HTTP 403):`fetch_page` 返回结构化错误,`research` 将对应页标记为失败并继续。
- 需要访问 DuckDuckGo / Brave / Tavily 但处于墙内?用代理:
```bash
SEARCH_ENGINE=duckduckgo SEARCH_HTTP_PROXY=http://127.0.0.1:7890 ai-search-mcp
```
- **礼貌爬虫定位**:工具内置按引擎限频(默认 8 次/分)与随机请求间隔;百度/搜狗通过首页播种会话 Cookie(BAIDUID / SUID)提升成功率,但**不做**验证码绕过、UA 伪造轮换、代理池轮换等反反爬对抗——这是合规红线。被 403/验证码拦截时请降频或切换引擎。
- **百度/搜狗可用性说明(实测)**:这两个引擎对网络环境很敏感——数据中心/海外 IP 下,百度即使带播种 Cookie 也会被强制图形验证码(`wappass`),搜狗缺少 JS 生成的 SNUID 会被 302 拦回(`antispider`)。因此默认环境下它们会自动故障转移到 Bing/360;若你在**大陆住宅 IP** 且百度/搜狗可达的环境中运行,可设 `SEARCH_LANG_ROUTING=1` 让中文查询优先走百度/搜狗/360(原生中文引擎,分词更好)。
- 我们遵循目标站点的 `robots.txt` 与服务条款;本工具**只返回搜索摘要与链接**,不转载全文内容。
## 解析规则覆盖(引擎改版免发版修复)
所有引擎的 SERP 解析规则(标题/URL/摘要提取正则、反爬检测关键词)都外置在 `src/selectors/*.json`。当某个搜索引擎改版导致解析失效时,**不必等待新版 npm**——按优先级覆盖即可:
1. **内置规则**(随包分发,默认)
2. **本地文件** `SEARCH_SELECTOR_FILE=/path/selectors.json`
3. **远程 URL** `SEARCH_SELECTOR_OVERRIDE_URL=https://gist.githubusercontent.com/.../selectors.json`(启动时拉取,优先级最高)
覆盖文件格式(按引擎 key 合并,未提到的引擎保持内置规则):
```json
{
"bing": {
"challenge": ["captcha", "challenge"],
"fields": {
"title": {
"pattern": "<h2[^>]*>\\s*<a[^>]+href=\"([^\"]+)\"[^>]*>([\\s\\S]*?)<\\/a>\\s*<\\/h2>",
"flags": "gi",
"urlGroup": 1,
"textGroup": 2
},
"snippet": {
"window": "after",
"size": 2000,
"pattern": "<p[^>]*>([\\s\\S]*?)<\\/p>",
"flags": "i",
"group": 1
}
}
}
}
```
字段说明:`title` 必填(`urlGroup` = URL 捕获组,`textGroup` = 标题捕获组);`url` 可选(`window: "before"` 在标题前窗口找链接);`snippet`/`snippetAlt` 可选(`window: "after"` 在标题后找摘要,`groups` 支持多个捕获组取首个非空);`challenge` 为反爬检测子串列表;`domain.fallback` 为 URL 解析失败时的兜底域名。
**免发版工作流**:用户报告"XX 引擎搜索坏了" → 作者把修复后的规则更新到一个 Gist → 用户设 `SEARCH_SELECTOR_OVERRIDE_URL=<gist>` 重启即修复。覆盖加载失败(文件损坏/网络不通)**永远不会阻塞启动**,自动回退上一级规则并打 warn 日志。
## 开发(Development)
```bash
npm test # 137 个单元测试(Node 内置 test runner)
node scripts/smoke-test.js # 端到端冒烟:search + freshness + research(需要网络)
```
### 日志示例(stderr,`SEARCH_LOG_LEVEL=debug`)
```
[ai-search-mcp] [info] ready (version 0.3.2, engine=auto, region=cn-zh, logLevel=debug)
[ai-search-mcp] [info] rewrite q="2026年 大模型 最新进展" -> q="大模型 最新进展" +freshness=year
[ai-search-mcp] [info] search q="大模型 最新进展" engine=bing cached=false took=972ms total=3 deduped=1
[ai-search-mcp] [info] fetch url=https://example.com/ len=130 mode=summary took=1189ms
[ai-search-mcp] [debug] engine-fail engine=baidu err=RATE_LIMITED took=1203ms
[ai-search-mcp] [warn] fetch-fail url=https://baike.baidu.com/... err=ENGINE_ERROR took=512ms
```
## 许可证(License)
[MIT](LICENSE)
---
**免责声明**:本项目为独立开源项目,与 DuckDuckGo、Bing/Microsoft、Brave、Tavily、百度、搜狗均无关联、背书或赞助关系。百度/搜狗基于公开 HTML 解析,若这些站点变更页面结构可能导致解析失效。
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: search for web results, fetch_page for retrieving a specific page's content, and research for combining search and deep-reading. There is no overlap or chance of misselection.
All tool names follow the same snake_case, verb_noun pattern (fetch_page, search, research). Although 'search' and 'research' are single-word verbs, the pattern is consistent and predictable.
With only 3 tools, the server is well-scoped for a web search and research workflow. Each tool earns its place with minimal redundancy.
The tool surface covers the full research lifecycle: search to discover results, fetch_page to retrieve content, and research to combine both efficiently. No critical gaps exist for the stated purpose.