searxng-mcp
searxng-mcp
一个用于通过自托管的 SearXNG 实例进行私有网络搜索的 MCP 服务器。结果由本地 ML 模型重新排序,完整页面内容通过 Firecrawl 获取,可选的 Ollama 实例提供查询扩展和 LLM 合成的摘要。
专为需要网络搜索但不想将查询发送给第三方搜索 API 的 Claude Code 和 LibreChat 代理设计。
使用 Claude Code 构建,采用 homelab-agent 中的多代理工作流——该平台在生产环境中使用 searxng-mcp 进行 AI 辅助研究。
快速开始
需要一个正在运行的 SearXNG 实例。强烈建议使用缓存后端。
最小化部署——启动一个 Dragonfly/Valkey 缓存后端并运行 searxng-mcp:
docker compose -f docker-compose.example.yml up -d
SEARXNG_URL=http://localhost:8081 CACHE_URL=redis://localhost:6381 npx @tadmstr/searxng-mcp如需包含 Firecrawl、Crawl4AI、Ollama、Kiwix、广告拦截代理和 NATS 的完整本地拓扑,请参阅 docker-compose.full.yml。
Related MCP server: searxng-mcp-bridge
工具
工具 | 描述 | 关键参数 |
| 通过 SearXNG 搜索并进行本地重排序。获取更广泛的结果池,按相关性重新排序,返回前 N 条。SearXNG 原生的直接答案、信息框、拼写纠正和相关建议会显示在列表上方以及 |
|
| 搜索、重排序,然后使用获取级联(Firecrawl → Crawl4AI → 原始 HTTP)获取前几个结果的完整内容。 |
|
| 搜索、获取前几个结果,然后通过 Ollama( |
|
| 从任何公共 URL 获取并提取可读的 Markdown。GitHub 主机走 GitHub 快速路径;YouTube 视频 URL 返回字幕,Reddit 帖子 URL 返回帖子+评论(两者均通过 robots 选择加入,见下文);其他所有 URL 使用获取级联(Firecrawl → Crawl4AI → 原始 HTTP)。按 token 预算截断(默认约 8,000 字符)。 |
|
| 抓取整个网站并返回每个页面的 URL/标题/摘要清单。先尝试 Firecrawl 抓取,回退到 sitemap 解析,然后可选 BFS。完整页面内容缓存在 Valkey 中,因此后续的 |
|
| 清除搜索缓存、获取缓存、抓取清单缓存或全部。适用于研究快速变化主题时缓存结果可能过时的情况。 |
|
| 域名能力数据库的只读视图。带 |
|
参数
category — general(默认)、news、it、science
time_range — day、week、month、year — 按发布日期限制结果。省略则返回所有时间的结果。
fetch_count — 要获取完整内容的前几个重排序结果的数量(search_and_fetch 默认 1,最大 3;search_and_summarize 默认 3,最大 5)。
domain_profile — 应用命名的域名过滤配置文件:homelab(突出自托管/Linux 文档)或 dev(突出 Stack Overflow、MDN、npm)。省略则使用默认过滤器。
expand — 当为 true 时,在搜索前通过 Ollama(OLLAMA_EXPAND_MODEL)重写查询以提高召回率。需要 OLLAMA_URL。默认为 EXPAND_QUERIES 环境变量的值。
language — BCP-47 语言代码(例如 en、de)或 all 以限制为特定语言。省略则使用 SearXNG 实例默认值。适用于 search、search_and_fetch 和 search_and_summarize。
engines — 逗号分隔的 SearXNG 引擎名称,用于限制搜索范围(例如 google,duckduckgo)。原样转发;未知/禁用的引擎会退化为较少结果而不是报错。适用于所有三个搜索工具。
site — 将结果限制为一个域名或列表(例如 github.com 或 ["github.com", "gitlab.com"])。尽力作为 site: 查询运算符应用——大多数引擎(Google、Bing、DDG、Brave)会遵守,有些会忽略。适用于所有三个搜索工具。
max_tokens(fetch_url)— 返回内容的近似 token 预算(字符 ≈ token × 4)。省略则使用约 2,000 token / 8,000 字符的默认值;最大 10,000 token。
target_selector(fetch_url)— CSS 选择器,用于将提取范围限定到特定元素(例如 article、main .content)。Firecrawl/Crawl4AI 原生支持,并在原始 HTTP 层客户端应用;快速路径和未匹配时忽略。
wait_for_selector(fetch_url)— 提取前等待的 CSS 选择器,用于 JS 渲染页面。渲染层(Firecrawl/Crawl4AI)支持;原始 HTTP(无 JS)忽略。
架构
MCP client (stdio)
│
▼
searxng-mcp ──────────────→ cache ($CACHE_URL) → result cache (search 1h, fetch 24h, crawl 6h)
│
├── expand (optional) → Ollama ($OLLAMA_URL) → rewritten query (qwen3:4b)
├── search ───────────→ SearXNG ($SEARXNG_URL) → raw results
├── rerank ───────────→ Reranker ($RERANKER_URL) → ranked results
│ (fallback: SearXNG order if reranker unavailable)
├── fetch content ────┬→ GitHub API (github.com) → markdown
│ ├→ Kiwix ($KIWIX_URL) → ZIM content (Wikipedia/SO/Arch Wiki, fast path)
│ ├→ Hister ($HISTER_URL) → browsing-history index (login-walled/JS-heavy fast path)
│ ├→ Firecrawl ($FIRECRAWL_URL) → page markdown (tier 1)
│ ├→ Crawl4AI ($CRAWL4AI_URL) → page markdown (tier 2, optional; via $ADBLOCK_PROXY_URL if set)
│ ├→ Raw HTTP + Readability → page markdown (tier 3 fallback; via $ADBLOCK_PROXY_URL if set)
│ └→ Wayback Machine (opt-in) → archived page markdown (tier 4, $WAYBACK_ENABLED)
├── crawl_site ───────┬→ Firecrawl crawl → page manifest (phase 1)
│ ├→ Sitemap parsing → page manifest (phase 2 fallback, fast-xml-parser)
│ └→ BFS crawl (opt-in) → page manifest (phase 3, $CRAWL_BFS_ENABLED)
└── summarize (opt.) → Ollama ($OLLAMA_URL) → synthesized summary ($OLLAMA_SUMMARIZE_MODEL)flowchart TD
entry["fetchPage(url)"]
cache{"Valkey cache hit?"}
cached["→ return cached { title, url, text }"]
github{"GitHub host?\ngithub.com · raw · api"}
gh_fetch["GitHub API / raw.githubusercontent.com / api.github.com\n→ return"]
llms{"llms.txt domain?"}
llms_fetch["Probe /llms-full.txt\nextract matching section\n→ return"]
kiwix{"Kiwix host?\nKIWIX_URL set"}
kiwix_fetch["Local Kiwix ZIM\nWikipedia · Stack Overflow · Arch Wiki\n→ cache + return"]
pdf{".pdf URL?"}
robots["robots.txt pre-check — tiers 1–3\ndisallowed → RobotsDisallowedError (cached 24h)"]
tier_skip(["Per-domain tier skip\nsuccess rate <30% over ≥10 tries\nor tier_skip operator override"])
t1["Tier 1 — Firecrawl\n$FIRECRAWL_URL"]
t2["Tier 2 — Crawl4AI\n$CRAWL4AI_URL · optional\nadblock proxy if $ADBLOCK_PROXY_URL"]
t3["Tier 3 — Raw HTTP + Readability\nfallback: raw HTML slice\nadblock proxy if $ADBLOCK_PROXY_URL"]
t4["Tier 4 — Wayback Machine CDX API\narchived snapshot · WAYBACK_ENABLED=true"]
post["Post-extraction\nJSON-LD Article · title cascade\nog:title → twitter:title → title → h1 → URL"]
result["→ return { title, url, text }"]
entry --> cache
cache -->|hit| cached
cache -->|miss| github
github -->|yes| gh_fetch
github -->|no| llms
llms -->|yes| llms_fetch
llms -->|no| kiwix
kiwix -->|yes| kiwix_fetch
kiwix -->|no| pdf
pdf -->|"yes — skip tier 1"| t2
pdf -->|no| robots
robots --> tier_skip
tier_skip --> t1
t1 -->|success| post
t1 -->|"empty / error"| t2
t2 -->|success| post
t2 -->|"empty / error"| t3
t3 -->|success| post
t3 -->|"empty / error"| t4
t4 -->|success| result
post --> result
style entry fill:#ffffff,stroke:#333333,color:#000000
style cache fill:#ffffff,stroke:#333333,color:#000000
style cached fill:#ffffff,stroke:#333333,color:#000000
style github fill:#dae8fc,stroke:#6c8ebf,color:#000000
style gh_fetch fill:#dae8fc,stroke:#6c8ebf,color:#000000
style llms fill:#dae8fc,stroke:#6c8ebf,color:#000000
style llms_fetch fill:#dae8fc,stroke:#6c8ebf,color:#000000
style kiwix fill:#fff9c4,stroke:#b8860b,color:#000000
style kiwix_fetch fill:#fff9c4,stroke:#b8860b,color:#000000
style pdf fill:#ffffff,stroke:#333333,color:#000000
style robots fill:#ffffff,stroke:#333333,color:#000000
style tier_skip fill:#f5f5f5,stroke:#666666,color:#000000
style t1 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
style t2 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
style t3 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
style t4 fill:#f8cecc,stroke:#a03030,color:#000000
style post fill:#e1d5e7,stroke:#7a5a8a,color:#000000
style result fill:#ffffff,stroke:#333333,color:#000000SearXNG 和 Firecrawl 是必需的。Crawl4AI、Valkey、Ollama、Kiwix 和重排序器是可选的——当其中任何一个不可用时,服务器会优雅降级。
广告拦截
searxng-mcp 使用两个独立的广告拦截边车,每个获取层级组一个:
边车 | 层级 | 机制 |
| 第 1 层(Firecrawl) | CDP 级拦截——完整 HTTPS 过滤,同一浏览器进程 |
| 第 2+3 层(Crawl4AI、原始获取) | HTTP 转发代理——过滤纯 HTTP 广告域名 |
第 1 层 — Puppeteer 广告拦截
Firecrawl 使用的 firecrawl-puppeteer 服务运行自定义镜像(docker/puppeteer-adblock/),该镜像在上游 trieve/puppeteer-service-ts 之上叠加了 @ghostery/adblocker-puppeteer。启动时加载 EasyList + EasyPrivacy,每 168 小时刷新一次;拦截器应用于 Firecrawl 创建的每个页面。加快广告密集型网站的获取速度并缩小渲染 DOM 大小。
环境变量:
变量 | 默认值 | 描述 |
| 未设置 | 设置为 |
| EasyList + EasyPrivacy | 逗号分隔的过滤器列表 URL。 |
|
| 拦截器从配置的 URL 重建的节奏。 |
基础镜像按 SHA256 摘要固定。要部署更改,请重建并重启服务:
docker compose -f ~/docker/firecrawl-simple/docker-compose.yml up -d --build firecrawl-puppeteer按域名绕过: domains.json 为未来的操作员覆盖保留了 adblock_skip 槽位。接线尚未实现——这需要 Firecrawl 将自定义标头转发到 puppeteer-service,而这不在其当前 API 中。跟踪为范围蔓延项 I。
第 2+3 层 — 广告拦截代理
设置 ADBLOCK_PROXY_URL(例如 http://adblock-proxy:8118)以将 Crawl4AI 和原始 Node fetch 请求路由到 HTTP 转发代理,该代理过滤广告和跟踪器请求。HTTPS CONNECT 隧道原样传递——无 MITM,因此过滤仅适用于纯 HTTP 广告域名。第 1 层的 puppeteer 钩子已处理该层的完整 HTTPS 过滤;代理覆盖第 2 层和第 3 层泄漏的内容。
参见 docker/adblock-proxy/ 了解服务定义、配置选项和部署说明(包含在 docker-compose.full.yml 中)。
数据驱动的层级路由
在调用获取级联之前,searxng-mcp 读取域名的 tier_stats_30d(参见 域名能力数据库),并跳过任何在至少 10 次尝试中成功率低于 30% 的层级。冷启动域名(少于 10 次尝试)保留默认级联。每次跳过都会发出 searxng.fetch.tier.skipped NATS 事件,reason: low_success_rate,并递增 searxng_fetch_total{outcome=skipped}。
操作员覆盖。 在 domains.json 中添加 tier_skip 映射以强制跳过层级,无论统计如何:
{
"tier_skip": {
"example-bot-blocked.com": ["tier1"],
"another-site.example": ["tier1", "tier2"]
}
}tier_skip 键可以是裸域名(example.com 匹配该域名及其所有子域名)或域名 + 路径前缀(example.com/api/)。文件热重载——无需重启。手动覆盖发出 reason: operator_override。
内容类型快速路径
提供结构化、非 HTML 内容的 URL —— application/json、任何 *+json、XML、YAML、TOML、CSV 或 text/plain —— 通过 HEAD 探测检测,并直接路由到原始 HTTP 层,而不是完整的 Firecrawl/Crawl4AI 级联。JSON 以美化格式返回,并放在围栏代码块中。以前,要求无头浏览器渲染 JSON API 响应或 CDN 资源会返回空 markdown,因此 API 和 CDN 端点(registry.npmjs.org、api.osv.dev、cdn.jsdelivr.net 等)直接失败。
保证:
探测是故障开放的。无法访问的主机、拒绝
HEAD的服务器或不可读/无法解析的Content-Type头都会照常落入正常级联。application/xhtml+xml被有意排除——那是给浏览器看的标记,不是结构化数据。服务器错误标记为
text/plain的 HTML 仍会被解析为 HTML,而不是作为原始文本块转储。
域名能力数据库
每次抓取都会将 searxng-mcp 了解到的目标域名信息记录到 Valkey 的 domain:<hostname> 下(90 天 TTL,schema_version 5)。每条记录捕获:
tier_stats_30d.{tier1,tier2,tier3,tier4,github}.{attempts, ok, fail, last_fail_reason, window_start_ms}—— 滚动 30 天窗口内每个层的抓取成功率。截止时间在读取时应用,由层路由决策和domain_stats报告共享,因此两者不会不一致——一个被抓取一次然后闲置的域名会报告一个真正空的窗口,而不是让过时数字一直存活到下次写入。tier4(Wayback Machine)槽位仅在WAYBACK_ENABLED=true时记录。github槽位记录 GitHub 快速路径(raw.githubusercontent.com/api.github.com/github.comREADME 抓取),该路径绕过层级联但仍在此跟踪。schema_version提升会重新构建现有记录——当前闲置域名的累积窗口会被丢弃(在 1→2、2→3、3→4、4→5 的提升中已有先例)。capabilities.metadata_fetch.{attempts, ok, fail, last_fail_reason}—— 元数据侧信道抓取(fetchRawHtmlForMetadata,用于 JSON-LD/og:title 采样)的成功/失败。与tier_stats_30d分开跟踪,因为它回答的是“该域名是否可达”,而不是“完整内容交付是否成功”。capabilities.seen_in_search.{count, last_seen_ms}—— 该域名在search结果中出现的频率。由searxSearch()在每条返回路径上(包括缓存命中)以即发即忘方式写入,不执行抓取,因此域名可以在被抓取之前就被跟踪。capabilities.robots_txt.{present, fetched, allows_us}—— robots.txt 是否存在以及是否允许我们capabilities.llms_full_txt.{present, size_bytes, last_checked}—— 该域名是否提供/llms-full.txtcapabilities.json_ld_article.{sampled, present, last_sampled_at}—— 页面是否带有 Article 模式的 JSON-LD(Schema.orgArticle/NewsArticle/BlogPosting/TechArticle及其子类型如ScholarlyArticle/OpinionNewsArticle/LiveBlogPosting,通过裸名称或完全限定的https://schema.org/...@type匹配),无论该模式是否有可提取的正文文本——许多网站发布只有标题/元数据的 JSON-LD,没有articleBody,这与 提取后处理 实际使用它不同。capabilities.og_title.{sampled, present, last_sampled_at}—— 与<meta property="og:title">相同preferred_strategy—— 当前在探测命中时设置为llms_full_txt;未来阶段将使用它来跳过层级联
使用捆绑的 CLI 检查记录,或通过 domain_stats 工具从代理查询(单域名或聚合;参见 工具):
pnpm dump-domain docs.anthropic.comdump-domain 区分已过期的窗口和完全没有数据的层,而不是将两者显示为相同。
同一主机名的并发更新(一次抓取期间并行触发的层尝试、robots 探测和提取后采样记录器)通过服务器端 Lua 比较并设置(CAS)序列化,并配以进程内每键队列,消除单个进程自身写入者之间的争用,因此 CAS 只需仲裁跨进程的真正并发写入。v3.17.0 之前的版本使用共享连接上的 WATCH/MULTI/EXEC 读-修改-写,这实际上并未序列化并发写入者——因此 v3.17.0 之前收集的数据大幅不完整。升级会通过 schema 提升丢弃现有的层统计;升级后 domain_stats 预计会接近空,并在接下来的几天内重新填充。
域名数据库持久化
域名数据库仅存在于 Valkey 中,具有 90 天 TTL 和 30 天滚动窗口,因此缓存刷新或 TTL 过期会擦除重新获取成本高昂的能力学习。两个 CLI 使其持久化:
pnpm domain-db-maintenance # SCAN all domain:* records → write a dated JSON snapshot (+ prune) and emit OTel gauges
pnpm restore-domain-db # re-seed the domain-db from the newest snapshot after a flushdomain-db-maintenance是一个独立作业(通过 cron 或 PM2 cron-restart 按计划运行——不是进程内定时器,因为 searxng-mcp 作为多个并发的每代理 stdio 子进程运行,每个都会触发它)。一次有界的SCAN同时提供两个输出:一个持久的带日期快照,以及当设置了OTEL_EXPORTER_OTLP_ENDPOINT时,在退出前强制刷新的指标(searxng_domains_tracked、searxng_domains_failing、searxng_domain_tier_success_ratio{tier})。restore-domain-db仅重新播种缺失的键或实时记录严格比快照更旧的键(比较last_fetch)——它绝不会覆盖更新或相等的实时记录,因此可以安全地针对活跃、部分填充的 Valkey 运行(例如在服务启动序列中用于自动刷新恢复)。
环境变量 | 默认值 | 用途 |
|
| 写入/读取带日期快照的位置。在部署中设置为持久路径(应用数据或 NFS 挂载)。 |
|
| 保留多少快照;每次维护运行时会修剪较旧的。 |
llms.txt 快速路径
对于 domains.json(llms_txt 数组)中白名单的文档域名,fetchPage 在调用任何层之前先尝试 <origin>/llms-full.txt,并提取与请求 URL 匹配的部分。这避免了针对文档完善的站点运行 puppeteer,并直接返回干净的 markdown 部分。探测结果和完整正文缓存在 Valkey 中(llms:<origin>:full,存在/不存在分别为 24 小时 / 7 天)。默认白名单:docs.anthropic.com、docs.openai.com、docs.stripe.com、docs.crawl4ai.com、docs.firecrawl.dev、docs.cursor.com。通过编辑 domains.json 扩展——该文件会热重载。
Kiwix 快速路径
当设置 KIWIX_URL 时,对已知可离线主机的抓取请求会在 Firecrawl/Crawl4AI 级联之前被拦截,并从本地 Kiwix ZIM 存档提供。这消除了像 Wikipedia(阻止无头抓取器)这样的站点 100% 的 tier-1 失败率,并返回干净可读的内容,零外部网络流量。
支持的主机和 ZIM 书籍(kiwix-serve 必须使用 --nodatealiases / -z 运行):
主机 | ZIM 书籍 |
|
|
|
|
|
|
Kiwix 路径在 llms-txt 快速路径之后、robots 门之前运行。如果 Kiwix 请求失败或返回空,则正常运行完整层级联。当 KIWIX_URL 未设置时,该功能零开销——isKiwixHost() 立即返回 false。
将 KIWIX_URL 设置为你的 kiwix-serve 基础 URL(例如 http://localhost:8292)。
YouTube 和 Reddit 快速路径
fetch_url 识别 YouTube 视频 URL(youtube.com、youtu.be)和 Reddit 帖子 URL,并可以直接提供它们,而不是抓取渲染后的页面:
YouTube —— 从观看页面提取视频的字幕轨道并返回转录文本。由
YOUTUBE_TRANSCRIPT_ENABLED启用(默认开启)。Reddit —— 获取公开的
.json视图,并返回帖子及顶级评论,采用标准{title, url, text}形状;在 HTTP 429 时回退。由REDDIT_FASTPATH_ENABLED启用(默认开启)。
两者都依赖非官方、未记录的端点(YouTube 的 timedtext API、Reddit 的 .json)——尽力而为,无 SLA;任一都可能因上游更改而中断,因此有终止开关。任何未命中时,请求都会回退到正常层级联(这仍然可以获取 YouTube 页面的标题/描述)。
robots.txt: 两个端点都被站点的 robots.txt 禁止(Reddit 禁止所有内容;YouTube 禁止 /api/,转录文本就在那里)。默认情况下,这些快速路径尊重这一点并保持休眠,回退到级联。在你自己的实例上,你可以选择使用 YOUTUBE_IGNORE_ROBOTS=true / REDDIT_IGNORE_ROBOTS=true 直接抓取。
站点爬取
crawl_site 爬取整个站点,并返回每个找到的页面的 URL/标题/摘要清单。它使用三阶段策略级联:
Firecrawl 爬取 —— 向 Firecrawl 发送爬取作业(
/crawl端点),轮询直到完成,并返回完整页面列表。由FIRECRAWL_CRAWL_POLL_INTERVAL_MS和FIRECRAWL_CRAWL_MAX_WAIT_MS控制。Sitemap 解析 —— 如果 Firecrawl 失败或返回空,则获取
/sitemap.xml(以及链接的 sitemap)并提取带标题/摘要的 URL。使用fast-xml-parser解析 sitemap XML。BFS 爬取(可选)—— 如果 sitemap 解析也失败,则从给定 URL 开始执行广度优先爬取,最多
CRAWL_BFS_MAX_DEPTH次链接跳转。仅在CRAWL_BFS_ENABLED=true或bfs工具参数为true时运行。
爬取期间获取的完整页面内容缓存在 Valkey 中(TTL:CRAWL_MANIFEST_TTL_SECONDS,默认 6 小时)。后续对清单中任何 URL 的 fetch_url 调用都会立即从缓存返回——后续读取零抓取开销。
可以使用 clear_cache(target="crawl") 清除清单缓存。
Wayback Machine 回退
当 WAYBACK_ENABLED=true 时,当所有三个主要层都失败时,第四层查询 Wayback Machine CDX API 以获取存档快照。返回的内容带有来源头([Archived snapshot – <timestamp> – <original_url>]),以便调用者知道内容可能不反映当前页面状态。
抓取质量
任何层返回带有原始 HTML 的内容后,提取后处理会提高标题和正文质量:
JSON-LD 文章提取 —— Schema.org
Article/NewsArticle/BlogPosting/TechArticle块提供比 tier-1 chrome 抓取更干净的headline和articleBody(每个脚本标签大小上限为 1 MB)。标题级联 —— 依次回退到
og:title→twitter:title→<title>(带发布者后缀剥离)→ 第一个<h1>→ URL。Tier-2 Readability 比较 —— 当 Crawl4AI 返回 markdown 时,JSDOM+Readability 也会在其原始 HTML 上运行,并且当其文本更长时(或当 Crawl4AI 返回少于 500 个字符时无条件)优先使用。
韧性
缓存绝不会让搜索挂起。 Valkey 客户端受
CACHE_COMMAND_TIMEOUT_MS/CACHE_CONNECT_TIMEOUT_MS/CACHE_MAX_RETRIES_PER_REQUEST约束(见配置)。当缓存后端停滞或 CPU 飙升时,命令会被拒绝而不是无限期挂起——现有的故障软降级处理会将该拒绝降级为缓存未命中(走实时搜索),而不是抛出异常。缓存连接失败、客户端错误和单条命令错误会输出一条限流的[searxng-mcp]stderr 日志行(按 key 去重,因此持续故障只会留下周期性的面包屑,而不会刷屏)——stderr 是已部署的 PM2 进程上唯一接通的遥测出口。进程崩溃处理——
uncaughtException记录日志后以退出码 1 退出(干净的 PM2 重启);unhandledRejection记录日志后继续运行,而不是让共享进程静默崩溃。优雅降级警告——重排序器回退以及 Ollama/LLM 扩展与摘要回退在静默降低质量时(重排序器不可用、LLM 后端不可达),各自输出一条限流的 stderr 日志行。
版本号单一来源——运行时从
package.json读取(src/version.ts)——McpServer版本、OTel tracer/meter 版本以及出站USER_AGENT都跟随它,因此不会各自漂移。
可观测性(可选启用)
追踪、指标和事件发布完全可选——只要不设置以下任何环境变量,服务器就零可观测性开销,且运行时绝不加载 OpenTelemetry 或 NATS 包。
OpenTelemetry(追踪 + 指标)——设置 OTEL_EXPORTER_OTLP_ENDPOINT 指向你的 collector HTTP 端点后,服务器会输出:
Span(每个请求):
tool.<name>→expand_query? →searxng_request→rerank→fetch(×N)→tier1_firecrawl|tier2_crawl4ai|tier3_rawfetch→post_extract;search_and_summarize另有summarize_llm。计数器:
searxng_search_total{profile, expand}、searxng_fetch_total{tier, outcome}、searxng_cache_total{namespace, outcome}、searxng_errors_total{stage, error_type}。直方图:
searxng_search_duration_seconds{profile}、searxng_fetch_duration_seconds{tier, outcome}。
标准 OTEL 环境变量同样适用(OTEL_SERVICE_NAME 默认为 searxng-mcp)。
NATS 事件——设置 NATS_URL(例如 nats://localhost:4222)后,服务器会在每次搜索、抓取、缓存命中/未命中、robots 跳过和错误时发布一条结构化事件。通过 NATS_CREDS(JWT 凭据文件)或 NATS_USER/NATS_PASSWORD(bcrypt 用户名/密码)进行认证——两者同时设置时凭据文件认证优先。主题:
主题 | 触发时机 |
| 搜索工具被调用 |
| 搜索返回(含来源、延迟、是否应用重排序) |
|
|
| 某一层返回空内容或抛出异常 |
| robots.txt 禁止访问 |
| 抓取完成(含 |
| 每次 Valkey 查询时 |
| 带阶段标签的错误 |
每个信封都包含 request_id,并在启用 OTel 时包含 trace_id,以便订阅者将两条流关联起来。主题前缀可通过 NATS_SUBJECT_PREFIX 覆盖。搜索查询会流经 search.* 事件——下游消费者负责任何 PII 脱敏。
礼貌性
诚实的 User-Agent——出站请求标识为
searxng-mcp/<version> (+https://github.com/TadMSTR/searxng-mcp; personal research)。robots.txt 合规——每个来源的
/robots.txt只抓取一次,并在 Valkey 中以robots:<origin>为 key 缓存 24 小时。禁止的路径在任何层运行之前就被跳过,并记录为skipped_robots url=… reason=…。
传输方式
stdio(默认)——兼容 Claude Code MCP 插件和 LibreChat 的 stdio 配置。
HTTP——设置 SEARXNG_MCP_TRANSPORT=http 以运行共享的 HTTP/SSE 服务器,适用于多客户端部署或基于 Docker 的场景。绑定到 SEARXNG_MCP_HOST:SEARXNG_MCP_PORT(默认 127.0.0.1:3001):
SEARXNG_MCP_TRANSPORT=http SEARXNG_MCP_PORT=3001 npx @tadmstr/searxng-mcp向 Claude Code 注册以对接 HTTP 服务器:
claude mcp add-json searxng --scope user '{
"type": "http",
"url": "http://localhost:3001/mcp"
}'会话以 Mcp-Session-Id 请求头为 key,因此多个客户端可以并发连接到同一个共享进程。空闲会话在 HTTP_SESSION_IDLE_TIMEOUT_MS 之后被清理,并硬性限制为 HTTP_MAX_SESSIONS 个——见配置。
HTTP 传输认证
HTTP 传输默认不认证,这之所以安全,仅仅是因为它默认绑定 127.0.0.1。如果你将 SEARXNG_MCP_HOST 改为其他任何地址——包括容器运行所必需的 0.0.0.0——请同时设置 SEARXNG_MCP_AUTH_TOKEN:
SEARXNG_MCP_AUTH_TOKEN=$(openssl rand -hex 32)设置后,除 GET /health 外的每个请求都必须携带该令牌作为 RFC 6750 bearer 凭据:
Authorization: Bearer <token>任何其他情况——无请求头、使用不同方案、令牌错误——都会收到 401,响应带 WWW-Authenticate: Bearer 和 JSON-RPC 错误体。三种情况下的响应完全相同,且绝不回显所提交的凭据。令牌以 SHA-256 摘要进行比较,因此比较是恒定时间的,不会泄露长度信息。
向 Claude Code 注册已认证的服务器:
claude mcp add-json searxng --scope user '{
"type": "http",
"url": "http://localhost:3001/mcp",
"headers": {"Authorization": "Bearer <token>"}
}'保持该变量未设置则完全保留之前的行为,因此 stdio 用户和现有的回环绑定 HTTP 部署无需任何更改。没有按调用方授权的模型——单个令牌认证的是对服务器的访问,而非特定客户端身份。启动时,非回环绑定且未设置令牌会记录一条警告。
GET /health 有意豁免于该检查。它是容器健康检查和监控存活探针,不接受任何输入,其响应(status、cache、sessions)不携带任何机密。
GET /health——无需认证的存活探针,与 MCP 端点一同绑定在 localhost。通过有界缓存命令超时来 ping Valkey(因此该检查本身绝不会挂起),并返回:
{"status": "ok", "cache": "up", "sessions": 3}或者,当缓存后端不可达时:
{"status": "degraded", "cache": "degraded", "sessions": 3}sessions 是实时的 HTTP 会话数。对系统管理员监控很有用,可以从 MCP 侧检测缓存降级,而无需直接对缓存后端进行插桩。
前置条件
Node.js 20+
pnpm(或 npm)
一个运行中的 SearXNG 实例
一个运行中的 Firecrawl 实例
一个运行中的、暴露 Jina 兼容
/v1/rerank端点的重排序器(可选)一个运行中的 Valkey 或 Redis 兼容实例(可选,用于结果缓存)
一个运行中的 Ollama 实例,已拉取
qwen3:4b和/或qwen3:14b(可选,用于查询扩展和摘要)
SearXNG
SearXNG 必须启用 JSON 输出格式。在 settings.yml 中:
search:
formats:
- html
- json重排序器
重排序器必须暴露 Jina 兼容的 /v1/rerank 端点。轻量级 FlashRank 封装效果很好——参见 homelab-agent 中的 docker/reranker/ 参考实现。
Firecrawl
任何 Firecrawl 兼容实例都可以。本地的 firecrawl-simple 部署就足够了。如果你的实例需要认证,请设置 FIRECRAWL_API_KEY(对于跳过认证的本地部署,默认为 placeholder-local)。
Crawl4AI
Crawl4AI 是可选的第二层抓取回退,在 Firecrawl 返回空内容时使用(被机器人拦截的页面、JS 重站点)。设置 CRAWL4AI_URL 以启用。如果未设置,级联会跳过该层直接进行原始 HTTP 抓取。
docker run -d -p 11235:11235 unclecode/crawl4ai:0.8.6如果你的实例需要 API 令牌认证,请设置 CRAWL4AI_API_TOKEN。
在 search_and_summarize 路径上,Crawl4AI 请求使用 fit_markdown 进行去噪内容提取。其他调用方(search_and_fetch、fetch_url)使用 raw_markdown。
Kiwix(可选)
kiwix-serve 通过 HTTP 提供 ZIM 归档服务。下载所需的 ZIM 文件并以 --nodatealiases(-z)运行 kiwix-serve,这样书名才能保持稳定:
kiwix-serve --port 8292 --nodatealiases /path/to/zims/每个受支持主机所需的 ZIM 文件:
Wikipedia:
wikipedia_en_all_mini(或maxi)Stack Overflow:
stackoverflow.com_en_allArch Wiki:
archlinux_en_all_maxi
ZIM 文件可以从 library.kiwix.org 下载。
Hister(可选)
Hister 是一个由 Firefox 扩展填充的浏览历史索引。设置 HISTER_URL 后,fetchPage 会在调用层级级联之前先查询历史索引——对于抓取器无法处理的登录墙页面和 JS 重页面很有用。
将 HISTER_URL 设置为你的 Hister 实例基础 URL,如果要求 bearer 令牌认证,则设置 HISTER_TOKEN。
Valkey / Redis
任何 Redis 兼容实例均可。推荐使用 Valkey。搜索结果缓存 1 小时;抓取的页面缓存 24 小时。如果不可用,服务器将在无缓存模式下运行。
Ollama
expand 和 search_and_summarize 需要它。拉取所需模型:
ollama pull qwen3:4b # query expansion
ollama pull qwen3:14b # summarizationthink: false 行为会自动处理——无需额外的 Ollama 配置。
配置
所有服务 URL 均可通过环境变量配置。
Variable | Default | Description |
|
| SearXNG 实例 URL |
|
| Firecrawl 实例 URL |
|
| Reranker 实例 URL |
|
| Firecrawl API 密钥(如需要) |
| (unset) | GitHub 个人访问令牌 — 将速率限制从 60 提升至 5,000 次/小时 |
| (unset) | Ollama API 基础 URL — |
| (unset) | 用于已认证 Ollama 代理的 Bearer 令牌 — 设置后添加 |
|
| 查询扩展( |
|
|
|
| (unset) | 用于 |
| (unset) | OpenAI 兼容后端的模型 ID;设置后覆盖 |
| (unset) | OpenAI 兼容后端的 Bearer 令牌 — 设置后添加 |
|
| 发送 |
|
| Redis 兼容 URL — 启用结果缓存。也接受 |
|
| Valkey 命令超时 — 停滞/CPU 飙升的缓存后端会拒绝而非挂起( |
|
| Valkey 连接超时。与 |
|
| 每个 Valkey 命令在拒绝前的最大重试次数。与 |
|
| 搜索结果缓存 TTL(秒) |
|
| 已抓取页面缓存 TTL(秒) |
|
| 爬取清单和页面内容缓存 TTL(秒)(6 小时) |
|
| 未传入 |
|
| 设置为 |
|
| BFS 爬取的最大链接跳数深度 |
|
| 等待 Firecrawl 爬取任务完成时的轮询间隔 |
|
| 在回退到站点地图之前等待 Firecrawl 爬取任务的最长时间 |
|
| 设置为 |
| (unset) | Crawl4AI 实例 URL — 在 Firecrawl 失败时启用第二层抓取回退 |
| (unset) | 可选 Bearer 令牌,用于启用了 API 令牌保护的 Crawl4AI 实例 |
|
| 设置为 |
| (unset) | 用于第二层(Crawl4AI)和第三层(原始 Node fetch)广告拦截的 HTTP 代理 URL — 例如 |
| (unset) | kiwix-serve 基础 URL(例如 |
| (unset) | Hister 浏览历史索引基础 URL — 在层级级联之前为登录墙和 JS 密集型页面启用 Hister 快速路径。未设置时该功能禁用且零开销。 |
| (unset) | 用于 Hister API 认证的 Bearer 令牌。当设置了 |
|
| 在 |
|
| 选择在 YouTube 的 |
|
| 在 |
|
| 选择在 Reddit 的 |
|
| 传输模式: |
|
| HTTP 监听端口(仅 HTTP 传输模式)。 |
|
| HTTP 监听地址(仅 HTTP 传输模式)。 |
| (unset) | 仅 HTTP 传输。设置后,除 |
|
| 仅 HTTP 传输。空闲时间超过此值的会话会被后台清理程序驱逐(有进行中请求的会话除外,因此长时间的 |
|
| 仅 HTTP 传输。硬上限兜底 — 如果会话映射超过此值,无论空闲超时如何,都会驱逐最近最少使用的空闲会话。 |
| (unset) | 用于 bcrypt 用户名/密码认证的 NATS 用户名,与 |
| (unset) | NATS 密码 — 参见 |
安装
npm(推荐)
npm install -g @tadmstr/searxng-mcp或直接使用 npx 运行:
npx @tadmstr/searxng-mcp从源码构建
git clone https://github.com/TadMSTR/searxng-mcp.git
cd searxng-mcp
pnpm install
pnpm build输出:build/src/index.js
MCP 客户端配置
Claude Code(CLI)
推荐使用 claude mcp add-json 注册服务器,以支持完整的环境变量注入:
claude mcp add-json searxng --scope user '{
"command": "npx",
"args": ["-y", "@tadmstr/searxng-mcp"],
"env": {
"SEARXNG_URL": "http://localhost:8081",
"FIRECRAWL_URL": "http://localhost:3002",
"RERANKER_URL": "http://localhost:8787",
"OLLAMA_URL": "http://localhost:11434",
"CACHE_URL": "redis://localhost:6379",
"CACHE_TTL_SECONDS": "3600",
"FETCH_CACHE_TTL_SECONDS": "86400",
"EXPAND_QUERIES": "false",
"CRAWL4AI_URL": "http://localhost:11235"
}
}'这会写入 ~/.claude.json。请勿将 searxng 添加到 ~/.claude/settings.json——该文件不用于 Claude Code 中的 MCP 环境变量注入。
Claude Desktop(claude_desktop_config.json)
{
"mcpServers": {
"searxng": {
"command": "npx",
"args": ["-y", "@tadmstr/searxng-mcp"],
"env": {
"SEARXNG_URL": "http://localhost:8081",
"FIRECRAWL_URL": "http://localhost:3002",
"RERANKER_URL": "http://localhost:8787",
"OLLAMA_URL": "http://localhost:11434",
"CACHE_URL": "redis://localhost:6379",
"CRAWL4AI_URL": "http://localhost:11235"
}
}
}
}LibreChat(librechat.yaml)
mcpServers:
searxng:
type: stdio
command: node
args:
- /path/to/searxng-mcp/build/src/index.js
env:
SEARXNG_URL: http://localhost:8081
FIRECRAWL_URL: http://localhost:3002
RERANKER_URL: http://localhost:8787
OLLAMA_URL: http://localhost:11434
CACHE_URL: redis://localhost:6379
CRAWL4AI_URL: http://localhost:11235GitHub URL
GitHub URL 由原生处理,无需 Firecrawl。githubFetch 根据主机名进行分发:
仓库根目录(
github.com/owner/repo)——通过 GitHub API 获取 README文件 blob(
github.com/owner/repo/blob/branch/path/to/file)——重写为raw.githubusercontent.com并获取原始内容原始文件(
raw.githubusercontent.com/...)——按原样直接获取API(
api.github.com/...)——响应解码(base64content字段)或格式化为 JSON 输出
此前,直接的 raw.githubusercontent.com 和 api.github.com URL 仅匹配 github.com,会落入 HTML 抓取层级级联,而该级联无法渲染原始文本文件或裸 JSON 响应——它们 100% 失败。现在它们走 GitHub 快速路径。
未认证请求的速率限制为 60 次/小时。设置 GITHUB_TOKEN 可将其提升至 5,000 次/小时。
安全
URL 安全(SSRF)
每次对受调用方影响或发现的 URL 的出站获取——原始 HTTP 层、robots.txt / llms.txt / Wayback / sitemap 探测、BFS 爬取链接获取以及 GitHub 快速路径——都受到双重防护:
字符串检查(
assertPublicUrl)——拒绝非 HTTP(S) URL 和私有/内部 IP 字面量:RFC1918(10.x、192.168.x、172.16–31.x)、回环(127.x、::1)、链路本地/云元数据(169.254.x)、CGNAT(100.64/10)、IPv6 ULA(fc00::/7)和链路本地(fe80::/10)、IPv4 映射以及组播/保留范围。连接时 DNS 验证——一个共享的 undici 分发器,其
connect.lookup验证 解析后的 地址(即套接字实际连接的地址)。这弥补了 DNS 重绑定 / TOCTOU 漏洞——即公共主机名解析为私有地址的情况——并且它会在每个重定向跳转时重新运行,因此重定向链无法跳入你的内部网络。
Firecrawl(tier1)和 Crawl4AI(tier2)自行解析并获取目标 URL,因此上述连接时分发器无法覆盖它们。fetchPage 和 crawlSite 在分发给任一服务之前立即调用 assertResolvedPublic(url)——一次性主机名解析,拒绝任何私有/保留结果——从而关闭该路径上常见的 DNS 重绑定情况(TOCTOU 窗口比连接时防护更窄,因为服务会重新解析)。
已配置的内部服务(Firecrawl、Crawl4AI、SearXNG、Ollama、Reranker)通过各自的 URL 访问,有意不加以防护。
重定向保护
原始 HTTP 和 GitHub 快速路径获取额外使用 redirect: "manual" 并直接拒绝 3xx 响应(Location 头永远不会回显给调用方)。跟随重定向的探测(robots.txt、llms.txt、sitemap)由上述连接时 DNS 验证覆盖,该验证会重新检查每一跳。
传输层暴露
stdio 没有网络面。HTTP 传输默认绑定 127.0.0.1,在该配置下无需认证;如果将其移出回环地址而不设置 SEARXNG_MCP_AUTH_TOKEN,则会将所有工具——包括任意 URL 的 fetch_url 和破坏性的 clear_cache——暴露给任何可以路由到该端口的对象。参见 HTTP 传输认证。
依赖审计
CI 在每次推送时运行 pnpm audit。锁文件(pnpm-lock.yaml)已提交,以确保构建可复现、可审计。
凭据处理
服务器不存储或记录任何凭据。API 密钥(FIRECRAWL_API_KEY、GITHUB_TOKEN、CRAWL4AI_API_TOKEN)从环境变量中读取,仅用于对各自服务的出站请求。
输入验证
环境变量在启动时进行验证——RERANK_RECENCY_WEIGHT 在值为 NaN、负数或 >1.0 时发出警告。数值型工具参数使用带范围约束的 z.coerce.number()。
贡献
有关设置说明、提交规范和 PR 流程,请参阅 CONTRIBUTING.md。
集成测试
一个覆盖域数据库并发的真实 Valkey 集成测试套件以 VALKEY_TEST_URL 为门槛,未设置时完全跳过,因此在没有 Valkey 的情况下,普通的 pnpm test 仍然可以运行:
VALKEY_TEST_URL=redis://:<password>@<host>:<port>/<scratch-db> pnpm test使用临时数据库索引——该套件会写入和删除 domain:* 键,并拒绝针对索引 0 或 1 运行,作为安全防护。
许可证
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceMCP server for web search and content extraction using DuckDuckGo or SearXNG, with Playwright-based fetching and LLM-powered data extraction.140MIT
- AlicenseNot gradedqualityAmaintenanceA minimal MCP server that exposes a private SearXNG instance as a search tool over streamable-HTTP, enabling web search from the llama.cpp WebUI or any compatible MCP client.1MIT
- AlicenseNot gradedqualityBmaintenanceOffline-first MCP server for web search and content fetching. It requires no external API keys and uses local models for intent classification, optional cross-lingual search, semantic re-ranking, and direct-answer extraction.ISC
- AlicenseNot gradedqualityCmaintenanceA fully local MCP server that provides web search via self-hosted SearXNG and page-to-markdown conversion (static and JS-rendered), all aggregated behind a single endpoint for use with AI assistants.MIT
Related MCP Connectors
Serper MCP — wraps the Serper Google Search API (serper.dev)
MCP server for Google search results via SERP API
Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/TadMSTR/searxng-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server