Files
hunjing/mcp/web-search/README.md
T
2026-08-21 16:00:13 +08:00

427 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 博闻 Bowen · MCP Web Search
博闻是魂镜的 MCP 联网搜索服务,支持多搜索引擎、多 API 密钥智能轮询、两级缓存和双传输模式。
> 文化命名出自《礼记·中庸》「博闻强识而让」,与辰正、一炁等同属魂镜文化命名体系。工程路径保留 `mcp/web-search`。
---
## 架构概览
```mermaid
flowchart TD
subgraph L1["① 传输层"]
ST["stdio<br/>stdin/stdout JSON-RPC"]
SSE["HTTP SSE<br/>/sse + /messages"]
HTTP["简化 HTTP<br/>/tools/list + /tools/call"]
end
subgraph L2["② 核心处理器"]
H["Handler<br/>传输层共享"]
C["Cache Manager<br/>L1 LRU → L2 Redis"]
end
subgraph L3["③ 负载均衡"]
LB["Load Balancer"]
KP["KeyPool<br/>EWMA 动态权重"]
CB["Circuit Breaker<br/>closed / open / half-open"]
end
subgraph L4["④ 搜索提供商"]
T[Tavily]
B[Bing]
V[Brave]
end
subgraph L5["⑤ 可观测性"]
M["Prometheus Metrics<br/>7 指标"]
S["/status + /health"]
A["管理 API<br/>enable / disable / clear"]
end
ST --> H
SSE --> H
HTTP --> H
H --> C
H --> LB
LB --> KP
KP --> CB
LB --> T
LB --> B
LB --> V
H --> M
H --> S
H --> A
```
### 核心特性
| 特性 | 说明 |
| ------ | ------ |
| **多 Provider** | Tavily、Bing、Brave 三家搜索,统一响应格式 |
| **多 Key 轮询** | 每 Provider 可配多个 API Key,按 EWMA 动态权重(延迟×成功率)自动排序 |
| **熔断器** | 每个 Key 独立熔断(closed → open → half-open),失败 key 自动隔离并恢复探测 |
| **故障转移** | 一个 Key 失败自动换 Key,一个 Provider 耗尽自动切其他 Provider |
| **双传输** | HTTP SSE(远程 Client 直连)+ stdio(子进程集成),由 `--port` / `--stdio` CLI 标志决定 |
| **两级缓存** | L1 内存 LRU(毫秒级)+ L2 Redis(可选,跨实例共享),L2 命中自动升温到 L1 |
| **配置热重载** | fsnotify 监听 YAML 变更,动态调整 provider 启用/禁用、缓存 TTL、重试次数,无需重启 |
| **管理 API** | 运行时启用/禁用 provider、清空缓存、查询密钥状态 |
| **SearchMode** | `first`(首个成功返回,默认低延迟)+ `merge`(并发搜所有引擎,URL 去重排序) |
| **可观测性** | 暗色风格 HTML 监控面板(`:9090`)、Prometheus 指标(`:9090/metrics`)、结构化彩色日志 |
---
## 快速开始
### 前置条件
- Go 1.22+
- 至少一个搜索 API Key(Tavily / Bing / Brave)
- (可选)Redis 用于二级缓存
#### 环境变量快速配置
```bash
# 必填——搜索 API Key(逗号分隔支持多 key 轮询)
export TAVILY_API_KEYS="tvly-key1,tvly-key2"
export BRAVE_API_KEYS="brave-key1"
```
完整环境变量见项目根目录 `.env.example` 中的「博闻」段。
### 启动
```bash
# HTTP SSE 模式(供 MCP Client 远程连接,或供灵台注册为工具)
go run ./cmd/server --port 8080
# stdio 模式(供 Claude Desktop / Cursor 等 MCP Client 子进程集成)
go run ./cmd/server --stdio
# 指定 YAML 配置文件(可选,不传则自动探测 config/mcp/web-search.yaml)
go run ./cmd/server --config ../../config/mcp/web-search.yaml
```
### 验证
```bash
# 存活探针
curl http://localhost:8080/health
# → {"status":"ok"}
# 系统状态
curl http://localhost:8080/status
# → {"version":"2.0.0","uptime":"...","providers":[...],"cache":{...}}
# 工具列表(MCP tools/list)
curl -X POST http://localhost:8080/tools/list
# → {"tools":[{"name":"web_search",...}]}
# 搜索调用
curl -X POST http://localhost:8080/tools/call \
-H "Content-Type: application/json" \
-d '{"tool":"web_search","input":{"query":"hello world"}}'
# 监控面板(浏览器打开)
open http://localhost:9090
```
---
## 端口规划
| 端口 | 用途 | 说明 |
| ------ | ------ | ------ |
| **8080** | MCP 服务端口 | tools/list、tools/call、SSE、管理 API、/health、/status |
| **9090** | 监控端口 | HTML 仪表盘(`/`)、Prometheus 指标(`/metrics`)、存活探针(`/health`) |
## MCP 服务端点(端口 8080)
### MCP 协议端点
| 路径 | 方法 | 说明 |
| ------ | ------ | ------ |
| `POST /tools/list` | POST | 列出可用工具(web_search),含 inputSchema |
| `POST /tools/call` | POST | 调用 web_search 工具执行搜索 |
| `GET /sse` | GET | SSE 事件流(MCP 标准握手) |
| `POST /messages` | POST | MCP JSON-RPC 消息(与 /sse 配对使用) |
| `GET /` | GET | 端点指引(纯文本) |
### web_search 工具
**inputSchema**:
| 参数 | 类型 | 默认 | 说明 |
| ------ | ------ | ------ | ------ |
| `query` | string | (必填) | 搜索关键词 |
| `max_results` | integer | 5 | 最大返回结果数(1–20) |
| `mode` | string | `"first"` | 搜索模式:`first`(首个成功返回) / `merge`(并发搜所有引擎后合并去重) |
| `providers` | string[] | 全部 | 优先使用的搜索引擎 |
**响应**(CallToolResult):
```json
{
"content": [{
"type": "json",
"text": "{\"provider\":\"tavily\",\"query\":\"...\",\"results\":[...],\"total_count\":5,\"cached\":false}"
}]
}
```
### 管理端点
| 路径 | 方法 | 说明 |
| ------ | ------ | ------ |
| `GET /health` | GET | 存活探针 → `{"status":"ok"}` |
| `GET /status` | GET | 系统状态:provider 列表、活跃密钥数、缓存命中率、uptime |
| `GET /admin/keys` | GET | 各 provider 启用状态 |
| `PUT /admin/provider/{name}/enable` | PUT | 启用指定 provider |
| `PUT /admin/provider/{name}/disable` | PUT | 禁用指定 provider |
| `POST /admin/cache/clear` | POST | 清空缓存 |
## 监控面板(端口 9090)
### HTML 仪表盘 `GET /`
暗色风格的可视化面板,展示:
- **四张概览卡片**:Provider 数、活跃密钥数、缓存命中率(绿/黄/红三档)、缓存条目总数
- **Provider 状态表格**:🟢/🔴 状态指示灯、名称、启用/禁用开关、活跃密钥计数
- **缓存统计表格**:L1(内存 LRU)命中、L2(Redis)命中、未命中、综合命中率
- **自动刷新**:每 30 秒自动刷新
- **导航链接**:一键跳转 /status、/admin/keys、/health
### Prometheus 原始指标 `GET /metrics`
| 指标名 | 类型 | 标签 | 说明 |
| -------- | ------ | ------ | ------ |
| `bowen_search_total` | Counter | `provider` | 搜索请求总数 |
| `bowen_search_duration_seconds` | Histogram | — | 搜索耗时分布 |
| `bowen_search_errors_total` | Counter | `reason` | 搜索错误数 |
| `bowen_cache_hits_total` | Counter | — | 缓存命中总数 |
| `bowen_cache_hits_l2_total` | Counter | — | Redis 二级缓存命中数 |
| `bowen_cache_misses_total` | Counter | — | 缓存未命中数 |
| `bowen_active_keys` | Gauge | `provider` | 各 provider 活跃密钥数 |
所有监控端点也可在端口 9090 访问,与 MCP 服务端口独立。
---
## 配置文件
非敏感配置放在项目根目录 `config/mcp/web-search.yaml`(从 `config/mcp/web-search.example.yaml` 复制)。
不传 `--config` 时自动按项目惯例探测:开发模式探测 `../../config/mcp/web-search.yaml`,容器内探测 `/etc/bowen/config.yaml`。
```yaml
server:
port: 8080 # SSE 模式监听端口
read_timeout: 30s # HTTP 读取超时
write_timeout: 30s # HTTP 写入超时
providers:
tavily:
enabled: true # 是否启用(热重载:支持动态变更)
timeout: 10s
max_results: 10
health_check:
interval: 60s
failure_threshold: 3
bing:
enabled: true
timeout: 10s
max_results: 20
brave:
enabled: true
timeout: 10s
max_results: 10
balancer:
strategy: weighted_round_robin
retry_count: 2 # 热重载:支持动态变更
circuit_breaker:
enabled: true
failure_threshold: 5
cooldown: 30s
cache:
enabled: true
ttl: 5m # 热重载:支持动态变更
max_items: 1000
redis:
enabled: false # 需要自行部署 Redis
addr: 127.0.0.1:6379
password: ""
db: 0
metrics:
enabled: true
port: 9090
path: /metrics
```
**配置优先级**:环境变量 > YAML 文件 > 内置默认值
**热重载**:指定 `--config` 后,修改 YAML 文件自动生效(500ms 防抖),支持动态变更:
| 配置项 | 变更后自动生效 |
| -------- | --------------- |
| `providers.{name}.enabled` | 运行时启用/禁用搜索源 |
| `cache.ttl` | 更新新缓存条目的过期时间 |
| `balancer.retry_count` | 更新重试次数 |
---
## 传输模式
博闻支持两种 MCP 传输模式,**不在配置中写死**,由启动 CLI 参数决定:
| 模式 | 启动方式 | 适用场景 |
| ------ | --------- | --------- |
| **SSE** | `--port 8080` | 灵台后端注册(简化 HTTP 端点 `/tools/list` + `/tools/call`)、远程 MCP Client 直连(`/sse` + `/messages`) |
| **Stdio** | `--stdio` | Claude Desktop / Cursor 等 MCP Client 以子进程方式启动,通过 stdin/stdout 走 JSON-RPC 通信 |
两种模式下核心业务逻辑完全共享(`internal/mcp/handler.go`),只有传输层不同。
---
## 环境变量
所有敏感信息通过环境变量注入(参见 `.env.example` 博闻段):
| 变量 | 必填 | 说明 |
| ------ | ------ | ------ |
| `TAVILY_API_KEYS` | 是 | Tavily 搜索 Key,多 key 逗号分隔 |
| `BRAVE_API_KEYS` | 是 | Brave Search Key |
| `MCP_WEB_SEARCH_REDIS_ENABLED` | 否 | Redis 二级缓存开关 |
| `MCP_WEB_SEARCH_REDIS_ADDR` | 否 | Redis 地址(默认 `127.0.0.1:6379`) |
| `MCP_WEB_SEARCH_REDIS_PASSWORD` | 否 | Redis 密码 |
| `MCP_WEB_SEARCH_REDIS_DB` | 否 | Redis 数据库编号(默认 0) |
---
## 开发
```bash
# 运行所有测试
make test
# 编译
make build # → bin/bowen-server
# 本地 SSE 模式
make run-sse
# 本地 stdio 模式
make run-stdio
# 代码检查
make lint
```
开发环境日志自动使用彩色输出(ANSI 颜色代码):
- `INFO` → 绿色
- `WARN` → 黄色
- `ERROR` → 红色
- `DEBUG` → 灰色
生产环境(`ENV=production`)自动切换为 JSON 格式,便于日志采集系统采集。
### 项目结构
```text
mcp/web-search/
├── cmd/server/main.go # 入口(--port / --stdio)
├── internal/
│ ├── config/
│ │ ├── config.go # YAML + 环境变量配置加载
│ │ ├── yaml.go # YAML 解析
│ │ └── reloader.go # 配置热重载(fsnotify)
│ ├── logger/logger.go # slog 双模式(dev 彩色 / prod JSON)
│ ├── models/types.go # 核心数据类型
│ ├── providers/ # 搜索提供商适配
│ │ ├── provider.go # SearchProvider 接口
│ │ ├── tavily.go / bing.go / brave.go
│ │ └── registry.go # 注册中心
│ ├── balancer/ # 负载均衡
│ │ ├── keypool.go # 密钥池(EWMA)
│ │ ├── circuitbreaker.go # 熔断器
│ │ └── balancer.go # LoadBalancer
│ ├── cache/cache.go # 两级缓存
│ ├── mcp/ # MCP 协议实现
│ │ ├── handler.go # 核心处理逻辑
│ │ ├── stdio.go # stdin/stdout JSON-RPC
│ │ ├── sse.go # HTTP SSE 传输
│ │ ├── http.go # 简化 HTTP 端点
│ │ ├── status.go # /health + /status
│ │ ├── admin.go # 管理 API
│ │ └── monitor.go # HTML 监控面板
│ └── metrics/metrics.go # Prometheus 指标
├── Makefile
├── go.mod / go.sum
```
---
## Docker
```bash
# 构建镜像
docker build -t bowen-web-search -f ../../docker/Dockerfile.web-search .
# 运行
docker run \
-e TAVILY_API_KEYS=tvly-xxx \
-e BRAVE_API_KEYS=brave-xxx \
-p 8080:8080 -p 9090:9090 \
bowen-web-search
```
Docker 镜像可通过魂镜的 `docker/docker-compose.app.yml` 编排启动。
---
## 集成到灵台
后端通过 MCP 注册 API 自动接入博闻:
```bash
# 注册博闻为 MCP Server
curl -X POST http://localhost:8000/api/v1/tools/mcp-servers \
-H "Content-Type: application/json" \
-d '{
"name": "bowen-web-search",
"endpoint_url": "http://localhost:8080",
"status": "enabled"
}'
```
注册后灵台会拉取工具清单,Agent 即可在对话中直接调用 `web_search` 工具。
---
## 搜索模式对比
| 模式 | 延迟 | 召回 | 使用场景 |
| ------ | ------ | ------ | --------- |
| `first`(默认) | 低(首个成功即返回) | 单 provider | 快速查天气、新闻、百科等简单查询 |
| `merge` | 中(等最慢 provider) | 高(多源合并去重) | 研究、对比多个来源、需要全面 coverage |
---
## 技术栈
| 类别 | 选型 |
| ------ | ------ |
| 语言 | Go 1.22+ |
| Web | 标准库 `net/http` |
| 日志 | 标准库 `log/slog` + 自定义彩色 Handler |
| 配置 | `gopkg.in/yaml.v3` + 环境变量 |
| 内存缓存 | `hashicorp/golang-lru/v2` |
| Redis | `redis/go-redis/v9`(可选) |
| 监控 | `prometheus/client_golang` |
| 热重载 | `fsnotify` |
| 测试 | 标准库 `testing` |
| 容器化 | Docker 多阶段构建 |