初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,426 @@
|
||||
# 博闻 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 多阶段构建 |
|
||||
Reference in new issue
Block a user