Skip to main content
Glama

searxng-mcp

Built with Claude Code CI License: MIT npm

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

Инструменты

Инструмент

Описание

Ключевые параметры

search

Поиск через SearXNG с локальным переранжированием. Извлекает более широкий пул результатов, переранжирует по релевантности, возвращает топ-N. Собственные прямые ответы SearXNG, инфобоксы, исправления орфографии и связанные предложения отображаются над списком и в structuredContent.

query, num_results (1–20), category, time_range, domain_profile, expand, language, engines, site

search_and_fetch

Поиск, переранжирование, затем извлечение полного содержимого лучших результатов с помощью каскада извлечения (Firecrawl → Crawl4AI → raw HTTP).

query, category, time_range, fetch_count (1–3), domain_profile, expand, language, engines, site

search_and_summarize

Поиск, извлечение лучших результатов, затем синтез резюме с цитатами через Ollama (OLLAMA_SUMMARIZE_MODEL). Если Ollama недоступен, возвращается необработанное извлечённое содержимое.

query, fetch_count (1–5), category, time_range, domain_profile, expand, language, engines, site

fetch_url

Извлечение и преобразование читаемого Markdown из любого публичного URL. Хосты GitHub используют быстрый путь GitHub; URL видео YouTube возвращают транскрипт, а URL тем Reddit — пост+комментарии (оба опциональны через robots, см. ниже); все остальные используют каскад извлечения (Firecrawl → Crawl4AI → raw HTTP). Обрезается до токен-бюджета (по умолчанию ~8 000 символов).

url, domain_profile, max_tokens, target_selector, wait_for_selector

crawl_site

Обход всего сайта и возврат манифеста URL/заголовок/сниппет для каждой страницы. Сначала пытается использовать Firecrawl crawl, затем парсинг sitemap, затем опциональный BFS. Полное содержимое страниц кэшируется в Valkey, поэтому последующие вызовы fetch_url не требуют затрат.

url, max_pages (по умолчанию: CRAWL_MAX_PAGES_DEFAULT), bfs (bool, опциональный BFS)

clear_cache

Очистка кэша поиска, кэша извлечения, кэша манифеста обхода или всего. Полезно при исследовании быстро меняющихся тем, где кэшированные результаты могут быть устаревшими.

target (search, fetch, crawl, all)

domain_stats

Только чтение базы данных возможностей доменов. С hostname: показатели успешности по уровням и флаги возможностей для одного домена. Без: агрегированные данные по всем отслеживаемым доменам (успешность по уровням, худшие неудачные домены, количество просмотренных, но не извлечённых). Возвращает структурированный вывод MCP (structuredContent) для программного порогового анализа.

hostname (опционально)

Параметры

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:#000000

SearXNG и Firecrawl обязательны. Crawl4AI, Valkey, Ollama, Kiwix и переранжировщик опциональны — сервер корректно деградирует, когда любой из них недоступен.

Блокировка рекламы

searxng-mcp использует два независимых сайдкара для блокировки рекламы, по одному на группу уровней извлечения:

Сайдкар

Уровень

Механизм

docker/puppeteer-adblock/

Уровень 1 (Firecrawl)

Перехват на уровне CDP — полная фильтрация HTTPS, тот же процесс браузера

docker/adblock-proxy/

Уровни 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

По умолчанию

Описание

ADBLOCK_DISABLE

не задано

Установите true, чтобы полностью пропустить загрузку фильтров.

ADBLOCK_FILTERS_URL

EasyList + EasyPrivacy

Список URL-адресов списков фильтров, разделённых запятыми.

ADBLOCK_REFRESH_HOURS

168

Периодичность, с которой блокировщик перестраивается из указанных 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 (fetch raw.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.txt

  • capabilities.json_ld_article.{sampled, present, last_sampled_at} — есть ли на странице JSON-LD со схемой Article (Schema.org Article/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.com

dump-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 flush
  • domain-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

DOMAIN_DB_SNAPSHOT_DIR

./domain-db-snapshots

Куда пишутся/читаются датированные снимки. Установите в долговечный путь (appdata или NFS mount) в деплое.

DOMAIN_DB_SNAPSHOT_RETENTION

14

Сколько снимков хранить; более старые удаляются при каждом запуске обслуживания.

Быстрый путь 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

en.wikipedia.org, wikipedia.org

wikipedia_en_all_mini

stackoverflow.com

stackoverflow.com_en_all

wiki.archlinux.org

archlinux_en_all_maxi

Путь 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/заголовок/сниппет для каждой найденной страницы. Используется трёхфазный каскад стратегий:

  1. Firecrawl crawl — отправляет задание на сканирование в Firecrawl (эндпоинт /crawl), опрашивает до завершения и возвращает полный список страниц. Управляется FIRECRAWL_CRAWL_POLL_INTERVAL_MS и FIRECRAWL_CRAWL_MAX_WAIT_MS.

  2. Разбор sitemap — если Firecrawl не сработал или вернул пусто, получает /sitemap.xml (и связанные sitemap) и извлекает URL с заголовками/сниппетами. Использует fast-xml-parser для разбора XML sitemap.

  3. 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) — приоритет у файла учётных данных, если заданы оба. Темы:

Тема

Когда

searxng.search.requested

Вызван инструмент поиска

searxng.search.completed

Поиск вернул результат (с источниками, задержкой, применённым реранком)

searxng.fetch.requested

Вызван fetchPage

searxng.fetch.tier.miss

Уровень вернул пустой результат или выбросил исключение

searxng.fetch.tier.skipped

robots.txt запрещает

searxng.fetch.completed

Выборка завершена (с tier_served, text_len, задержкой)

searxng.cache.hit / .miss

При каждом обращении к Valkey

searxng.error

Ошибки с тегом этапа

Каждый конверт включает 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_all

  • Arch 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

SEARXNG_URL

http://localhost:8081

URL экземпляра SearXNG

FIRECRAWL_URL

http://localhost:3002

URL экземпляра Firecrawl

RERANKER_URL

http://localhost:8787

URL экземпляра Reranker

FIRECRAWL_API_KEY

placeholder-local

API-ключ Firecrawl (если требуется)

GITHUB_TOKEN

(unset)

Персональный токен доступа GitHub — увеличивает лимит запросов с 60 до 5 000 запросов/час

OLLAMA_URL

(unset)

Базовый URL API Ollama — требуется для expand и search_and_summarize

OLLAMA_API_KEY

(unset)

Bearer-токен для аутентифицированных прокси Ollama — добавляет заголовок Authorization: Bearer <key> при установке

OLLAMA_EXPAND_MODEL

qwen3:4b

Модель, используемая для расширения запроса (параметр expand). Можно переопределить без пересборки.

OLLAMA_SUMMARIZE_MODEL

qwen3:14b

Модель, используемая search_and_summarize. Можно переопределить без пересборки.

LLM_BASE_URL

(unset)

OpenAI-совместимая конечная точка чата (например, vLLM, llama.cpp, LM Studio) для expand + search_and_summarize. Должна включать путь API — например, http://host:8000/v1 — сервер добавляет /chat/completions. При установке имеет приоритет над OLLAMA_URL, поэтому уже загруженную модель можно переиспользовать вместо запуска отдельной модели Ollama.

LLM_MODEL

(unset)

Идентификатор модели для OpenAI-совместимого бэкенда; переопределяет OLLAMA_EXPAND_MODEL / OLLAMA_SUMMARIZE_MODEL при установке.

LLM_API_KEY

(unset)

Bearer-токен для OpenAI-совместимого бэкенда — добавляет Authorization: Bearer <key> при установке.

LLM_DISABLE_THINKING

true

Отправляет chat_template_kwargs.enable_thinking: false, чтобы модели рассуждений (например, Qwen3) возвращали прямой вывод. Установите false для серверов, которые отклоняют это поле.

CACHE_URL

redis://localhost:6381

URL, совместимый с Redis — включает кэширование результатов. Также принимает VALKEY_URL или REDIS_URL в качестве псевдонимов. Работает с Redis, Valkey и Dragonfly. Сервер корректно деградирует при недоступности.

CACHE_COMMAND_TIMEOUT_MS

2500

Таймаут команды Valkey — зависший/перегруженный кэш-бэкенд отклоняет запрос вместо зависания (cacheGet() — первый await в каждом поиске). Недопустимые/неположительные значения возвращаются к значению по умолчанию, а не становятся NaN, который отключил бы таймаут.

CACHE_CONNECT_TIMEOUT_MS

3000

Таймаут подключения Valkey. Такое же поведение возврата к значению по умолчанию, как у CACHE_COMMAND_TIMEOUT_MS.

CACHE_MAX_RETRIES_PER_REQUEST

2

Максимальное количество повторных попыток на команду Valkey перед отклонением. Такое же поведение возврата к значению по умолчанию, как у CACHE_COMMAND_TIMEOUT_MS.

CACHE_TTL_SECONDS

3600

TTL кэша результатов поиска в секундах

FETCH_CACHE_TTL_SECONDS

86400

TTL кэша загруженных страниц в секундах

CRAWL_MANIFEST_TTL_SECONDS

21600

TTL кэша манифеста обхода и содержимого страниц в секундах (6 часов)

CRAWL_MAX_PAGES_DEFAULT

20

Максимальное количество страниц по умолчанию, возвращаемое crawl_site, когда max_pages не передан

CRAWL_BFS_ENABLED

false

Установите true, чтобы глобально включить резервный BFS в crawl_site. Также можно включить для отдельного вызова параметром bfs.

CRAWL_BFS_MAX_DEPTH

3

Максимальная глубина переходов по ссылкам для BFS-обхода

FIRECRAWL_CRAWL_POLL_INTERVAL_MS

2000

Интервал опроса при ожидании завершения задания обхода Firecrawl

FIRECRAWL_CRAWL_MAX_WAIT_MS

120000

Максимальное время ожидания задания обхода Firecrawl перед возвратом к sitemap

EXPAND_QUERIES

false

Установите true, чтобы глобально включить расширение запросов

CRAWL4AI_URL

(unset)

URL экземпляра Crawl4AI — включает резервный вариант загрузки второго уровня при сбое Firecrawl

CRAWL4AI_API_TOKEN

(unset)

Необязательный Bearer-токен для экземпляров Crawl4AI с защитой API-токеном

WAYBACK_ENABLED

false

Установите true, чтобы включить резервный вариант четвёртого уровня Wayback Machine — загружает архивные снимки, когда все три уровня не сработали

ADBLOCK_PROXY_URL

(unset)

URL HTTP-прокси для блокировки рекламы на втором (Crawl4AI) и третьем (сырой Node fetch) уровнях — например, http://adblock-proxy:8118. См. docker/adblock-proxy/.

KIWIX_URL

(unset)

Базовый URL kiwix-serve (например, http://localhost:8292) — включает быстрый путь Kiwix для Wikipedia, Stack Overflow и Arch Wiki. Функция отключена и не создаёт накладных расходов, когда не установлена.

HISTER_URL

(unset)

Базовый URL индекса истории просмотров Hister — включает быстрый путь Hister перед каскадом уровней для страниц за логином и страниц с большим количеством JS. Функция отключена и не создаёт накладных расходов, когда не установлена.

HISTER_TOKEN

(unset)

Bearer-токен для аутентификации API Hister. Требуется, когда HISTER_URL установлен и в экземпляре включена аутентификация по токену.

YOUTUBE_TRANSCRIPT_ENABLED

true

Включает быстрый путь получения транскриптов YouTube в fetch_url. Установите false, чтобы отключить (например, если неофициальная конечная точка timedtext сломается на стороне YouTube).

YOUTUBE_IGNORE_ROBOTS

false

Разрешает загрузку транскриптов YouTube, несмотря на запрет /api/ в robots.txt YouTube. По умолчанию уважает robots (быстрый путь остаётся неактивным, переходит к каскаду).

REDDIT_FASTPATH_ENABLED

true

Включает быстрый путь Reddit .json в fetch_url. Установите false, чтобы отключить.

REDDIT_IGNORE_ROBOTS

false

Разрешает загрузку Reddit .json, несмотря на robots.txt Reddit (Disallow: /). По умолчанию уважает robots (быстрый путь остаётся неактивным, переходит к каскаду).

SEARXNG_MCP_TRANSPORT

stdio

Режим транспорта: stdio (по умолчанию, один клиент) или http (общий HTTP/SSE-сервер).

SEARXNG_MCP_PORT

3001

HTTP-порт прослушивания (только для режима HTTP-транспорта).

SEARXNG_MCP_HOST

127.0.0.1

HTTP-адрес прослушивания (только для режима HTTP-транспорта).

SEARXNG_MCP_AUTH_TOKEN

(unset)

Только для HTTP-транспорта. При установке каждый запрос, кроме GET /health, должен отправлять Authorization: Bearer <token> или получит 401. Неустановленное значение (по умолчанию) полностью отключает проверку. Устанавливайте это, когда SEARXNG_MCP_HOST не является loopback — см. Аутентификация HTTP-транспорта.

HTTP_SESSION_IDLE_TIMEOUT_MS

600000

Только для HTTP-транспорта. Сессия, простаивающая дольше этого времени, удаляется фоновой очисткой (сессии с выполняющимся запросом освобождаются, поэтому длинный вызов crawl_site никогда не закрывается посреди запроса). Ограничивает рост карты сессий от клиентов, убитых в середине хода, которые никогда не вызывают transport.onclose.

HTTP_MAX_SESSIONS

256

Только для HTTP-транспорта. Жёсткий предел — если карта сессий когда-либо превысит это значение, наименее недавно использованная простаивающая сессия удаляется независимо от таймаута простоя.

NATS_USER

(unset)

Имя пользователя NATS для аутентификации по имени пользователя/паролю bcrypt, используется вместе с NATS_PASSWORD. Игнорируется, если также установлен NATS_CREDS (аутентификация JWT через файл учётных данных имеет приоритет).

NATS_PASSWORD

(unset)

Пароль NATS — см. NATS_USER.

Установка

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:11235

URL-адреса 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 — защищён двумя способами:

  1. Проверка строки (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/зарезервированные диапазоны.

  2. Проверка 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

Available Tools

7 tools
clear_cacheA

Purge the search and/or fetch result cache. Useful when researching fast-moving topics where cached results from the past hour may be stale.

ParametersJSON Schema
NameRequiredDescriptionDefault
targetNoWhich cache to clear: search results, fetched pages, crawl manifests, or all (default all)all

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description correctly indicates a destructive action ('purge') and specifies the cache targets. However, it lacks details on rate limits, auth requirements, or side effects of clearing different caches.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, both relevant. No fluff. Slightly more structured formatting (e.g., bullet points) could improve, but it's already efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (1 param, no output schema), the description adequately covers purpose and typical scenario. Could mention that clearing cache is immediate or note persistence, but not necessary.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the parameter 'target' is fully described in the schema with enum and default. The description adds no additional meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states the tool purges search and/or fetch result cache, distinguishing it from sibling tools that perform searches, fetches, or crawls. The verb 'purge' and specific resources are explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides a specific use case: 'researching fast-moving topics where cached results from the past hour may be stale.' Does not explicitly mention when not to use, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

crawl_siteA

Crawl a site and return a manifest of pages with titles and snippets. Full page content is cached — call fetch_url on any page URL for the full text. Strategy: Firecrawl (JS rendering) → sitemap-first → BFS (if enabled).

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesBase URL to crawl
max_pagesNoMaximum pages to crawl (default 20, max 100)
exclude_pathNoExclude URLs matching this path prefix (e.g. '/blog')
include_pathNoOnly include URLs matching this path prefix (e.g. '/docs')
same_domain_onlyNoRestrict crawl to the same domain (default true)

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided; description carries full burden. Discloses caching behavior and crawl strategy (Firecrawl, sitemap-first, BFS). Could mention rate limits or error handling, but sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with core purpose, each sentence adds value. No redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers caching, strategy, and relationship to fetch_url. Lacks detailed return structure of manifest, but adequate for a tool with no output schema and good parameter descriptions.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, baseline 3. Description adds no extra meaning beyond parameter names and default values in schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states 'Crawl a site and return a manifest of pages' (specific verb+resource+output). Distinguishes from sibling fetch_url by noting it caches full page content for later retrieval.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implied guidance: use fetch_url after crawling for full text. No explicit when-to-use or when-not-to-use compared to search or other siblings. Lacks exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

domain_statsDomain capability statsA

Read the searxng-mcp domain capability database — what it has learned about hosts from fetches: per-tier success rates (tier1-3 cascade, tier4 wayback, github fast path), llms.txt/robots presence, metadata reachability, and search appearances. Provide hostname for one domain's record, or omit it for an aggregate across all tracked domains (per-tier success rates, worst failing domains, seen-but-never-fetched count). Read-only. Aggregate mode reports truncated: true if the internal scan cap is hit.

ParametersJSON Schema
NameRequiredDescriptionDefault
hostnameNoHostname or URL to look up (e.g. 'docs.anthropic.com'). Omit for an aggregate over all tracked domains.

Output Schema

ParametersJSON Schema
NameRequiredDescription
modeYes
foundYes
recordYes
hostnameYes
aggregateYes
unavailableNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description explicitly labels the operation as read-only and states that aggregate mode reports whether the internal scan cap was hit. It does not describe error behavior or output formatting, but the read-only nature and data scope are well conveyed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is focused and information-dense, listing the relevant statistics and parameter behavior without unnecessary filler. The repeated read-only statement is minor but not harmful, and the overall structure is easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the single optional parameter and the presence of an output schema, the description adequately covers what the tool does, what data it returns, and how to select between per-domain and aggregate modes. No critical usage context appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The only parameter, hostname, is fully described in both the schema and the description, including an example value and the behavior when omitted. The optional/aggregate distinction is explicit, leaving no ambiguity about parameter usage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads the searxng-mcp domain capability database and enumerates the specific statistical data it exposes. It is easily distinguished from the search/fetch sibling tools because its focus is domain capability statistics rather than retrieving or searching content.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains how to use the tool: provide a hostname for one domain or omit it for an aggregate. It does not explicitly contrast when to use this tool versus the sibling search/fetch tools, but the domain-stats purpose makes the appropriate context reasonably clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

fetch_urlA

Fetch and extract readable content from any URL. GitHub URLs are fetched via the GitHub API; all others go through a fetch cascade: Firecrawl → Crawl4AI → raw HTTP. Returns clean markdown where possible. Content is trimmed to a token budget (default ~2000 tokens / 8000 chars; raise with max_tokens). Results cached for 24 hours. Blocked domains and private/internal addresses are refused.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesURL to fetch and extract content from
max_tokensNoApproximate token budget for the returned content (chars ≈ tokens × 4). Omit for the ~2000-token / 8000-char default; max 10000 tokens.
domain_profileNoNamed domain profile to apply: 'homelab', 'dev', or omit for default filters
target_selectorNoCSS selector to scope extraction to a specific element (e.g. 'article', 'main .content'). Honored by Firecrawl/Crawl4AI and applied client-side on the raw-HTTP tier; ignored by fast paths and if it matches nothing.
wait_for_selectorNoCSS selector to wait for before extracting, for JS-rendered pages. Honored by the rendering tiers (Firecrawl/Crawl4AI); ignored on raw HTTP (no JS).

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden for behavioral disclosure. It details the fetch cascade, token budget, caching (24 hours), and refusal of blocked/internal domains. It does not mention rate limits or error handling, but the main behaviors are well covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long, front-loaded with the primary action. The second sentence provides essential details without unnecessary words. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description explains return is clean markdown and trimmed to token budget. It covers caching and refusal behavior. It could mention return format more explicitly but is adequate for a fetch tool with moderate complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds value by explaining the default token budget (2000 tokens / 8000 chars) and how max_tokens relates to characters. It also clarifies behavior for domain_profile, target_selector, and wait_for_selector, which goes beyond the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool fetches and extracts readable content from any URL, with specific handling for GitHub URLs and a cascade of backends. It distinguishes itself from siblings like crawl_site (multi-page) and search (search) by focusing on single URL fetch and extraction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states what the tool does and provides context for when to use it (fetching any URL). It mentions blocked domains and private addresses are refused, giving a clear exclusion criterion. However, it does not explicitly contrast with siblings like crawl_site or search_and_fetch, which could be used for similar tasks.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_and_fetchA

Search the web, rerank results, then fetch the full content of the top result(s). GitHub URLs are fetched via the GitHub API; all others go through a fetch cascade: Firecrawl → Crawl4AI → raw HTTP. Results and fetched pages are cached. Blocked domains are filtered. Returns the result list plus clean markdown of the fetched pages.

ParametersJSON Schema
NameRequiredDescriptionDefault
siteNoRestrict results to one domain or a list of domains (e.g. 'github.com'). Best-effort — applied as a site: query operator; most engines honor it but some ignore it.
queryYesSearch query
expandNoUse local LLM to generate 2-3 query variants and merge results for a wider search surface (default: off). Adds ~3s latency.
enginesNoComma-separated SearXNG engine names to restrict the search to (e.g. 'google,duckduckgo'). Forwarded verbatim; unknown/disabled engines degrade to fewer results rather than erroring.
categoryNoSearch category: general, news, it, or science (default general)general
languageNoBCP-47 language code (e.g. 'en', 'de') or 'all' for all languages. Omit to use the SearXNG instance default.
min_scoreNoDrop results whose reranker relevance score is below this threshold (0-1, omit for no filtering). Filters on the RAW cross-encoder relevance score, not the recency-adjusted score used for ordering. Scores are strongly bimodal — relevant results cluster near 1.0 and irrelevant ones near 0, with little in between — so any value in roughly 0.01-0.9 behaves about the same; 0.01-0.1 is the useful range and 0.5 is not a midpoint. A high score means topically related, NOT correct: an Apache mod_proxy page scores 0.967 on an nginx query. Thresholds are model-dependent and not comparable across rerankers. No-op (with a logged warning) when the reranker is unavailable, since no scores exist to filter on.
time_rangeNoLimit results to: day, week, month, or year (omit for all time)
fetch_countNoNumber of top results to fetch full content for (default 1, max 3)
domain_profileNoNamed domain profile to apply: 'homelab', 'dev', or omit for default filters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure — and it delivers substantially. It reveals the multi-step pipeline (search → rerank → fetch), the GitHub-API special path, the fallback cascade order (Firecrawl → Crawl4AI → raw HTTP), caching of results and pages, blocked-domain filtering, and the return shape (result list + clean markdown). This far exceeds what the name or schema alone would convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences with the core purpose front-loaded in sentence one, followed by the most operationally important details in priority order: GitHub special-casing, fetch cascade, caching/filtering, and return format. Every sentence earns its place; there is zero redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 10-parameter tool with no annotations and no output schema, this is close to complete: it covers the pipeline, fetch strategy, caching, filtering, and return format. The gaps are minor — no explicit error behavior, rate limits, or detailed result-entry structure — but the highly descriptive schema compensates for most of what the description omits.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema itself is unusually rich — e.g., `min_score` documents bimodal score distribution, the 0.01-0.1 useful range, model-dependence, and no-op behavior. The description adds pipeline-level framing that helps connect parameters to stages (rerank → min_score, top results → fetch_count), but provides no per-parameter details beyond the schema. The baseline 3 applies as schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb chain and resource: 'Search the web, rerank results, then fetch the full content of the top result(s).' This clearly differentiates it from sibling tools `search` (search only) and `fetch_url` (fetch only) by describing the combined pipeline. The GitHub-API special case and fetch cascade further pin down exactly what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The pipeline description makes the usage context clear: this tool is for when an agent needs both ranked search results AND full page content. The details about caching, blocked-domain filtering, and the fetch cascade give a concrete picture of behavior. However, it never explicitly names alternatives or states when NOT to use it (e.g., 'if only URLs are needed, use search'), so exclusion guidance is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_and_summarizeA

Search, rerank, fetch top results, then synthesize a summary with citations using a local LLM (OLLAMA_SUMMARIZE_MODEL). Returns a structured answer with source attribution. If the LLM is unavailable it falls back to raw fetched content, and that fallback is always announced by a leading '--- summarization unavailable (: ) --- ' marker: if you do not see that marker, what you received is a real synthesis. Best for deep research where you want pre-digested synthesis rather than raw pages.

ParametersJSON Schema
NameRequiredDescriptionDefault
siteNoRestrict results to one domain or a list of domains (e.g. 'github.com'). Best-effort — applied as a site: query operator; most engines honor it but some ignore it.
queryYesResearch query to search for and summarize
expandNoUse query expansion before searching (default: off)
enginesNoComma-separated SearXNG engine names to restrict the search to (e.g. 'google,duckduckgo'). Forwarded verbatim; unknown/disabled engines degrade to fewer results rather than erroring.
categoryNoSearch category: general, news, it, or science (default general)general
languageNoBCP-47 language code (e.g. 'en', 'de') or 'all' for all languages. Omit to use the SearXNG instance default.
min_scoreNoDrop results whose reranker relevance score is below this threshold (0-1, omit for no filtering). Filters on the RAW cross-encoder relevance score, not the recency-adjusted score used for ordering. Scores are strongly bimodal — relevant results cluster near 1.0 and irrelevant ones near 0, with little in between — so any value in roughly 0.01-0.9 behaves about the same; 0.01-0.1 is the useful range and 0.5 is not a midpoint. A high score means topically related, NOT correct: an Apache mod_proxy page scores 0.967 on an nginx query. Thresholds are model-dependent and not comparable across rerankers. No-op (with a logged warning) when the reranker is unavailable, since no scores exist to filter on.
time_rangeNoLimit results to: day, week, month, or year (omit for all time)
fetch_countNoNumber of top results to fetch and synthesize (default 3, max 5)
domain_profileNoNamed domain profile to apply: 'homelab', 'dev', or omit for default filters

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so thoroughly: it discloses reranking, fetching limits, local LLM synthesis, the explicit fallback marker, min_score no-op warnings, and per-parameter default behaviors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although long, the description is information-dense and every sentence adds operational detail. The fallback marker is slightly repetitive but still useful for programmatic detection.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description covers search expansion, engine restrictions, categories, language, time range, fetch count, scoring thresholds, profiles, and fallback behavior. It is sufficiently complete without an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All 10 parameters are documented with meaningful detail beyond the schema. Especially strong for min_score, explaining the bimodal distribution, non-comparability across rerankers, and the no-op warning when unavailable.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states a multi-step purpose: search, rerank, fetch top results, and synthesize a cited summary. Explicitly distinguishes itself from raw-page tools with 'rather than raw pages' and 'deep research'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tells when to use it ('Best for deep research where you want pre-digested synthesis') and contrastively implies when not to use it (raw pages). Also documents fallback behavior, which helps manage expectations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev3.29.0
    • Changeddomain_stats2 fields changed
      • changedOutput schema / properties / aggregate / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "domains_tracked": {
        -        "type": "number"
        -      },
        -      "failing_count": {
        -        "type": "number"
        -      },
        -      "seen_never_fetched": {
        -        "type": "number"
        -      },
        -      "tiers": {
        -        "$ref": "#/properties/record/anyOf/0/properties/tiers"
        -      },
        -      "top_failing": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "attempts": {
        -              "type": "number"
        -            },
        -            "domain": {
        -              "type": "string"
        -            },
        -            "ok": {
        -              "type": "number"
        -            },
        -            "success_rate": {
        -              "type": "number"
        -            }
        -          },
        -          "required": [
        -            "domain",
        -            "attempts",
        -            "ok",
        -            "success_rate"
        -          ],
        -          "type": "object"
        -        },
        -        "type": "array"
        -      },
        -      "truncated": {
        -        "type": "boolean"
        -      }
        -    },
        -    "required": [
        -      "domains_tracked",
        -      "seen_never_fetched",
        -      "tiers",
        -      "failing_count",
        -      "top_failing",
        -      "truncated"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "domains_tracked": {
        +        "type": "number"
        +      },
        +      "failing_count": {
        +        "type": "number"
        +      },
        +      "schema_version": {
        +        "type": "number"
        +      },
        +      "seen_never_fetched": {
        +        "type": "number"
        +      },
        +      "tiers": {
        +        "$ref": "#/properties/record/anyOf/0/properties/tiers"
        +      },
        +      "top_failing": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "attempts": {
        +              "type": "number"
        +            },
        +            "domain": {
        +              "type": "string"
        +            },
        +            "ok": {
        +              "type": "number"
        +            },
        +            "success_rate": {
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "domain",
        +            "attempts",
        +            "ok",
        +            "success_rate"
        +          ],
        +          "type": "object"
        +        },
        +        "type": "array"
        +      },
        +      "truncated": {
        +        "type": "boolean"
        +      },
        +      "window": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "elapsed_ms": {
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "oldest_sample_ms": {
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "ttl_ceiling_ms": {
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "oldest_sample_ms",
        +          "elapsed_ms",
        +          "ttl_ceiling_ms"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "schema_version",
        +      "window",
        +      "domains_tracked",
        +      "seen_never_fetched",
        +      "tiers",
        +      "failing_count",
        +      "top_failing",
        +      "truncated"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedOutput schema / properties / unavailable
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
  2. 4 tool updatesv3.23.0
    • Changeddomain_stats1 field changed
      • changedOutput schema / properties / record / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "capabilities": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "llms_full_txt": {
        -            "type": "boolean"
        -          },
        -          "metadata_fetch_rate": {
        -            "type": [
        -              "number",
        -              "null"
        -            ]
        -          },
        -          "robots_allows_us": {
        -            "type": [
        -              "boolean",
        -              "null"
        -            ]
        -          },
        -          "seen_in_search": {
        -            "type": "number"
        -          }
        -        },
        -        "required": [
        -          "llms_full_txt",
        -          "robots_allows_us",
        -          "metadata_fetch_rate",
        -          "seen_in_search"
        -        ],
        -        "type": "object"
        -      },
        -      "domain": {
        -        "type": "string"
        -      },
        -      "first_seen": {
        -        "type": "string"
        -      },
        -      "last_fetch": {
        -        "type": "string"
        -      },
        -      "preferred_strategy": {
        -        "type": [
        -          "string",
        -          "null"
        -        ]
        -      },
        -      "tiers": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "github": {
        -            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        -          },
        -          "tier1": {
        -            "additionalProperties": false,
        -            "properties": {
        -              "attempts": {
        -                "type": "number"
        -              },
        -              "fail": {
        -                "type": "number"
        -              },
        -              "ok": {
        -                "type": "number"
        -              },
        -              "success_rate": {
        -                "type": [
        -                  "number",
        -                  "null"
        -                ]
        -              }
        -            },
        -            "required": [
        -              "attempts",
        -              "ok",
        -              "fail",
        -              "success_rate"
        -            ],
        -            "type": "object"
        -          },
        -          "tier2": {
        -            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        -          },
        -          "tier3": {
        -            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        -          },
        -          "tier4": {
        -            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        -          }
        -        },
        -        "required": [
        -          "tier1",
        -          "tier2",
        -          "tier3",
        -          "tier4",
        -          "github"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "domain",
        -      "first_seen",
        -      "last_fetch",
        -      "preferred_strategy",
        -      "tiers",
        -      "capabilities"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "capabilities": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "llms_full_txt": {
        +            "type": "boolean"
        +          },
        +          "metadata_fetch_rate": {
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "robots_allows_us": {
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          },
        +          "seen_in_search": {
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "llms_full_txt",
        +          "robots_allows_us",
        +          "metadata_fetch_rate",
        +          "seen_in_search"
        +        ],
        +        "type": "object"
        +      },
        +      "domain": {
        +        "type": "string"
        +      },
        +      "first_seen": {
        +        "type": "string"
        +      },
        +      "last_fetch": {
        +        "type": "string"
        +      },
        +      "preferred_strategy": {
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "tiers": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "crawl": {
        +            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        +          },
        +          "github": {
        +            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        +          },
        +          "solver": {
        +            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        +          },
        +          "tier1": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "attempts": {
        +                "type": "number"
        +              },
        +              "fail": {
        +                "type": "number"
        +              },
        +              "ok": {
        +                "type": "number"
        +              },
        +              "success_rate": {
        +                "type": [
        +                  "number",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "required": [
        +              "attempts",
        +              "ok",
        +              "fail",
        +              "success_rate"
        +            ],
        +            "type": "object"
        +          },
        +          "tier2": {
        +            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        +          },
        +          "tier3": {
        +            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        +          },
        +          "tier4": {
        +            "$ref": "#/properties/record/anyOf/0/properties/tiers/properties/tier1"
        +          }
        +        },
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "domain",
        +      "first_seen",
        +      "last_fetch",
        +      "preferred_strategy",
        +      "tiers",
        +      "capabilities"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedsearch1 field changed
      • addedInput schema / properties / min_score
        Added value: +{
        +  "description": "Drop results whose reranker relevance score is below this threshold (0-1, omit for no filtering). Filters on the RAW cross-encoder relevance score, not the recency-adjusted score used for ordering. Scores are strongly bimodal — relevant results cluster near 1.0 and irrelevant ones near 0, with little in between — so any value in roughly 0.01-0.9 behaves about the same; 0.01-0.1 is the useful range and 0.5 is not a midpoint. A high score means topically related, NOT correct: an Apache mod_proxy page scores 0.967 on an nginx query. Thresholds are model-dependent and not comparable across rerankers. No-op (with a logged warning) when the reranker is unavailable, since no scores exist to filter on.",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
    • Changedsearch_and_fetch1 field changed
      • addedInput schema / properties / min_score
        Added value: +{
        +  "description": "Drop results whose reranker relevance score is below this threshold (0-1, omit for no filtering). Filters on the RAW cross-encoder relevance score, not the recency-adjusted score used for ordering. Scores are strongly bimodal — relevant results cluster near 1.0 and irrelevant ones near 0, with little in between — so any value in roughly 0.01-0.9 behaves about the same; 0.01-0.1 is the useful range and 0.5 is not a midpoint. A high score means topically related, NOT correct: an Apache mod_proxy page scores 0.967 on an nginx query. Thresholds are model-dependent and not comparable across rerankers. No-op (with a logged warning) when the reranker is unavailable, since no scores exist to filter on.",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
    • Changedsearch_and_summarize1 field changed
      • addedInput schema / properties / min_score
        Added value: +{
        +  "description": "Drop results whose reranker relevance score is below this threshold (0-1, omit for no filtering). Filters on the RAW cross-encoder relevance score, not the recency-adjusted score used for ordering. Scores are strongly bimodal — relevant results cluster near 1.0 and irrelevant ones near 0, with little in between — so any value in roughly 0.01-0.9 behaves about the same; 0.01-0.1 is the useful range and 0.5 is not a midpoint. A high score means topically related, NOT correct: an Apache mod_proxy page scores 0.967 on an nginx query. Thresholds are model-dependent and not comparable across rerankers. No-op (with a logged warning) when the reranker is unavailable, since no scores exist to filter on.",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
  3. 4 tool updatesv3.15.1
    • Changedfetch_url3 fields changed
      • addedInput schema / properties / max_tokens
        Added value: +{
        +  "description": "Approximate token budget for the returned content (chars ≈ tokens × 4). Omit for the ~2000-token / 8000-char default; max 10000 tokens.",
        +  "maximum": 10000,
        +  "minimum": 100,
        +  "type": "integer"
        +}
      • addedInput schema / properties / target_selector
        Added value: +{
        +  "description": "CSS selector to scope extraction to a specific element (e.g. 'article', 'main .content'). Honored by Firecrawl/Crawl4AI and applied client-side on the raw-HTTP tier; ignored by fast paths and if it matches nothing.",
        +  "maxLength": 500,
        +  "type": "string"
        +}
      • addedInput schema / properties / wait_for_selector
        Added value: +{
        +  "description": "CSS selector to wait for before extracting, for JS-rendered pages. Honored by the rendering tiers (Firecrawl/Crawl4AI); ignored on raw HTTP (no JS).",
        +  "maxLength": 500,
        +  "type": "string"
        +}
    • Changedsearch3 fields changed
      • addedInput schema / properties / engines
        Added value: +{
        +  "description": "Comma-separated SearXNG engine names to restrict the search to (e.g. 'google,duckduckgo'). Forwarded verbatim; unknown/disabled engines degrade to fewer results rather than erroring.",
        +  "type": "string"
        +}
      • addedInput schema / properties / site
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  ],
        +  "description": "Restrict results to one domain or a list of domains (e.g. 'github.com'). Best-effort — applied as a site: query operator; most engines honor it but some ignore it."
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "answers": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "answer": {
        +            "type": "string"
        +          },
        +          "url": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "answer",
        +          "url"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "corrections": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "infoboxes": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "content": {
        +            "type": "string"
        +          },
        +          "title": {
        +            "type": "string"
        +          },
        +          "url": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "title",
        +          "content",
        +          "url"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "results": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "content": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "title": {
        +            "type": "string"
        +          },
        +          "url": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "title",
        +          "url",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "suggestions": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "answers",
        +    "infoboxes",
        +    "corrections",
        +    "suggestions",
        +    "results"
        +  ],
        +  "type": "object"
        +}
    • Changedsearch_and_fetch2 fields changed
      • addedInput schema / properties / engines
        Added value: +{
        +  "description": "Comma-separated SearXNG engine names to restrict the search to (e.g. 'google,duckduckgo'). Forwarded verbatim; unknown/disabled engines degrade to fewer results rather than erroring.",
        +  "type": "string"
        +}
      • addedInput schema / properties / site
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  ],
        +  "description": "Restrict results to one domain or a list of domains (e.g. 'github.com'). Best-effort — applied as a site: query operator; most engines honor it but some ignore it."
        +}
    • Changedsearch_and_summarize2 fields changed
      • addedInput schema / properties / engines
        Added value: +{
        +  "description": "Comma-separated SearXNG engine names to restrict the search to (e.g. 'google,duckduckgo'). Forwarded verbatim; unknown/disabled engines degrade to fewer results rather than erroring.",
        +  "type": "string"
        +}
      • addedInput schema / properties / site
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  ],
        +  "description": "Restrict results to one domain or a list of domains (e.g. 'github.com'). Best-effort — applied as a site: query operator; most engines honor it but some ignore it."
        +}
  4. 1 tool updatev3.14.0
    • Addeddomain_stats
  5. 6 tool updatesv3.12.0
    • First observedclear_cache
    • First observedcrawl_site
    • First observedfetch_url
    • First observedsearch
    • First observedsearch_and_fetch
    • First observedsearch_and_summarize

TDQS

A4.4/5.0

Scored across 7 tools

Disambiguation5/5

Each tool occupies a distinct stage of a research pipeline: search only, search+fetch, search+summarize, direct URL fetch, site crawl, cache control, and stats. The progressive search variants are explicitly differentiated by what they return and when they fetch. No two tools appear interchangeable.

Naming Consistency4/5

Most tools use a clear verb or verb_and_verb pattern: search, search_and_fetch, search_and_summarize, fetch_url, crawl_site, clear_cache. domain_stats is the only noun-style outlier, but it is still readable and fits the utility's purpose. Overall the convention is predictable.

Tool Count5/5

Seven tools is a well-scoped set for a SearXNG-backed research server. Each tool adds a distinct capability without redundancy. The count is appropriate for the purpose.

Completeness5/5

The surface covers the full research loop: search, retrieve content, summarize, crawl, cache control, and diagnostics. There are no obvious dead ends for common workflows. The set feels complete for the server's stated purpose.

Maintenance

ActivityActive
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for web search and content extraction using DuckDuckGo or SearXNG, with Playwright-based fetching and LLM-powered data extraction.
    140
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A 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.
    4
    MIT
  • A
    license
    Not graded
    quality
    F
    maintenance
    Offline-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
  • A
    license
    Not graded
    quality
    D
    maintenance
    A 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