Skip to main content
Glama
Huakira

Grok SearXNG Adapter MCP

by Huakira
README.md
# Grok SearXNG Adapter

> **自用 fork,AI 生成,不负责。**
> 本仓库把 Grok/xAI Responses API 的 `web_search` 重写为单容器,同时提供 SearXNG 兼容 HTTP 接口与 Streamable HTTP MCP。代码由 AI 辅助生成,仅供本人部署使用,不提供任何稳定性承诺或售后支持。能用就拿走,不能用请不要提 issue 索赔。

把 Grok/xAI Responses API 的 `web_search` 包装成 SearXNG 兼容的 HTTP 搜索接口,供 LobeHub 与 Open WebUI 使用;同时提供 Streamable HTTP MCP 端点,给支持 MCP 的 Agent 复用同一份 Grok 搜索与 Firecrawl 原文抓取能力。服务只使用本地 Docker Compose 构建,不推送镜像。

`web_search` 是 xAI 的服务端工具:它可在一次请求内搜索,并按模型需要浏览网页。`/search` 适配器要求模型在最终文本中返回带 URL 的 JSON 结果,这是兼容中转最稳定的契约;若中转未按要求输出 JSON,才无额外请求地尝试读取其原生来源字段。

## 同一容器的两条入口

同一个 FastAPI/ASGI 进程在 Docker 容器内同时提供:

- `GET/POST /search`:SearXNG 兼容 JSON,服务 LobeHub 与 Open WebUI。
- `/mcp`:Streamable HTTP MCP(stateless),提供 Grok 搜索与 Firecrawl 原文抓取。

MCP 默认工作流是“先搜索,再按需读取原文”:

```text
Agent
  ├─ web_search(query) ──> Grok web_search ──> URL、标题、摘要
  └─ web_fetch(urls)  ───> Firecrawl       ──> 原始 Markdown + metadata
```

Grok 内部浏览过的完整页面内容不会通过 Responses API 无损暴露。Firecrawl
`web_fetch` 的作用是把 Agent 选中的原始网页正文真正放回 Agent 上下文,而不是
给 Grok 的回答再做一次摘要。普通搜索不会自动调用 Firecrawl,避免增加延迟和费用。

`/mcp` 默认暴露 `web_search` 与 `web_fetch` 两个只读工具;可选的 `web_research`
(让 Grok 直接返回带引用的综合回答)由 `MCP_RESEARCH_ENABLED` 控制是否暴露,默认关闭。

## 部署

```bash
cp .env.example .env
# 编辑 .env,填入 GROK_BASE_URL、GROK_API_KEY、API_KEY
cp docker-compose.example.yml docker-compose.yml
# 按需编辑 docker-compose.yml(端口、BIND_ADDRESS 等)
docker compose up -d --build
```

仓库只提交 `docker-compose.example.yml`;你的 `docker-compose.yml` 由 `cp` 生成,被 `.gitignore` 忽略,可以放本机定制。Compose 只在服务器本机构建镜像,**不会推送到任何镜像仓库**。默认监听 `127.0.0.1:8080`;需要让其他机器访问时,设置 `BIND_ADDRESS=0.0.0.0` 并由防火墙或反向代理保护。

## 环境变量

| 变量 | 必填 | 说明 |
| --- | --- | --- |
| `GROK_BASE_URL` | 是 | Grok 中转站的 OpenAI 兼容 API 根地址,通常含 `/v1` |
| `GROK_API_KEY` | 是 | 本服务请求中转站的密钥 |
| `GROK_MODEL` | 是 | 中转站提供的 Grok 模型名 |
| `GROK_API_MODE` | 否 | `responses`(默认,Grok 服务端 Web Search)或 `chat_completions`(仅中转明确支持该端点的 Web Search tools 时使用) |
| `GROK_SEARCH_TOOL_TYPE` | 否 | 搜索工具类型,默认 `web_search` |
| `API_KEY` | 是 | LobeHub/Open WebUI 请求本服务时的访问密钥 |
| `REQUEST_TIMEOUT` | 否 | 上游超时秒数,默认 `240` |
| `UPSTREAM_RETRY_ATTEMPTS` | 否 | 上游 5xx 的最大尝试次数,默认 `3` |
| `MAX_RESULTS` | 否 | 单请求最大结果数,默认 `10` |
| `SEARCH_MAX_USES` | 否 | Grok web search 最大工具使用次数,默认、推荐均为 `1`;更高值容易让兼容中转变慢或超时 |
| `MAX_RESULTS_PER_DOMAIN` | 否 | 同一域名的最多结果数,默认 `3`;允许同一机构、媒体或社交平台的多条高相关结果,不屏蔽任何站点 |
| `BIND_ADDRESS` / `PORT` | 否 | 对外宿主机绑定地址和端口,默认 `127.0.0.1:8080`(容器内部固定为 8080) |

MCP 与 Firecrawl:

| 变量 | 必填 | 说明 |
| --- | --- | --- |
| `MCP_ENABLED` | 否 | 是否挂载 `/mcp`,默认 `true`;设为 `false` 则 `/mcp` 不存在,`/search` 不受影响 |
| `MCP_RESEARCH_ENABLED` | 否 | 是否暴露可选的 `web_research` 工具,默认 `false` |
| `FIRECRAWL_API_KEY` | 启用 `web_fetch` 时是 | 本服务调用 Firecrawl 的上游密钥;与 `GROK_API_KEY`、客户端 `API_KEY` 用途不同;缺失时 `web_search` 仍可用,`web_fetch` 返回明确未配置错误 |
| `FIRECRAWL_BASE_URL` | 否 | Firecrawl API 地址,默认 `https://api.firecrawl.dev`,可指向兼容的自托管服务 |
| `FIRECRAWL_TIMEOUT` | 否 | 单次 Firecrawl scrape 超时秒数,默认 `60` |
| `FIRECRAWL_RETRY_ATTEMPTS` | 否 | Firecrawl 5xx/超时重试次数,默认 `2` |
| `FETCH_MAX_URLS` | 否 | 单次 `web_fetch` 最多读取的 URL 数,默认 `5` |
| `FETCH_MAX_CHARS` | 否 | 每页返回给 Agent 的正文字符上限,默认 `40000`,防止上下文膨胀 |

## 接口

`GET /search?q=OpenAI&format=json&count=5` 和 `POST /search` 都返回 SearXNG JSON。请求必须带以下任一头:

```text
Authorization: Bearer <API_KEY>
X-API-Key: <API_KEY>
```

若客户端只有 URL 配置、无法添加请求头(常见于 SearXNG 集成),在 URL 保留查询参数:

```text
http://<server>:18763/search?api_key=<API_KEY>
```

查询参数会出现在客户端配置和部分访问日志中;优先使用请求头,或只在受信任的私有部署中使用该方式。LobeHub 本身不提供独立 API Key 字段,因此它只能使用此方式。

健康检查无需鉴权:`GET /healthz`。

### MCP 端点 `/mcp`

`/mcp` 是 Streamable HTTP MCP(stateless,每次请求独立会话,无需 `Mcp-Session-Id`)。鉴权与 `/search` 共用 `API_KEY`,只接受请求头,不读 URL 查询参数:

```text
Authorization: Bearer <API_KEY>
X-API-Key: <API_KEY>
```

工具:

- `web_search(query, max_results?, search_mode?)`:复用与 `/search` 相同的 Grok provider;返回 `{query, results:[{url,title,content,source_type}]}`。`search_mode` 取 `balanced`/`realtime`/`social`/`unrestricted`,作为提示影响结果偏好,不屏蔽任何站点。
- `web_fetch(urls, question?, max_chars?)`:用 Firecrawl 抓取已知 URL 的原始 Markdown;按 URL 独立记录错误,允许部分成功;超长正文按 `FETCH_MAX_CHARS` 裁剪并标记 `truncated=true`。只允许 HTTP/HTTPS,拒绝解析到私网/回环的地址与重定向,降低 SSRF 风险。
- `web_research(query)`:可选(`MCP_RESEARCH_ENABLED=true` 时才暴露)。让 Grok 综合搜索、浏览并带引用回答;返回 `{query, answer, sources, limitations}`。这是二手综合结果,需要原文时仍应 `web_fetch`。

## 客户端配置

- LobeHub:设置 `SEARXNG_URL=http://<server>:8080?api_key=<API_KEY>`。LobeHub 会自动追加 `/search`;适配器会兼容其把 `/search` 拼入 `api_key` 值的请求形式。
- Open WebUI:选择 SearXNG,URL 填 `http://<server>:8080/search?api_key=<API_KEY>`。如界面提供独立 API Key 字段,也可改用 `X-API-Key`。

LobeHub 的内置 Web Search 与模型内置搜索是不同路径。若要使用模型的服务端搜索,请在 Agent 中关闭 `lobe-web-browsing/search` 与 `crawlMultiPages`,避免“LobeHub 搜索 + Jina 抓取”和模型内置浏览重复执行。

## 延迟与稳定性

- 每次请求只进行一次 Grok `web_search` 调用,并以模型最终输出的结构化 JSON 作为主结果来源。
- 原生 sources/citations 仅在模型没有按约定输出 JSON 时使用;它是无额外请求的兼容兜底,不是主路径。
- SearXNG 格式只能表达 URL、标题和短摘要,不能把 Grok 服务端浏览到的完整页面上下文无损传给 LobeHub。深度研究应使用 `/mcp` 的 `web_search` + `web_fetch`,或单独的 Grok/MCP 工具。
- `/mcp` 使用 stateless Streamable HTTP:每个请求独立处理,客户端不需要维护 `Mcp-Session-Id`。搜索与 fetch 不自动串联,避免每次查询都承担抓取延迟。

## 更新

```bash
git pull
docker compose up -d --build
```