Skip to main content
Glama
README.md
# 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.

[![npm version](https://img.shields.io/npm/v/ai-search-mcp)](https://www.npmjs.com/package/ai-search-mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](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

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

With only 3 tools, the server is well-scoped for a web search and research workflow. Each tool earns its place with minimal redundancy.

Completeness5/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues