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
```
网关配置经请求头传入(见上文协议),容器无需任何供应商环境变量。
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues