searxng-mcp
searxng-mcp
MCP-сервер для приватного веб-поиска через самостоятельно размещённый экземпляр SearXNG. Результаты переранжируются локальной ML-моделью, полное содержимое страниц извлекается через Firecrawl, а опциональный экземпляр Ollama обеспечивает расширение запросов и синтезированные LLM-резюме.
Предназначен для использования с Claude Code и агентами LibreChat, которым нужен веб-поиск без отправки запросов стороннему поисковому API.
Создан с помощью Claude Code с использованием мультиагентного рабочего процесса из homelab-agent — той же платформы, которая использует searxng-mcp в производстве для исследований с помощью ИИ.
Быстрый старт
Требуется работающий экземпляр 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 → raw HTTP). |
|
| Поиск, извлечение лучших результатов, затем синтез резюме с цитатами через Ollama ( |
|
| Извлечение и преобразование читаемого Markdown из любого публичного URL. Хосты GitHub используют быстрый путь GitHub; URL видео YouTube возвращают транскрипт, а URL тем Reddit — пост+комментарии (оба опциональны через robots, см. ниже); все остальные используют каскад извлечения (Firecrawl → Crawl4AI → raw HTTP). Обрезается до токен-бюджета (по умолчанию ~8 000 символов). |
|
| Обход всего сайта и возврат манифеста URL/заголовок/сниппет для каждой страницы. Сначала пытается использовать Firecrawl crawl, затем парсинг sitemap, затем опциональный BFS. Полное содержимое страниц кэшируется в Valkey, поэтому последующие вызовы |
|
| Очистка кэша поиска, кэша извлечения, кэша манифеста обхода или всего. Полезно при исследовании быстро меняющихся тем, где кэшированные результаты могут быть устаревшими. |
|
| Только чтение базы данных возможностей доменов. С |
|
Параметры
category — general (по умолчанию), news, it, science
time_range — day, week, month, year — ограничивает результаты по дате публикации. Опустите для результатов за всё время.
fetch_count — количество лучших переранжированных результатов, для которых извлекается полное содержимое (по умолчанию 1, максимум 3 для search_and_fetch; по умолчанию 3, максимум 5 для search_and_summarize).
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) — приблизительный токен-бюджет для возвращаемого содержимого (символы ≈ токены × 4). Опустите для значения по умолчанию ~2 000 токенов / 8 000 символов; максимум 10 000 токенов.
target_selector (fetch_url) — CSS-селектор для ограничения извлечения конкретным элементом (например, article, main .content). Нативно поддерживается Firecrawl/Crawl4AI и применяется на стороне клиента на уровне raw HTTP; игнорируется на быстрых путях и когда не соответствует ничему.
wait_for_selector (fetch_url) — CSS-селектор, ожидаемый перед извлечением для страниц, отображаемых с помощью JS. Поддерживается уровнями рендеринга (Firecrawl/Crawl4AI); игнорируется на raw 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, raw fetch) | HTTP-прокси — фильтрует домены рекламы по обычному HTTP |
Уровень 1 — Puppeteer adblock
Сервис firecrawl-puppeteer, используемый Firecrawl, работает на пользовательском образе (docker/puppeteer-adblock/), который добавляет @ghostery/adblocker-puppeteer поверх вышестоящего trieve/puppeteer-service-ts. EasyList + EasyPrivacy загружаются при запуске и обновляются каждые 168 часов; блокировщик применяется к каждой странице, создаваемой Firecrawl. Ускоряет извлечение сайтов с большим количеством рекламы и уменьшает размер отображаемого DOM.
Переменные окружения:
Var | По умолчанию | Описание |
| не задано | Установите |
| 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 и raw Node fetch через HTTP-прокси, который фильтрует запросы рекламы и трекеров. HTTPS CONNECT-туннели передаются без изменений — без MITM, поэтому фильтрация применяется только к доменам рекламы по обычному HTTP. Хук puppeteer уровня 1 уже обрабатывает полную фильтрацию HTTPS для этого уровня; прокси покрывает то, что просачивается на уровнях 2 и 3.
См. docker/adblock-proxy/ для определения сервиса, параметров конфигурации и инструкций по развёртыванию (включено в docker-compose.full.yml).
Управляемая данными маршрутизация по уровням
Перед вызовом каскада извлечения searxng-mcp читает tier_stats_30d домена (см. база данных возможностей доменов) и пропускает любой уровень с частотой успеха ниже 30% при как минимум 10 попытках. Домены с холодным стартом (<10 попыток) сохраняют каскад по умолчанию. Каждый пропуск генерирует событие NATS searxng.fetch.tier.skipped с reason: low_success_rate и увеличивает счётчик searxng_fetch_total{outcome=skipped}.
Переопределение оператором. Добавьте карту tier_skip в domains.json, чтобы принудительно пропускать уровни независимо от статистики:
{
"tier_skip": {
"example-bot-blocked.com": ["tier1"],
"another-site.example": ["tier1", "tier2"]
}
}Ключи tier_skip могут быть простыми доменами (example.com соответствует домену и всем поддоменам) или доменом + префиксом пути (example.com/api/). Файл перезагружается на лету — перезапуск не требуется. Ручные переопределения генерируют reason: operator_override.
Быстрый путь по типу содержимого
URL, обслуживающий структурированный не-HTML контент — application/json, любой *+json, XML, YAML, TOML, CSV или text/plain — обнаруживается через HEAD-пробу и направляется напрямую в raw-HTTP уровень вместо полного каскада Firecrawl/Crawl4AI. JSON возвращается в pretty-printed виде внутри fenced code block. Ранее запрос к headless-браузеру отрендерить JSON API-ответ или CDN-ресурс возвращал пустой markdown, поэтому API и CDN-эндпоинты (registry.npmjs.org, api.osv.dev, cdn.jsdelivr.net, …) просто не работали.
Гарантии:
Проба fail-open. Недоступный хост, сервер, отказывающий в
HEAD, или нечитаемый/непарсируемый заголовокContent-Type— всё это без изменений переходит в обычный каскад.application/xhtml+xmlнамеренно исключён — это разметка для браузера, а не структурированные данные.HTML, который сервер ошибочно помечает как
text/plain, всё равно парсится как HTML, а не выгружается как сырой текстовый блок.
База возможностей домена
Каждый fetch записывает то, что searxng-mcp узнаёт о целевом домене, в Valkey под ключом domain:<hostname> (TTL 90 дней, schema_version 5). В каждой записи фиксируется:
tier_stats_30d.{tier1,tier2,tier3,tier4,github}.{attempts, ok, fail, last_fail_reason, window_start_ms}— частота успешных fetch-запросов по каждому уровню за скользящее 30-дневное окно. Отсечка применяется во время чтения, общая для решений о маршрутизации по уровням и для отчётовdomain_stats, поэтому эти два механизма не могут расходиться — домен, который был получен один раз и затем простаивал, сообщает о действительно пустом окне, а не о устаревших числах, доживающих до следующей записи. Слотtier4(Wayback Machine) записывается только приWAYBACK_ENABLED=true. Слотgithubфиксирует быстрый путь GitHub (fetchraw.githubusercontent.com/api.github.com/ README сgithub.com), который обходит каскад уровней, но всё равно отслеживается здесь. Поднятиеschema_versionпересобирает существующие записи с нуля — накопленные окна для currently-idle доменов отбрасываются (прецедент есть во всех подъёмах 1→2, 2→3, 3→4, 4→5).capabilities.metadata_fetch.{attempts, ok, fail, last_fail_reason}— успех/неудача побочного канала fetch метаданных (fetchRawHtmlForMetadata, используется для выборки JSON-LD/og:title). Отслеживается отдельно отtier_stats_30d, поскольку отвечает на вопрос «доступен ли домен вообще», а не «прошла ли доставка полного контента».capabilities.seen_in_search.{count, last_seen_ms}— как часто домен появляется в результатахsearch. Записывается fire-and-forget функциейsearxSearch()на каждом пути возврата (включая попадания в кэш) без выполнения fetch, поэтому домен может отслеживаться до того, как он был получен.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}— есть ли на странице JSON-LD со схемой Article (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 (для одного домена или агрегированно; см. Tools):
pnpm dump-domain docs.anthropic.comdump-domain различает истёкшее окно и уровень, у которого вообще нет данных, а не показывает оба одинаково.
Конкурентные обновления для одного и того же hostname (рекордеры попыток уровня, robots-пробы и пост-извлечения, которые срабатывают параллельно во время одного fetch) сериализуются через серверный Lua compare-and-set в паре с внутрипроцессной очередью на ключ, которая устраняет конкуренцию между собственными писателями одного процесса, так что CAS должен арбитрировать только действительно конкурентные записи между процессами. Версии до v3.17.0 использовали WATCH/MULTI/EXEC read-modify-write против общего соединения, что на самом деле не сериализует конкурентных писателей — данные, собранные до v3.17.0, в результате были существенно неполными. При обновлении существующая статистика уровней отбрасывается через поднятие schema; ожидайте, что domain_stats сразу после обновления будет читаться почти пустым и наполнится в течение следующих дней.
Персистентность domain-db
Domain-db живёт только в Valkey с TTL 90 дней и скользящими 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 работает как несколько конкурентных per-agent stdio-дочерних процессов, каждый из которых запускал бы её). Один ограниченныйSCANпитает оба вывода: долговечный датированный снимок и, когда заданOTEL_EXPORTER_OTLP_ENDPOINT, gauges (searxng_domains_tracked,searxng_domains_failing,searxng_domain_tier_success_ratio{tier}), принудительно сбрасываемые перед выходом.restore-domain-dbповторно заполняет только те ключи, которые отсутствуют или чья живая запись строго старее снимка (сравниваетlast_fetch) — он никогда не затирает более свежую или равную живую запись, поэтому его безопасно запускать против живого, частично заполненного Valkey (например, в последовательности загрузки сервиса для автоматического восстановления после сброса).
Env var | Default | Purpose |
|
| Куда пишутся/читаются датированные снимки. Установите в долговечный путь (appdata или NFS mount) в деплое. |
|
| Сколько снимков хранить; более старые удаляются при каждом запуске обслуживания. |
Быстрый путь llms.txt
Для whitelisted документационных доменов в domains.json (массив llms_txt) fetchPage сначала пробует <origin>/llms-full.txt и извлекает секцию, соответствующую запрошенному URL, перед вызовом любого уровня. Это позволяет избежать запуска puppeteer против хорошо инструментированных сайтов документации и возвращает чистую markdown-секцию напрямую. Результаты проб и полное тело кэшируются в Valkey (llms:<origin>:full, 24 ч / 7 д для present/absent). Стандартный whitelist: docs.anthropic.com, docs.openai.com, docs.stripe.com, docs.crawl4ai.com, docs.firecrawl.dev, docs.cursor.com. Расширяется редактированием domains.json — файл перезагружается на лету.
Быстрый путь Kiwix
Когда задан KIWIX_URL, fetch-запросы для известных офлайн-совместимых хостов перехватываются
до каскада Firecrawl/Crawl4AI и обслуживаются из локального Kiwix
ZIM-архива. Это устраняет 100% отказов tier-1 для таких сайтов, как Wikipedia (которая
блокирует headless-скраперы), и возвращает чистый читаемый контент с нулевым внешним сетевым трафиком.
Поддерживаемые хосты и ZIM-книги (kiwix-serve должен запускаться с --nodatealiases / -z):
Host | ZIM book |
|
|
|
|
|
|
Путь Kiwix выполняется после быстрого пути llms-txt и перед robots-гейтом. Если запрос Kiwix
завершается ошибкой или возвращает пусто, выполняется полный каскад уровней как обычно. Когда KIWIX_URL
не задан, функция добавляет нулевые накладные расходы — isKiwixHost() сразу возвращает false.
Установите KIWIX_URL в базовый URL вашего kiwix-serve (например, http://localhost:8292).
Быстрые пути YouTube и Reddit
fetch_url распознаёт URL видео YouTube (youtube.com, youtu.be) и URL тредов Reddit и может обслуживать их напрямую, вместо скрейпинга отрендеренной страницы:
YouTube — извлекает дорожку субтитров видео со страницы просмотра и возвращает транскрипт. Включается через
YOUTUBE_TRANSCRIPT_ENABLED(по умолчанию включено).Reddit — получает публичное представление
.jsonи возвращает пост плюс топ-комментарии в стандартной форме{title, url, text}; при HTTP 429 переходит к следующему шагу. Включается черезREDDIT_FASTPATH_ENABLED(по умолчанию включено).
Оба полагаются на неофициальные, недокументированные эндпоинты (timedtext API YouTube, .json Reddit) — best-effort без SLA; любой из них может сломаться при изменении на стороне апстрима, отсюда kill-переключатели. При любом промахе запрос переходит к обычному каскаду уровней (который всё ещё может получить заголовок/описание страницы YouTube).
robots.txt: оба эндпоинта запрещены robots.txt сайтов (Reddit запрещает всё; YouTube запрещает /api/, где живёт транскрипт). По умолчанию эти быстрые пути уважают это и остаются спящими, переходя к каскаду. На собственном экземпляре вы можете включить прямой fetch с помощью YOUTUBE_IGNORE_ROBOTS=true / REDDIT_IGNORE_ROBOTS=true.
Сканирование сайтов
crawl_site сканирует весь сайт и возвращает манифест URL/заголовок/сниппет для каждой найденной страницы. Используется трёхфазный каскад стратегий:
Firecrawl crawl — отправляет задание на сканирование в Firecrawl (эндпоинт
/crawl), опрашивает до завершения и возвращает полный список страниц. УправляетсяFIRECRAWL_CRAWL_POLL_INTERVAL_MSиFIRECRAWL_CRAWL_MAX_WAIT_MS.Разбор sitemap — если Firecrawl не сработал или вернул пусто, получает
/sitemap.xml(и связанные sitemap) и извлекает URL с заголовками/сниппетами. Используетfast-xml-parserдля разбора XML sitemap.BFS crawl (opt-in) — если разбор sitemap также не сработал, выполняет обход в ширину, начиная с заданного URL, до
CRAWL_BFS_MAX_DEPTHпереходов по ссылкам. Запускается только приCRAWL_BFS_ENABLED=trueили параметре инструментаbfsравномtrue.
Полное содержимое страниц, полученное во время сканирования, кэшируется в Valkey (TTL: CRAWL_MANIFEST_TTL_SECONDS, по умолчанию 6 часов). Последующие вызовы fetch_url для любого URL из манифеста возвращаются немедленно из кэша — нулевые накладные расходы на fetch для повторных чтений.
Кэш манифеста можно очистить с помощью clear_cache(target="crawl").
Запасной вариант Wayback Machine
Когда WAYBACK_ENABLED=true, четвёртый уровень запрашивает CDX API Wayback Machine для архивного снимка, когда все три основных уровня не сработали. Возвращённый контент снабжается префиксом происхождения ([Archived snapshot – <timestamp> – <original_url>]), чтобы вызывающие знали, что контент может не отражать текущее состояние страницы.
Качество fetch
После того как любой уровень возвращает контент с сырым HTML, пост-извлечение улучшает качество заголовка и тела:
Извлечение JSON-LD Article — блоки Schema.org
Article/NewsArticle/BlogPosting/TechArticleдают более чистыеheadlineиarticleBody, чем скрейпинг chrome tier-1 (ограничение размера 1 МБ на тег script).Каскад заголовков — перебор через
og:title→twitter:title→<title>(с удалением суффикса издателя) → первый<h1>→ URL.Сравнение Readability tier-2 — когда Crawl4AI возвращает markdown, JSDOM+Readability также запускается поверх его сырого HTML и предпочитается, когда его текст длиннее (или безусловно, когда Crawl4AI возвращает менее 500 символов).
Устойчивость
Кэш никогда не зависает в поиске. Клиент Valkey ограничен
CACHE_COMMAND_TIMEOUT_MS/CACHE_CONNECT_TIMEOUT_MS/CACHE_MAX_RETRIES_PER_REQUEST(см. Конфигурация). Зависший или перегруженный по CPU кэш-бэкенд теперь отклоняет команду вместо бесконечного зависания — существующая обработка отказов (fail-soft) превращает это отклонение в промах кэша (обслуживание из живого источника), а не в исключение. Ошибки подключения к кэшу, ошибки клиента и ошибки отдельных команд выводят троттлированную строку в stderr[searxng-mcp](дедуплицированную по ключу, так что длительный сбой оставляет периодический «хлебный крошку», а не поток сообщений) — stderr является единственным каналом телеметрии, подключённым к развёрнутому процессу PM2.Обработчики аварийных завершений процесса —
uncaughtExceptionлогирует и завершает процесс с кодом 1 (чистый перезапуск PM2);unhandledRejectionлогирует и продолжает работу, не роняя общий процесс молча.Предупреждения о плавной деградации — фолбэк реранкера и фолбэки Ollama/LLM для расширения и суммаризации выводят по одной троттлированной строке в stderr, когда они незаметно снижают качество (реранкер недоступен, LLM-бэкенд недоступен).
Версия имеет единый источник — берётся из
package.jsonво время выполнения (src/version.ts) — версияMcpServer, версия OTel-трейсера/метрика и исходящийUSER_AGENTотслеживают её, поэтому они не могут расходиться независимо.
Наблюдаемость (опционально)
Трейсинг, метрики и публикация событий полностью опциональны — если ни одна из переменных окружения ниже не задана, сервер не имеет никаких накладных расходов на наблюдаемость и не загружает пакеты OpenTelemetry или NATS во время выполнения.
OpenTelemetry (трейсы + метрики) — задайте OTEL_EXPORTER_OTLP_ENDPOINT на HTTP-эндпоинт вашего коллектора, и сервер будет отправлять:
Спаны (на запрос):
tool.<name>→expand_query? →searxng_request→rerank→fetch(×N) →tier1_firecrawl|tier2_crawl4ai|tier3_rawfetch→post_extract; плюсsummarize_llmдляsearch_and_summarize.Счётчики:
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.txt и ошибке. Аутентификация через NATS_CREDS (файл учётных данных JWT) или NATS_USER/NATS_PASSWORD (имя пользователя/пароль bcrypt) — приоритет у файла учётных данных, если заданы оба. Темы:
Тема | Когда |
| Вызван инструмент поиска |
| Поиск вернул результат (с источниками, задержкой, применённым реранком) |
| Вызван |
| Уровень вернул пустой результат или выбросил исключение |
| robots.txt запрещает |
| Выборка завершена (с |
| При каждом обращении к Valkey |
| Ошибки с тегом этапа |
Каждый конверт включает request_id и (когда OTel включён) trace_id, чтобы подписчики могли объединять два потока. Префикс темы можно переопределить через NATS_SUBJECT_PREFIX. Поисковые запросы проходят через события search.* — ответственность за очистку PII лежит на downstream-потребителях.
Вежливость
Честный User-Agent — исходящие запросы идентифицируются как
searxng-mcp/<version> (+https://github.com/TadMSTR/searxng-mcp; personal research).Соблюдение robots.txt —
/robots.txtзагружается один раз для каждого источника и кэшируется на 24 часа в Valkey под ключомrobots:<origin>. Запрещённые пути пропускаются до запуска любого уровня и логируются какskipped_robots url=… reason=….
Транспорт
stdio (по умолчанию) — совместим с MCP-плагином Claude Code и конфигурацией stdio в LibreChat.
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, поэтому несколько клиентов могут одновременно подключаться к одному общему процессу. Простаивающие сессии удаляются после 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, должен нести токен как bearer-учётные данные согласно RFC 6750:
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-развёртываниям, привязанным к loopback, не требуется никаких изменений. Модель авторизации по вызывающему отсутствует — один токен аутентифицирует доступ к серверу, а не конкретную личность клиента. При запуске привязка не к loopback без токена выводит предупреждение.
GET /health намеренно освобождён от проверки. Это healthcheck контейнера и liveness-проба мониторинга, он не принимает входных данных, а его ответ (status, cache, sessions) не содержит секретов.
GET /health — неаутентифицированная liveness-проба, привязанная к localhost вместе с MCP-эндпоинтом. Пингует 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 хорошо подходит — см. ссылку docker/reranker/ в homelab-agent.
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 обслуживает ZIM-архивы по HTTP. Скачайте
необходимые ZIM-файлы и запустите kiwix-serve с --nodatealiases (-z), чтобы названия книг были
стабильными:
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 на базовый URL вашего экземпляра Hister и HISTER_TOKEN, если требуется аутентификация по bearer-токену.
Valkey / Redis
Любой Redis-совместимый экземпляр. Рекомендуется Valkey. Результаты поиска кэшируются на 1 час; загруженные страницы — на 24 часа. Если недоступен, сервер работает без кэширования.
Ollama
Требуется для expand и search_and_summarize. Загрузите необходимые модели:
ollama pull qwen3:4b # query expansion
ollama pull qwen3:14b # summarizationПоведение think: false обрабатывается автоматически — дополнительная настройка Ollama не требуется.
Конфигурация
Все URL-адреса сервисов настраиваются через переменные окружения.
Variable | Default | Description |
|
| URL экземпляра SearXNG |
|
| URL экземпляра Firecrawl |
|
| URL экземпляра Reranker |
|
| API-ключ Firecrawl (если требуется) |
| (unset) | Персональный токен доступа GitHub — увеличивает лимит запросов с 60 до 5 000 запросов/час |
| (unset) | Базовый URL API Ollama — требуется для |
| (unset) | Bearer-токен для аутентифицированных прокси Ollama — добавляет заголовок |
|
| Модель, используемая для расширения запроса (параметр |
|
| Модель, используемая |
| (unset) | OpenAI-совместимая конечная точка чата (например, vLLM, llama.cpp, LM Studio) для |
| (unset) | Идентификатор модели для OpenAI-совместимого бэкенда; переопределяет |
| (unset) | Bearer-токен для OpenAI-совместимого бэкенда — добавляет |
|
| Отправляет |
|
| URL, совместимый с Redis — включает кэширование результатов. Также принимает |
|
| Таймаут команды Valkey — зависший/перегруженный кэш-бэкенд отклоняет запрос вместо зависания ( |
|
| Таймаут подключения Valkey. Такое же поведение возврата к значению по умолчанию, как у |
|
| Максимальное количество повторных попыток на команду Valkey перед отклонением. Такое же поведение возврата к значению по умолчанию, как у |
|
| TTL кэша результатов поиска в секундах |
|
| TTL кэша загруженных страниц в секундах |
|
| TTL кэша манифеста обхода и содержимого страниц в секундах (6 часов) |
|
| Максимальное количество страниц по умолчанию, возвращаемое |
|
| Установите |
|
| Максимальная глубина переходов по ссылкам для BFS-обхода |
|
| Интервал опроса при ожидании завершения задания обхода Firecrawl |
|
| Максимальное время ожидания задания обхода Firecrawl перед возвратом к sitemap |
|
| Установите |
| (unset) | URL экземпляра Crawl4AI — включает резервный вариант загрузки второго уровня при сбое Firecrawl |
| (unset) | Необязательный Bearer-токен для экземпляров Crawl4AI с защитой API-токеном |
|
| Установите |
| (unset) | URL HTTP-прокси для блокировки рекламы на втором (Crawl4AI) и третьем (сырой Node fetch) уровнях — например, |
| (unset) | Базовый URL kiwix-serve (например, |
| (unset) | Базовый URL индекса истории просмотров Hister — включает быстрый путь Hister перед каскадом уровней для страниц за логином и страниц с большим количеством JS. Функция отключена и не создаёт накладных расходов, когда не установлена. |
| (unset) | Bearer-токен для аутентификации API Hister. Требуется, когда |
|
| Включает быстрый путь получения транскриптов YouTube в |
|
| Разрешает загрузку транскриптов YouTube, несмотря на запрет |
|
| Включает быстрый путь Reddit |
|
| Разрешает загрузку Reddit |
|
| Режим транспорта: |
|
| HTTP-порт прослушивания (только для режима HTTP-транспорта). |
|
| HTTP-адрес прослушивания (только для режима HTTP-транспорта). |
| (unset) | Только для HTTP-транспорта. При установке каждый запрос, кроме |
|
| Только для HTTP-транспорта. Сессия, простаивающая дольше этого времени, удаляется фоновой очисткой (сессии с выполняющимся запросом освобождаются, поэтому длинный вызов |
|
| Только для HTTP-транспорта. Жёсткий предел — если карта сессий когда-либо превысит это значение, наименее недавно использованная простаивающая сессия удаляется независимо от таймаута простоя. |
| (unset) | Имя пользователя NATS для аутентификации по имени пользователя/паролю bcrypt, используется вместе с |
| (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 — этот файл не используется для внедрения переменных окружения MCP в Claude Code.
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:11235URL-адреса GitHub
URL-адреса GitHub обрабатываются нативно без Firecrawl. githubFetch распределяет по имени хоста:
Корень репозитория (
github.com/owner/repo) — получает README через GitHub APIФайл blob (
github.com/owner/repo/blob/branch/path/to/file) — переписывает и получает сырое содержимое сraw.githubusercontent.comСырой файл (
raw.githubusercontent.com/...) — получается напрямую как естьAPI (
api.github.com/...) — ответ декодируется (поляcontentв base64) или выводится в виде JSON с форматированием
Прямые URL-адреса raw.githubusercontent.com и api.github.com ранее соответствовали только github.com и попадали в каскад уровней HTML-скрапинга, который не может отобразить сырой текстовый файл или чистый JSON-ответ — они не работали в 100% случаев. Теперь они используют быстрый путь GitHub.
Неаутентифицированные запросы ограничены 60 запросами в час. Установите GITHUB_TOKEN, чтобы увеличить это значение до 5 000 в час.
Безопасность
Безопасность URL (SSRF)
Каждый исходящий запрос к URL-адресу, на который влияет вызывающий код или который был обнаружен — уровень raw-HTTP, проверки robots.txt / llms.txt / Wayback / sitemap, выборка ссылок при BFS-обходе и быстрый путь GitHub — защищён двумя способами:
Проверка строки (
assertPublicUrl) — отклоняет URL-адреса, не являющиеся HTTP(S), и частные/внутренние IP-литералы: RFC1918 (10.x,192.168.x,172.16–31.x), loopback (127.x,::1), link-local / метаданные облака (169.254.x), CGNAT (100.64/10), IPv6 ULA (fc00::/7) и link-local (fe80::/10), IPv4-mapped, а также multicast/зарезервированные диапазоны.Проверка DNS во время подключения — общий диспетчер undici, чей
connect.lookupпроверяет разрешённый адрес (именно тот, к которому подключается сокет). Это закрывает разрыв DNS-rebinding / TOCTOU, когда публичное имя хоста разрешается в частный адрес, и повторяется на каждом переходе редиректа, поэтому цепочка редиректов не может попасть в вашу внутреннюю сеть.
Firecrawl (tier1) и Crawl4AI (tier2) сами разрешают и получают целевой URL, поэтому вышеупомянутый диспетчер времени подключения не может их покрыть. fetchPage и crawlSite вызывают assertResolvedPublic(url) — однократное разрешение имени хоста, отклоняющее любой частный/зарезервированный результат — непосредственно перед отправкой в любой из сервисов, закрывая распространённый случай DNS-rebinding на этом пути (более узкое окно TOCTOU, чем защита времени подключения, поскольку сервис повторно разрешает адрес).
Настроенные внутренние сервисы (Firecrawl, Crawl4AI, SearXNG, Ollama, Reranker) доступны по своим собственным URL-адресам и намеренно не защищены.
Защита от редиректов
Запросы raw-HTTP и быстрого пути GitHub дополнительно используют redirect: "manual" и полностью отклоняют ответы 3xx (заголовок Location никогда не возвращается вызывающему коду). Проверки, следующие за редиректами (robots.txt, llms.txt, sitemap), покрываются вышеупомянутой проверкой DNS во время подключения, которая повторно проверяет каждый переход.
Сетевая экспозиция транспорта
stdio не имеет сетевой поверхности. HTTP-транспорт по умолчанию привязывается к 127.0.0.1 и в такой конфигурации не требует аутентификации; перемещение его за пределы loopback без установки SEARXNG_MCP_AUTH_TOKEN открывает каждый инструмент — включая fetch_url с произвольным URL и разрушительный clear_cache — для всего, что может маршрутизировать к порту. См. Аутентификация HTTP-транспорта.
Аудит зависимостей
CI запускает pnpm audit при каждом push. Файл блокировки (pnpm-lock.yaml) фиксируется для воспроизводимых и проверяемых сборок.
Обработка учётных данных
Сервер не хранит и не регистрирует учётные данные. Ключи API (FIRECRAWL_API_KEY, GITHUB_TOKEN, CRAWL4AI_API_TOKEN) считываются из переменных окружения и используются только в исходящих запросах к соответствующим сервисам.
Проверка входных данных
Переменные окружения проверяются при запуске — RERANK_RECENCY_WEIGHT предупреждает о значениях NaN, отрицательных или >1.0. Числовые параметры инструментов используют z.coerce.number() с ограничениями диапазона.
Внесение вклада
См. CONTRIBUTING.md для инструкций по настройке, соглашений о коммитах и процесса PR.
Интеграционные тесты
Набор интеграционных тестов с реальным Valkey, покрывающий конкурентность домен-БД, активируется через VALKEY_TEST_URL и полностью пропускается, если он не установлен, поэтому обычный pnpm test по-прежнему работает без Valkey:
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