web-reach
by FnSGit
README.md
# web-reach
分层网页能力 MCP。四层策略内置,对外只暴露 3 个工具。
一个工具胜过四个:模型不需要在 Exa / Jina / 各家搜索 API 之间选,也不需要在
「先搜还是先抓」上试错。质量信号、去重、降级链都由服务内部处理。
## 为什么分层
实测(2026-11-14,同一批 5 条 query + 一道事实核查题,真实调用):
| 后端 | 优点 | 硬伤 |
|---|---|---|
| DeepSeek Search | 0.5s;`rerank_score` 0–3、`authority` 0–4、`site:/lang:/after:` 算子 | 同域名版本页重复严重(10 条仅覆盖 6 个域名) |
| Exa | 来源多样(与 DeepSeek 唯一域名只重叠 20–43%) | 慢 8 倍、无质量信号 |
| DeepSeek Fetch | 0.2–0.35s、正文干净(去掉导航与链接) | 14 个 URL 中 7 个 404:Wikipedia、HN、docs.anthropic.com 整站失败 |
| Jina Reader | 覆盖率高 | 慢 6–20 倍、噪声多 |
单用任何一个都有肉眼可见的缺口,所以按层组合,缺口由下一层补。
## 四层
```mermaid
flowchart TD
Q[query] --> L1["L1 DeepSeek Search<br/>rerank_score / authority"]
L1 --> F["过滤:score ≥ min_score<br/>authority ≥ min_authority<br/>按域名+路径前缀去重"]
F --> C{hits 够数?}
C -->|不够 / diversify=always| L2[L2 Exa 补新域名]
C -->|够| OUT[hits]
L2 --> OUT
U[url] --> FD[L3 DeepSeek Fetch]
FD -->|404 / 空 / 短于 400 字符| FJ[L3' Jina 兜底]
FD -->|ok| BODY[markdown]
FJ --> BODY
QQ[question] --> L4["L4 服务端深搜<br/>多步检索 + 引用"]
```
## 工具
| 工具 | 用途 | 何时不用 |
|---|---|---|
| `web_search` | 检索;返回带 score / authority / date 的命中,已过滤去重 | 要整篇原文时先用它再 `web_fetch` |
| `web_fetch` | 单 URL 取正文;双后端自动降级 | 只有摘要需求时不必抓 |
| `web_research` | 服务端多步深搜,返回带引用答案 | 需要原文留存 / 引用原句 / 可复现采集 |
### web_search
| 参数 | 默认 | 说明 |
|---|---|---|
| `query` | — | 支持 `site:` `-site:` `lang:` `after:` `before:` `"精确短语"` |
| `question` | — | 与 `query` 不同时填,改善 query 理解 |
| `max_results` | 8 | 1–20 |
| `min_score` | 1.5 | rerank_score 下限(文档称 ≥1.5 为高质量) |
| `min_authority` | 0 | authority 下限;查官方结论时给 3 |
| `diversify` | `auto` | `auto` 不足才补;`always` 预留一半名额给第二索引;`never` 只走第一层 |
### web_fetch
先走快速提取器;404 / 空正文 / 正文短于 400 字符时自动切第二个提取器。
`TRACE:` 行说明走了哪层、各层耗时。
### web_research
服务端最多约 20 次工具调用,由 `deepseek-flash` 决定检索策略。
`USAGE:` 行给出计费口径(searches / fetches / billed tokens)。
页面正文不会返回(服务端加密在 `signature` 里),要原文用 `web_fetch`。
## 失败语义
统一 `WEB_REACH_ERROR code=<code>`:
| code | 含义 | 处置 |
|---|---|---|
| `no_key` | 缺 API key | 检查密钥来源 |
| `rate_limited` | 429,已自动重试 | 仍失败则降低并发 |
| `not_found` | 目标无可用内容 | 换 URL 或用 `web_search` 找替代源 |
| `blocked` | 鉴权 / 策略 / 内容审查拒绝 | 换源 |
| `timeout` | 上游超时,已重试 | 该站可能不可达 |
| `upstream` | 其它上游错误 | 看 message |
## 配置
密钥来源:环境变量优先,其次 `~/.config/sextant/secrets.env`(只读 `KEY=VALUE`,不执行、不打印)。
| 变量 | 必需 | 用途 |
|---|---|---|
| `DEEPSEEK_API_KEY` | 是 | L1 检索、L3 抓取、L4 深搜 |
| `EXA_API_KEY` | 否 | L2 多样性;缺失时 L2 跳过 |
| `JINA_API_KEY` | 否 | 提高 `r.jina.ai` 配额 |
| `WEB_REACH_PROXY` | 否 | 显式代理 |
**代理**:宿主启动 MCP 子进程时常常只传精简 env,`HTTP(S)_PROXY` 丢失。实测缺少代理时
`r.jina.ai` 直连 20s 超时、带代理 3.9s 成功,而 Bun 只在进程启动时读取代理环境变量。
因此本服务自行解析:环境变量 → `WEB_REACH_PROXY` → 探测 `127.0.0.1:7890` 是否可达。
Bun 不支持 socks5 代理(`UnsupportedProxyProtocol`),配置里会被跳过。
### omp
`~/.omp/agent/mcp.json`:
```json
{
"mcpServers": {
"web-reach": {
"type": "stdio",
"command": "bun",
"args": ["/absolute/path/to/web-reach/src/index.ts"]
}
}
}
```
## 开发
```sh
bun install
bun run smoke # 真实 stdio 握手,三个工具各打一次真实请求
npx tsc --noEmit
```
冒烟覆盖:第一层命中、`diversify=always` 的跨索引补充、DeepSeek 抓取成功、
Wikipedia 走 Jina 兜底、`web_research` 引用、不存在的域名报错。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues