Skip to main content
Glama
MDZZ-123

web-search-mcp

by MDZZ-123
README.md
# web-search-mcp

多供应商 LLM 服务端搜索网关:把搜索请求转发给支持服务端搜索(DEEP_SEARCH)的 LLM 中转网关,多供应商自动兜底。提供 REST 与 MCP(streamable HTTP)两种接口。

- 零第三方依赖(纯 Go 标准库),Docker 多阶段构建,镜像解压约 15MB
- 中文请求体统一 ASCII 转义(\uXXXX),规避网关中文乱码问题
- 支持 ECH(Encrypted Client Hello)

## 环境变量

| 变量 | 说明 |
|---|---|
| `PORT` | 监听端口,默认 8080 |
| `WEBSEARCH_TIMEOUT_MS` | 默认单请求超时(毫秒),默认 90000;可被 `X-Search-Config.timeoutMs` 覆盖 |

网关配置**全部由请求头动态提供**,修改配置无需重启容器。

## 请求头协议

| 头 | 示例 | 说明 |
|---|---|---|
| `X-Search-Config` | `{"timeoutMs":90000,"retries":3,"dns":["https://223.6.6.6/dns-query"]}` | 全局参数(可选) |
| `X-Search-Providers` | 网关数组 JSON | 按数组顺序兜底,改配置即切换,**无需重启** |

### X-Search-Config 全局参数

| 字段 | 说明 |
|---|---|
| `timeoutMs` | 单请求超时(毫秒),0=默认 |
| `retries` | 网络失败/5xx 重试轮数,0-10,默认 3 |
| `dns` | DoH 端点数组(`https://` 开头),**ECH 查询用**,顺序轮询;缺省用内置端点 |

### X-Search-Providers 数组元素

```json
{
  "name": "primary",
  "baseURL": "https://api.example.com/v1",
  "apiKey": "sk-你的key",
  "model": "grok-4.3",
  "apiStyle": "auto",
  "channel": "auto",
  "ech": true,
  "echHost": "cdn.example.net",
  "timeoutMs": 90000,
  "ips": ["1.2.3.4"]
}
```

| 字段 | 必填 | 说明 |
|---|---|---|
| `name` | | 供应商名(错误报告/日志用) |
| `baseURL` | ✅ | 网关地址,如 `https://api.example.com/v1` |
| `apiKey` | ✅ | Bearer token |
| `model` | ✅ | 模型名 |
| `apiStyle` | | `responses` / `chat` / `auto`(缺省 auto,按模型名含 `-search` 判 chat) |
| `channel` | | `auto`/`openai`/`grok`/`deepseek`/`gemini`/`ds-search`/`kimi`/`glm`/`qwen`/`perplexity` |
| `params` | | 通道参数透传(如 `searchMode`、`search_context_size`) |
| `ech` | | 是否启用 ECH(默认 false) |
| `echHost` | | 查询 HTTPS 记录(获取 ECHConfig)的域名;缺省=baseURL 的 host |
| `timeoutMs` | | 单网关超时覆盖 |
| `ips` | | 调试:显式 IP 列表(`ip` 或 `ip:port`),跳过 DNS 解析 |
| `priority` | | 预留排序字段 |

### ECH(Encrypted Client Hello)

网关配置开启 `ech: true` 时,通过 `echHost`(缺省为 baseURL 的 host)获取 ECHConfig 并用于 TLS 握手。

## HTTP API

| 端点 | 说明 |
|---|---|
| `POST /search` | `{"query":"...","provider":"可选"}` → 搜索结果 JSON |
| `POST /mcp` | MCP Streamable HTTP(initialize/tools/list/tools/call,非流式) |
| `GET /health` | 健康检查 |
| `GET /providers` | 说明网关由请求头动态提供 |

### POST /search 响应

```json
{
  "ok": true,
  "query": "...",
  "answer": "回答文本(含 [n](url) 来源引用)",
  "searched": true,
  "sources": [{"url": "https://...", "title": "..."}],
  "usage": {"input_tokens": 112, "output_tokens": 500},
  "provider": "primary",
  "model": "grok-4.3",
  "search_calls": 1,
  "latencyMs": 15700
}
```

全供应商失败时返回 `502` + `errors` 数组(每个供应商的失败原因)。

## MCP 接入(Reasonix / Claude 等客户端)

在 MCP 客户端配置 HTTP MCP server:

```toml
[[plugins]]
name    = "web-search"
type    = "http"
url     = "https://your-domain.example.com/web_search/mcp"
headers = { "X-Search-Providers" = "${SEARCH_PROVIDERS}" }
call_timeout_seconds = 180
```

工具名 `web_search`,参数:`query`(必填)、`provider`(可选,指定供应商)。

## 网络容错

- 每次请求重新 DNS 解析(不缓存固定 IP,天然支持 CDN 节点轮换)
- 连接失败/超时/5xx 自动重试(默认 3 轮,`retries` 可调);每轮重新解析、跳过已失败 IP
- **4xx(403 等)不重试**,网关错误原文透传(据此换模型/网关)

## 开发

```bash
go test ./...        # 44 用例:配置/ECH/DoH/通道/MCP 协议全覆盖
go build -o bin/web-search-mcp .
```

## Docker 部署

```bash
docker run -d --name web-search -p 8080:8080 \
  -e PORT=8080 \
  ghcr.io/mdzz-123/web-search-mcp:latest
```

网关配置经请求头传入(见上文协议),容器无需任何供应商环境变量。