内容
# 网关 websearch-gw
适用于编码代理(Pi、Claude Code)的自托管、零成本网页搜索网关,通过 MCP
streamable-http。SearXNG(搜索)+ Crawl4AI(页面读取)+ Valkey(缓存)位于一个网关
进程后面,该进程暴露了两个工具:`web_search` 和 `web_read`。无需 API 密钥,无需付费层,
无需任何托管备用方案。请参阅 `PLAN.md` 以获取完整的设计原理和构建历史。
```
Claude Code ──┐ ┌── SearXNG :8080 仅搜索
├──► websearch-gw :8090/mcp ──┼── Crawl4AI :11235 仅读取
Pi 代理 ────┘ (streamable-http) └── Valkey :6379 仅缓存
```
## 先决条件
- Docker(或 OrbStack 作为 Docker 后端 — 该项目已针对 OrbStack 进行构建和测试)。
如果在新 shell 中 `docker` 不在 `PATH` 中:
```bash
export PATH="/usr/local/bin:/opt/homebrew/bin:$HOME/.orbstack/bin:$PATH"
```
`docker version` 应显示 `Context: orbstack`(或您正在使用的后端)。
- Python 3.11+(仅在本地运行时运行网关的测试套件需要,不需要运行堆栈本身 — 堆栈完全容器化)。
## 首次设置
```bash
git clone <此仓库> websearch-gw && cd websearch-gw
./scripts/gen-secret.sh # 生成 searxng/settings.yml
cp .env.example .env
sed -i '' "s/^CRAWL4AI_API_TOKEN=$/CRAWL4AI_API_TOKEN=$(openssl rand -hex 32)/" .env
docker compose up -d --build
docker compose ps # 所有四个容器应在 ~30 秒内报告健康状态
curl -s localhost:8090/health # {"status":"ok", ...}
```
**为什么 `.env` 是必需的,而不是可选的:** Crawl4AI 的镜像有一个刻意的默认安全设置 —
如果没有设置 `CRAWL4AI_API_TOKEN`,其服务器将绑定到容器自己的环回地址,并且根本不公开
暴露给其他容器,即使它们在同一个 Docker 网络中。设置令牌既修复了绑定,又将 Crawl4AI
切换到 Bearer-authenticated 模式;网关持有此令牌,代理永远不会看到它。
## 连接客户端
**Pi** — 安装 MCP 桥接扩展并指向网关:
```bash
pi install npm:pi-mcp-extension
```
`~/.pi/agent/mcp.json`:
```json
{
"settings": { "toolPrefix": "mcp" },
"mcpServers": {
"websearch-gw": {
"transport": "streamable-http",
"url": "http://127.0.0.1:8090/mcp",
"lifecycle": "eager"
}
}
}
```
**Claude Code**:
```bash
claude mcp add --transport http websearch-gw http://127.0.0.1:8090/mcp
```
## 工具
- **`web_search(query, depth, count, freshness)`** — `depth="links"`(默认)返回原始
SearXNG 结果。`depth="snippets"`/`"full"` 过度获取候选池,并使用
FlashRank(免费的本地跨编码器)重新排序,然后修剪到 `count`;`"full"` 还
为前几个重新排序结果获取页面内容。
- **`web_read(url, size, query, page)`** — 将 URL 读取为干净的 Markdown。`size` 为 `s`/`m`/`l`
(截断)或 `full`(分页,25K 字符边界)。如果提供 `query`,则激活
查询相关性过滤,而不是通用样板移除。结果缓存在 Valkey 中。
获取请求通过分层备用方案(直接 API 跳过 → Crawl4AI → 原始 HTTP → Wayback
机器),因此一个阻止或 JS 繁重的页面不会直接失败。
两个工具在任何网络调用之前都会拒绝不安全的 URL(环回/私有/链路本地 IP,一个小型主机名阻止列表)。
## 仓库布局
| 路径 | 内容 |
|---|---|
| `docker-compose.yml` | 4 容器堆栈 — 所有端口仅绑定到 `127.0.0.1` |
| `searxng/` | SearXNG 配置(`settings.yml.template` 已提交;`settings.yml` 已生成,gitignored) |
| `gateway/` | MCP 网关本身 — 请参阅 `gateway/README.md` 以获取其内部实现和测试套件 |
| `scripts/gen-secret.sh` | 生成 `searxng/settings.yml` 并带有新密钥 |
| `PLAN.md` | 设计文档:架构原理、先前研究、构建顺序、开放风险 |
## 验证堆栈
```bash
curl -s 'http://localhost:8080/search?q=test&format=json' | head -c 300 # SearXNG JSON API
curl -s http://localhost:11235/health # Crawl4AI 存活状态
docker exec websearch-valkey valkey-cli ping # -> PONG
curl -s localhost:8090/health # 网关 + 所有 3 个依赖项
```
## 运行网关的测试套件
```bash
cd gateway
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q -m "not live_stack" # 确定性子集,不需要运行堆栈
pytest -q # 包括针对正在运行的堆栈的所有内容在内的所有测试
```
请参阅 `gateway/README.md` 以了解每个测试文件的作用。
## 未构建的内容
锁定(使代理无法访问网页 — 在 Pi 上阻止 `bash curl`/`wget`,
在 Claude Code 上拒绝 `WebSearch`/`WebFetch`)和可选的查询扩展是故意
未构建的。请参阅 PLAN.md §7 了解为什么锁定在构建顺序中是最后一步。