Skip to main content
Glama

searxng-mcp

Built with Claude Code CI License: MIT npm

Un servidor MCP para búsqueda web privada a través de una instancia autoalojada de SearXNG. Los resultados se reordenan con un modelo de ML local, el contenido completo de las páginas se obtiene mediante Firecrawl, y una instancia opcional de Ollama proporciona expansión de consultas y resúmenes sintetizados por LLM.

Diseñado para su uso con agentes de Claude Code y LibreChat que necesitan búsqueda web sin enviar consultas a una API de búsqueda de terceros.

Construido con Claude Code utilizando el flujo de trabajo multiagente de homelab-agent — la misma plataforma que usa searxng-mcp en producción para investigación asistida por IA.

Inicio rápido

Se requiere una instancia en ejecución de SearXNG. Se recomienda encarecidamente un backend de caché.

Stack mínimo — inicia un backend de caché Dragonfly/Valkey y ejecuta 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

Para una topología local completa que incluya Firecrawl, Crawl4AI, Ollama, Kiwix, el proxy de bloqueo de anuncios y NATS, consulta docker-compose.full.yml.

Related MCP server: searxng-mcp-bridge

Herramientas

Herramienta

Descripción

Parámetros clave

search

Búsqueda a través de SearXNG con reordenamiento local. Obtiene un grupo de resultados más amplio, reordena por relevancia y devuelve los N mejores. Las respuestas directas nativas de SearXNG, los infoboxes, las correcciones ortográficas y las sugerencias relacionadas se muestran encima de la lista y en structuredContent.

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

search_and_fetch

Busca, reordena y luego obtiene el contenido completo de los mejores resultados usando la cascada de obtención (Firecrawl → Crawl4AI → HTTP sin procesar).

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

search_and_summarize

Busca, obtiene los mejores resultados y luego sintetiza un resumen con citas mediante Ollama (OLLAMA_SUMMARIZE_MODEL). Si Ollama no está disponible, se recurre al contenido obtenido sin procesar.

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

fetch_url

Obtiene y extrae markdown legible de cualquier URL pública. Los hosts de GitHub toman la ruta rápida de GitHub; las URLs de videos de YouTube devuelven la transcripción y las URLs de hilos de Reddit devuelven publicación+comentarios (ambos opt-in mediante robots, ver más abajo); todos los demás usan la cascada de obtención (Firecrawl → Crawl4AI → HTTP sin procesar). Se recorta a un presupuesto de tokens (por defecto ~8,000 caracteres).

url, domain_profile, max_tokens, target_selector, wait_for_selector

crawl_site

Rastrea un sitio completo y devuelve un manifiesto de URL/título/fragmento para cada página. Primero intenta el rastreo de Firecrawl, luego recurre al análisis de sitemap y, opcionalmente, a BFS. El contenido completo de la página se almacena en caché en Valkey para que las llamadas posteriores a fetch_url tengan costo cero.

url, max_pages (por defecto: CRAWL_MAX_PAGES_DEFAULT), bfs (bool, BFS opt-in)

clear_cache

Purga la caché de búsqueda, la caché de obtención, la caché de manifiesto de rastreo o todo. Útil al investigar temas de rápida evolución donde los resultados en caché pueden estar desactualizados.

target (search, fetch, crawl, all)

domain_stats

Vista de solo lectura de la base de datos de capacidades de dominio. Con hostname: tasas de éxito por nivel y banderas de capacidad de un dominio. Sin él: un agregado de todos los dominios rastreados (éxito por nivel, peores dominios con fallos, recuento de vistos-pero-nunca-obtenidos). Devuelve salida estructurada de MCP (structuredContent) para umbrales programáticos.

hostname (opcional)

Parámetros

category — general (por defecto), news, it, science

time_range — day, week, month, year — limita los resultados por fecha de publicación. Omitir para resultados de todos los tiempos.

fetch_count — número de mejores resultados reordenados para obtener su contenido completo (por defecto 1, máximo 3 para search_and_fetch; por defecto 3, máximo 5 para search_and_summarize).

domain_profile — aplica un perfil de filtro de dominio con nombre: homelab (muestra documentación autoalojada/Linux) o dev (muestra Stack Overflow, MDN, npm). Omitir para filtros predeterminados.

expand — cuando es true, reescribe la consulta mediante Ollama (OLLAMA_EXPAND_MODEL) antes de buscar para mejorar la recuperación. Requiere OLLAMA_URL. Por defecto, toma el valor de la variable de entorno EXPAND_QUERIES.

language — código de idioma BCP-47 (por ejemplo, en, de) o all para restringir a un idioma específico. Omitir para usar el valor predeterminado de la instancia de SearXNG. Disponible en search, search_and_fetch y search_and_summarize.

engines — nombres de motores de SearXNG separados por comas para restringir la búsqueda (por ejemplo, google,duckduckgo). Se reenvían tal cual; los motores desconocidos/deshabilitados degradan a menos resultados en lugar de generar errores. Disponible en las tres herramientas de búsqueda.

site — restringe los resultados a un dominio o una lista (por ejemplo, github.com o ["github.com", "gitlab.com"]). Se aplica de la mejor manera posible como operador de consulta site: — la mayoría de los motores (Google, Bing, DDG, Brave) lo respetan, algunos lo ignoran. Disponible en las tres herramientas de búsqueda.

max_tokens (fetch_url) — presupuesto aproximado de tokens para el contenido devuelto (caracteres ≈ tokens × 4). Omitir para el valor predeterminado de ~2,000 tokens / 8,000 caracteres; máximo 10,000 tokens.

target_selector (fetch_url) — selector CSS para limitar la extracción a un elemento específico (por ejemplo, article, main .content). Es respetado de forma nativa por Firecrawl/Crawl4AI y se aplica en el lado del cliente en el nivel HTTP sin procesar; se ignora en las rutas rápidas y cuando no coincide con nada.

wait_for_selector (fetch_url) — selector CSS para esperar antes de extraer, para páginas renderizadas con JS. Es respetado por los niveles de renderizado (Firecrawl/Crawl4AI); se ignora en HTTP sin procesar (sin JS).

Arquitectura

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 y Firecrawl son obligatorios. Crawl4AI, Valkey, Ollama, Kiwix y el reordenador son opcionales: el servidor se degrada con elegancia cuando cualquiera de ellos no está disponible.

Bloqueo de anuncios

searxng-mcp utiliza dos sidecars independientes de bloqueo de anuncios, uno por grupo de niveles de obtención:

Sidecar

Nivel

Mecanismo

docker/puppeteer-adblock/

Nivel 1 (Firecrawl)

Intercepción a nivel de CDP: filtrado HTTPS completo, mismo proceso de navegador

docker/adblock-proxy/

Niveles 2+3 (Crawl4AI, obtención sin procesar)

Proxy de reenvío HTTP: filtra dominios de anuncios de HTTP simple

Nivel 1 — Bloqueo de anuncios de Puppeteer

El servicio firecrawl-puppeteer utilizado por Firecrawl ejecuta una imagen personalizada (docker/puppeteer-adblock/) que superpone @ghostery/adblocker-puppeteer sobre el trieve/puppeteer-service-ts ascendente. EasyList + EasyPrivacy se cargan al inicio y se actualizan cada 168 horas; el bloqueador se aplica a cada página que Firecrawl crea. Acelera la obtención de sitios con muchos anuncios y reduce el tamaño del DOM renderizado.

Variables de entorno:

Var

Predeterminado

Descripción

ADBLOCK_DISABLE

sin establecer

Establecer a true para omitir la carga de filtros por completo.

ADBLOCK_FILTERS_URL

EasyList + EasyPrivacy

Lista de URLs de listas de filtros separadas por comas.

ADBLOCK_REFRESH_HOURS

168

Cadencia con la que el bloqueador se reconstruye desde las URLs configuradas.

La imagen base está fijada por digest SHA256. Para implementar un cambio, reconstruye y reinicia el servicio:

docker compose -f ~/docker/firecrawl-simple/docker-compose.yml up -d --build firecrawl-puppeteer

Omisión por dominio: domains.json reserva una ranura adblock_skip para futuras anulaciones del operador. El cableado aún no está implementado: requeriría que Firecrawl reenvíe un encabezado personalizado al servicio de puppeteer, lo cual no es parte de su API actual. Se rastrea como elemento de expansión de alcance I.

Niveles 2+3 — Proxy de bloqueo de anuncios

Establece ADBLOCK_PROXY_URL (por ejemplo, http://adblock-proxy:8118) para enrutar las solicitudes de Crawl4AI y de obtención sin procesar de Node a través de un proxy de reenvío HTTP que filtra solicitudes de anuncios y rastreadores. Los túneles CONNECT de HTTPS se pasan sin modificar (sin MITM), por lo que el filtrado se aplica solo a dominios de anuncios de HTTP simple. El enganche de puppeteer del nivel 1 ya maneja el filtrado HTTPS completo para ese nivel; el proxy cubre lo que se filtra en los niveles 2 y 3.

Consulta docker/adblock-proxy/ para la definición del servicio, las opciones de configuración y las instrucciones de implementación (incluidas en docker-compose.full.yml).

Enrutamiento de niveles basado en datos

Antes de invocar la cascada de obtención, searxng-mcp lee el tier_stats_30d del dominio (consulta base de datos de capacidades de dominio) y omite cualquier nivel con una tasa de éxito inferior al 30% en al menos 10 intentos. Los dominios en frío (<10 intentos) mantienen la cascada predeterminada. Cada omisión emite un evento NATS searxng.fetch.tier.skipped con reason: low_success_rate e incrementa searxng_fetch_total{outcome=skipped}.

Anulación del operador. Agrega un mapa tier_skip a domains.json para forzar la omisión de niveles independientemente de las estadísticas:

{
  "tier_skip": {
    "example-bot-blocked.com": ["tier1"],
    "another-site.example": ["tier1", "tier2"]
  }
}

Las claves de tier_skip pueden ser dominios simples (example.com coincide con el dominio y todos los subdominios) o dominio + prefijo de ruta (example.com/api/). El archivo se recarga en caliente: no se necesita reinicio. Las anulaciones manuales emiten reason: operator_override.

Ruta rápida de tipo de contenido

Una URL que sirve contenido estructurado no HTML — application/json, cualquier *+json, XML, YAML, TOML, CSV o text/plain — se detecta mediante una sonda HEAD y se enruta directamente al nivel HTTP sin procesar en lugar de a la cascada completa de Firecrawl/Crawl4AI. El JSON se devuelve con formato bonito dentro de un bloque de código cercado. Anteriormente, pedir a un navegador sin interfaz que renderizara una respuesta de API JSON o un recurso CDN devolvía markdown vacío, por lo que los endpoints de API y CDN (registry.npmjs.org, api.osv.dev, cdn.jsdelivr.net, …) simplemente fallaban.

Garantías:

  • La sonda es fail-open. Un host inalcanzable, un servidor que rechaza HEAD o una cabecera Content-Type ilegible o no analizable caen todos a la cascada normal sin cambios.

  • application/xhtml+xml se excluye deliberadamente: eso es marcado para un navegador, no datos estructurados.

  • El HTML que un servidor etiqueta erróneamente como text/plain se sigue analizando como HTML, no se vuelca como un bloque de texto sin procesar.

Base de datos de capacidades de dominio

Cada búsqueda registra lo que searxng-mcp aprende sobre el dominio de destino en Valkey bajo domain:<hostname> (TTL de 90 días, schema_version 5). Capturado por registro:

  • tier_stats_30d.{tier1,tier2,tier3,tier4,github}.{attempts, ok, fail, last_fail_reason, window_start_ms} — tasa de éxito de búsqueda por nivel en una ventana móvil de 30 días. El corte se aplica en tiempo de lectura, compartido por las decisiones de enrutamiento de nivel y los informes de domain_stats, de modo que ambos no pueden discrepar: un dominio buscado una vez y luego dejado inactivo informa una ventana genuinamente vacía en lugar de números obsoletos que sobreviven hasta la siguiente escritura. La ranura tier4 (Wayback Machine) se registra solo cuando WAYBACK_ENABLED=true. La ranura github registra la ruta rápida de GitHub (raw.githubusercontent.com / api.github.com / búsquedas de README de github.com), que omite la cascada de niveles pero aún se rastrea aquí. Un aumento de schema_version reconstruye los registros existentes desde cero: las ventanas acumuladas para dominios actualmente inactivos se descartan (con precedente en los aumentos 1→2, 2→3, 3→4, 4→5).

  • capabilities.metadata_fetch.{attempts, ok, fail, last_fail_reason} — éxito/fracaso de la búsqueda del canal secundario de metadatos (fetchRawHtmlForMetadata, utilizado para el muestreo de JSON-LD/og:title). Se rastrea por separado de tier_stats_30d porque responde a "¿es alcanzable este dominio?", no a "¿la entrega de contenido completo tuvo éxito?".

  • capabilities.seen_in_search.{count, last_seen_ms} — con qué frecuencia aparece el dominio en los resultados de search. Escrito de forma fire-and-forget por searxSearch() en cada ruta de retorno (incluidos los aciertos de caché) sin realizar una búsqueda, de modo que un dominio puede rastrearse antes de que se busque.

  • capabilities.robots_txt.{present, fetched, allows_us} — presencia de robots.txt y si nos permite

  • capabilities.llms_full_txt.{present, size_bytes, last_checked} — si el dominio sirve /llms-full.txt

  • capabilities.json_ld_article.{sampled, present, last_sampled_at} — si la página lleva JSON-LD de esquema Article (Schema.org Article/NewsArticle/BlogPosting/TechArticle y subtipos como ScholarlyArticle/OpinionNewsArticle/LiveBlogPosting, coincididos por nombre simple o @type totalmente calificado https://schema.org/...), independientemente de si ese esquema tenía texto de cuerpo extraíble — muchos sitios publican JSON-LD de titular/metadatos sin articleBody, lo cual es una preocupación distinta de que la post-extracción realmente lo use.

  • capabilities.og_title.{sampled, present, last_sampled_at} — lo mismo para <meta property="og:title">

  • preferred_strategy — actualmente se establece en llms_full_txt cuando una sonda presente aterriza; las fases futuras usarán esto para omitir la cascada de niveles

Inspeccione un registro con el CLI incluido, o consúltelo desde un agente mediante la herramienta domain_stats (de un solo dominio o agregada; consulte Herramientas):

pnpm dump-domain docs.anthropic.com

dump-domain distingue una ventana que ha expirado de un nivel que no tiene datos en absoluto, en lugar de mostrar ambos de la misma manera.

Las actualizaciones concurrentes para el mismo hostname (los registradores de intento de nivel, sonda de robots y muestra post-extracción que se disparan en paralelo durante una búsqueda) se serializan mediante un compare-and-set Lua del lado del servidor, junto con una cola por clave en el proceso que elimina la contención entre los propios escritores de un solo proceso, de modo que el CAS solo tiene que arbitrar escrituras genuinamente concurrentes entre procesos. Las versiones anteriores a v3.17.0 usaban una lectura-modificación-escritura WATCH/MULTI/EXEC contra una conexión compartida, que en realidad no serializa escritores concurrentes — los datos recopilados antes de v3.17.0 eran sustancialmente incompletos como resultado. La actualización descarta las estadísticas de nivel existentes mediante el aumento de esquema; espere que domain_stats lea casi vacío inmediatamente después de actualizar y se rellene en los días siguientes.

Persistencia de la base de datos de dominios

La base de datos de dominios vive solo en Valkey bajo un TTL de 90 días y ventanas móviles de 30 días, por lo que un vaciado de caché o la expiración del TTL borra el aprendizaje de capacidades que es costoso de readquirir. Dos CLIs lo hacen duradero:

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 es un trabajo independiente (ejecútelo en un horario mediante cron o un reinicio cron de PM2 — no como un temporizador en el proceso, ya que searxng-mcp se ejecuta como varios hijos stdio concurrentes por agente que cada uno lo dispararía). Un SCAN acotado alimenta ambas salidas: una instantánea fechada duradera y, cuando OTEL_EXPORTER_OTLP_ENDPOINT está establecido, medidores (searxng_domains_tracked, searxng_domains_failing, searxng_domain_tier_success_ratio{tier}) forzados a vaciarse antes de salir.

  • restore-domain-db re-siembra solo las claves que faltan o cuyo registro vivo es estrictamente más antiguo que la instantánea (compara last_fetch) — nunca sobrescribe un registro vivo más reciente o igual, por lo que es seguro ejecutarlo contra un Valkey vivo y parcialmente poblado (por ejemplo, en una secuencia de arranque de servicio para recuperación automática de vaciado).

Variable de entorno

Predeterminado

Propósito

DOMAIN_DB_SNAPSHOT_DIR

./domain-db-snapshots

Dónde se escriben/leen las instantáneas fechadas. Establezca una ruta duradera (appdata o montaje NFS) en el despliegue.

DOMAIN_DB_SNAPSHOT_RETENTION

14

Cuántas instantáneas conservar; las más antiguas se podan en cada ejecución de mantenimiento.

Ruta rápida de llms.txt

Para dominios de documentación en lista blanca en domains.json (array llms_txt), fetchPage intenta <origin>/llms-full.txt primero y extrae la sección que coincide con la URL solicitada antes de invocar cualquier nivel. Esto evita ejecutar puppeteer contra sitios de documentación bien instrumentados y devuelve directamente una sección de markdown limpia. Los resultados de la sonda y el cuerpo completo se almacenan en caché en Valkey (llms:<origin>:full, 24 h / 7 d para presente/ausente). Lista blanca predeterminada: docs.anthropic.com, docs.openai.com, docs.stripe.com, docs.crawl4ai.com, docs.firecrawl.dev, docs.cursor.com. Extienda editando domains.json — el archivo se recarga en caliente.

Ruta rápida de Kiwix

Cuando KIWIX_URL está establecido, las solicitudes de búsqueda para hosts conocidos con capacidad offline se interceptan antes de la cascada de Firecrawl/Crawl4AI y se sirven desde el archivo ZIM local de Kiwix. Esto elimina la tasa de fallo del 100% del nivel 1 para sitios como Wikipedia (que bloquea los raspadores sin interfaz) y devuelve contenido limpio y legible con cero tráfico de red externo.

Hosts admitidos y libros ZIM (kiwix-serve debe ejecutarse con --nodatealiases / -z):

Host

Libro ZIM

en.wikipedia.org, wikipedia.org

wikipedia_en_all_mini

stackoverflow.com

stackoverflow.com_en_all

wiki.archlinux.org

archlinux_en_all_maxi

La ruta de Kiwix se ejecuta después de la ruta rápida de llms-txt y antes de la puerta de robots. Si la solicitud de Kiwix falla o devuelve vacío, la cascada de niveles completa se ejecuta normalmente. Cuando KIWIX_URL no está establecido, la característica añade cero sobrecarga — isKiwixHost() devuelve false inmediatamente.

Establezca KIWIX_URL a su URL base de kiwix-serve (por ejemplo, http://localhost:8292).

Rutas rápidas de YouTube y Reddit

fetch_url reconoce URLs de videos de YouTube (youtube.com, youtu.be) y URLs de hilos de Reddit y puede servirlas directamente en lugar de raspar la página renderizada:

  • YouTube — extrae la pista de subtítulos del video de la página de visualización y devuelve la transcripción. Habilitado por YOUTUBE_TRANSCRIPT_ENABLED (activado por defecto).

  • Reddit — obtiene la vista pública .json y devuelve la publicación más los comentarios principales en la forma estándar {title, url, text}; cae en caso de HTTP 429. Habilitado por REDDIT_FASTPATH_ENABLED (activado por defecto).

Ambos dependen de endpoints no oficiales y no documentados (la API timedtext de YouTube, el .json de Reddit) — de mejor esfuerzo sin SLA; cualquiera puede romperse ante un cambio ascendente, de ahí los interruptores de apagado. Ante cualquier fallo, la solicitud cae a la cascada de niveles normal (que aún puede obtener el título/descripción de una página de YouTube).

robots.txt: ambos endpoints están deshabilitados por los robots.txt de los sitios (Reddit lo deshabilita todo; YouTube deshabilita /api/, donde vive la transcripción). Por defecto, estas rutas rápidas respetan eso y permanecen inactivas, cayendo a la cascada. En su propia instancia puede optar por la búsqueda directa con YOUTUBE_IGNORE_ROBOTS=true / REDDIT_IGNORE_ROBOTS=true.

Rastreo de sitios

crawl_site rastrea un sitio completo y devuelve un manifiesto de URL/título/fragmento para cada página encontrada. Utiliza una cascada de estrategias de tres fases:

  1. Rastreo de Firecrawl — envía un trabajo de rastreo a Firecrawl (endpoint /crawl), sondea hasta que se complete y devuelve la lista completa de páginas. Controlado por FIRECRAWL_CRAWL_POLL_INTERVAL_MS y FIRECRAWL_CRAWL_MAX_WAIT_MS.

  2. Análisis de sitemap — si Firecrawl falla o devuelve vacío, obtiene /sitemap.xml (y sitemaps enlazados) y extrae URLs con títulos/fragmentos. Utiliza fast-xml-parser para el análisis XML del sitemap.

  3. Rastreo BFS (opt-in) — si el análisis del sitemap también falla, realiza un rastreo en amplitud comenzando desde la URL dada hasta CRAWL_BFS_MAX_DEPTH saltos de enlace. Solo se ejecuta cuando CRAWL_BFS_ENABLED=true o el parámetro de herramienta bfs es true.

El contenido completo de la página obtenido durante el rastreo se almacena en caché en Valkey (TTL: CRAWL_MANIFEST_TTL_SECONDS, predeterminado 6 horas). Las llamadas posteriores a fetch_url para cualquier URL en el manifiesto devuelven inmediatamente desde la caché — cero sobrecarga de búsqueda para lecturas de seguimiento.

La caché del manifiesto se puede limpiar con clear_cache(target="crawl").

Respaldo de Wayback Machine

Cuando WAYBACK_ENABLED=true, un cuarto nivel consulta la API CDX de Wayback Machine para obtener una instantánea archivada cuando fallan los tres niveles principales. El contenido devuelto se prefija con una cabecera de procedencia ([Instantánea archivada – <timestamp> – <original_url>]) para que los llamadores sepan que el contenido puede no reflejar el estado actual de la página.

Calidad de búsqueda

Después de que cualquier nivel devuelva contenido con HTML sin procesar, un pase posterior a la extracción mejora la calidad del título y del cuerpo:

  • Extracción de artículo JSON-LD — los bloques de Schema.org Article / NewsArticle / BlogPosting / TechArticle suministran headline y articleBody más limpios que el raspado de cromo del nivel 1 (con límite de tamaño de 1 MB por etiqueta de script).

  • Cascada de títulos — retrocede a través de og:title → twitter:title → <title> (con eliminación de sufijo de editor) → primer <h1> → URL.

  • Comparación de legibilidad del nivel 2 — cuando Crawl4AI devuelve markdown, JSDOM+Readability también se ejecuta sobre su HTML sin procesar y se prefiere cuando su texto es más largo (o incondicionalmente cuando Crawl4AI devuelve menos de 500 caracteres).

Resiliencia

  • La caché nunca bloquea una búsqueda. El cliente de Valkey está limitado por CACHE_COMMAND_TIMEOUT_MS/CACHE_CONNECT_TIMEOUT_MS/CACHE_MAX_RETRIES_PER_REQUEST (ver Configuración). Un backend de caché atascado o con picos de CPU ahora rechaza el comando en lugar de bloquearse para siempre — el manejo existente de fallo suave degrada ese rechazo a un fallo de caché (servir en vivo) en lugar de lanzar una excepción. Los fallos de conexión de caché, errores de cliente y errores por comando emiten una línea de stderr limitada [searxng-mcp] (deduplicada por clave, de modo que una interrupción sostenida deja una miga de pan periódica, no una inundación) — stderr es el único sumidero de telemetría conectado en el proceso PM2 desplegado.

  • Manejadores de caída de proceso — uncaughtException registra y luego sale con código 1 (reinicio limpio de PM2); unhandledRejection registra y continúa en lugar de colapsar el proceso compartido silenciosamente.

  • Advertencias de degradación elegante — el respaldo del reranker y los respaldos de expansión y resumen de Ollama/LLM emiten una línea de stderr limitada cada uno cuando degradan silenciosamente la calidad (reranker no disponible, backend LLM inalcanzable).

  • La versión tiene una única fuente desde package.json en tiempo de ejecución (src/version.ts) — la versión de McpServer, la versión del tracer/medidor de OTel y el USER_AGENT saliente la siguen, por lo que no pueden desviarse de forma independiente.

Observabilidad (opt-in)

El rastreo, las métricas y la publicación de eventos son completamente opt-in — sin ninguna de las variables de entorno siguientes configuradas, el servidor tiene cero sobrecarga de observabilidad y nunca carga los paquetes de OpenTelemetry o NATS en tiempo de ejecución.

OpenTelemetry (trazas + métricas) — establece OTEL_EXPORTER_OTLP_ENDPOINT al endpoint HTTP de tu colector y el servidor emite:

  • Spans (por solicitud): tool.<name> → expand_query? → searxng_request → rerank → fetch (×N) → tier1_firecrawl | tier2_crawl4ai | tier3_rawfetch → post_extract; además summarize_llm para search_and_summarize.

  • Contadores: searxng_search_total{profile, expand}, searxng_fetch_total{tier, outcome}, searxng_cache_total{namespace, outcome}, searxng_errors_total{stage, error_type}.

  • Histogramas: searxng_search_duration_seconds{profile}, searxng_fetch_duration_seconds{tier, outcome}.

Se aplican las variables de entorno estándar de OTEL (OTEL_SERVICE_NAME por defecto es searxng-mcp).

Eventos NATS — establece NATS_URL (por ejemplo, nats://localhost:4222) y el servidor publica un evento estructurado en cada búsqueda, captura, acierto/fallo de caché, omisión de robots y error. Autentica mediante NATS_CREDS (un archivo de credenciales JWT) o NATS_USER/NATS_PASSWORD (nombre de usuario/contraseña bcrypt) — la autenticación por archivo de credenciales gana si ambos están configurados. Asuntos:

Asunto

Cuándo

searxng.search.requested

Herramienta de búsqueda invocada

searxng.search.completed

Búsqueda devuelta (con fuentes, latencia, rerank aplicado)

searxng.fetch.requested

fetchPage llamado

searxng.fetch.tier.miss

Un nivel devolvió vacío o lanzó una excepción

searxng.fetch.tier.skipped

robots.txt lo desautorizó

searxng.fetch.completed

Captura resuelta (con tier_served, text_len, latencia)

searxng.cache.hit / .miss

En cada búsqueda en Valkey

searxng.error

Errores etiquetados por etapa

Cada sobre incluye request_id y (cuando OTel está habilitado) trace_id para que los suscriptores puedan unir los dos flujos. El prefijo del asunto se puede sobrescribir mediante NATS_SUBJECT_PREFIX. Las consultas de búsqueda fluyen a través de eventos search.* — los consumidores posteriores son responsables de cualquier limpieza de PII.

Cortesía

  • User-Agent honesto — las solicitudes salientes se identifican como searxng-mcp/<version> (+https://github.com/TadMSTR/searxng-mcp; investigación personal).

  • Cumplimiento de robots.txt — /robots.txt se obtiene una vez por origen y se almacena en caché durante 24 horas en Valkey bajo robots:<origin>. Las rutas no permitidas se omiten antes de que se ejecute cualquier nivel y se registran como skipped_robots url=… reason=….

Transporte

stdio (predeterminado) — compatible con el plugin MCP de Claude Code y la configuración stdio de LibreChat.

HTTP — establece SEARXNG_MCP_TRANSPORT=http para ejecutarse como un servidor HTTP/SSE compartido adecuado para despliegues multi-cliente o configuraciones basadas en Docker. Se vincula a SEARXNG_MCP_HOST:SEARXNG_MCP_PORT (por defecto 127.0.0.1:3001):

SEARXNG_MCP_TRANSPORT=http SEARXNG_MCP_PORT=3001 npx @tadmstr/searxng-mcp

Registra con Claude Code contra un servidor HTTP:

claude mcp add-json searxng --scope user '{
  "type": "http",
  "url": "http://localhost:3001/mcp"
}'

Las sesiones se identifican mediante el encabezado Mcp-Session-Id, por lo que múltiples clientes pueden conectarse al mismo proceso compartido de forma concurrente. Las sesiones inactivas se eliminan después de HTTP_SESSION_IDLE_TIMEOUT_MS y se limitan estrictamente a HTTP_MAX_SESSIONS — ver Configuración.

Autenticación del transporte HTTP

El transporte HTTP está sin autenticación por defecto, lo cual es seguro solo porque se vincula a 127.0.0.1 por defecto. Si cambias SEARXNG_MCP_HOST a cualquier otra cosa — incluido 0.0.0.0, que es lo que requiere ejecutar en un contenedor — establece también SEARXNG_MCP_AUTH_TOKEN:

SEARXNG_MCP_AUTH_TOKEN=$(openssl rand -hex 32)

Cuando está configurado, cada solicitud excepto GET /health debe llevar el token como credencial de portador RFC 6750:

Authorization: Bearer <token>

Cualquier otra cosa — sin encabezado, un esquema diferente, un token incorrecto — recibe 401 con WWW-Authenticate: Bearer y un cuerpo de error JSON-RPC. La respuesta es idéntica en los tres casos y nunca repite la credencial presentada. Los tokens se comparan como resúmenes SHA-256, por lo que la comparación es de tiempo constante y no filtra información de longitud.

Registrar un servidor autenticado con Claude Code:

claude mcp add-json searxng --scope user '{
  "type": "http",
  "url": "http://localhost:3001/mcp",
  "headers": {"Authorization": "Bearer <token>"}
}'

Dejar la variable sin configurar preserva exactamente el comportamiento anterior, por lo que los usuarios de stdio y los despliegues HTTP existentes vinculados a loopback no necesitan cambios. No hay un modelo de autorización por llamador — un solo token autentica el acceso al servidor, no una identidad de cliente particular. Al inicio, un enlace no-loopback sin token registra una advertencia.

GET /health está deliberadamente exento de la verificación. Es el healthcheck del contenedor y la sonda de liveness de monitoreo, no toma entrada, y su respuesta (status, cache, sessions) no lleva secretos.

GET /health — sonda de liveness sin autenticación, vinculada a localhost junto al endpoint MCP. Hace ping a Valkey a través del tiempo de espera de comando de caché limitado (para que la verificación en sí nunca se bloquee) y devuelve:

{"status": "ok", "cache": "up", "sessions": 3}

o, cuando el backend de caché es inalcanzable:

{"status": "degraded", "cache": "degraded", "sessions": 3}

sessions es el recuento de sesiones HTTP en vivo. Útil para el monitoreo de administradores de sistemas para detectar una caché degradada desde el lado MCP sin instrumentar directamente el backend de caché.

Requisitos previos

  • Node.js 20+

  • pnpm (o npm)

  • Una instancia de SearXNG en ejecución

  • Una instancia de Firecrawl en ejecución

  • Un reranker en ejecución que exponga un endpoint /v1/rerank compatible con Jina (opcional)

  • Una instancia de Valkey o compatible con Redis en ejecución (opcional, para caché de resultados)

  • Una instancia de Ollama en ejecución con qwen3:4b y/o qwen3:14b descargados (opcional, para expansión de consultas y resumen)

SearXNG

SearXNG debe tener habilitado el formato de salida JSON. En settings.yml:

search:
  formats:
    - html
    - json

Reranker

El reranker debe exponer un endpoint /v1/rerank compatible con Jina. Un envoltorio ligero de FlashRank funciona bien — ver la referencia docker/reranker/ en homelab-agent.

Firecrawl

Cualquier instancia compatible con Firecrawl funciona. El despliegue local firecrawl-simple es suficiente. Establece FIRECRAWL_API_KEY si tu instancia requiere autenticación (por defecto es placeholder-local para despliegues locales que omiten la autenticación).

Crawl4AI

Crawl4AI es un respaldo de captura de segundo nivel opcional que se usa cuando Firecrawl devuelve contenido vacío (páginas bloqueadas por bots, sitios con mucho JavaScript). Establece CRAWL4AI_URL para habilitarlo. Si no está configurado, la cascada salta a la captura HTTP cruda.

docker run -d -p 11235:11235 unclecode/crawl4ai:0.8.6

Si tu instancia requiere autenticación por token de API, establece CRAWL4AI_API_TOKEN.

En la ruta search_and_summarize, las solicitudes de Crawl4AI usan fit_markdown para la extracción de contenido filtrado por ruido. Otros llamadores (search_and_fetch, fetch_url) usan raw_markdown.

Kiwix (opcional)

kiwix-serve sirve archivos ZIM sobre HTTP. Descarga los archivos ZIM requeridos y ejecuta kiwix-serve con --nodatealiases (-z) para que los nombres de los libros sean estables:

kiwix-serve --port 8292 --nodatealiases /path/to/zims/

Archivos ZIM requeridos para cada host compatible:

  • Wikipedia: wikipedia_en_all_mini (o maxi)

  • Stack Overflow: stackoverflow.com_en_all

  • Arch Wiki: archlinux_en_all_maxi

Los archivos ZIM se pueden descargar desde library.kiwix.org.

Hister (opcional)

Hister es un índice de historial de navegación poblado por una extensión de Firefox. Cuando HISTER_URL está configurado, fetchPage verifica el índice de historial antes de invocar la cascada de niveles — útil para páginas con muro de inicio de sesión y con mucho JavaScript donde los raspadores fallan.

Establece HISTER_URL a la URL base de tu instancia de Hister y HISTER_TOKEN si se requiere autenticación por token de portador.

Valkey / Redis

Cualquier instancia compatible con Redis. Se recomienda Valkey. Los resultados de búsqueda se almacenan en caché durante 1 hora; las páginas capturadas durante 24 horas. Si no está disponible, el servidor opera sin caché.

Ollama

Requerido para expand y search_and_summarize. Descarga los modelos requeridos:

ollama pull qwen3:4b   # query expansion
ollama pull qwen3:14b  # summarization

El comportamiento de think: false se maneja automáticamente — no se necesita configuración adicional de Ollama.

Configuración

Todas las URLs de servicios son configurables mediante variables de entorno.

Variable

Default

Description

SEARXNG_URL

http://localhost:8081

URL de la instancia de SearXNG

FIRECRAWL_URL

http://localhost:3002

URL de la instancia de Firecrawl

RERANKER_URL

http://localhost:8787

URL de la instancia de Reranker

FIRECRAWL_API_KEY

placeholder-local

Clave API de Firecrawl (si es necesaria)

GITHUB_TOKEN

(unset)

Token de acceso personal de GitHub: aumenta el límite de solicitudes de 60 a 5,000 por hora

OLLAMA_URL

(unset)

URL base de la API de Ollama: necesaria para expand y search_and_summarize

OLLAMA_API_KEY

(unset)

Token Bearer para proxies de Ollama autenticados: añade la cabecera Authorization: Bearer <key> cuando se establece

OLLAMA_EXPAND_MODEL

qwen3:4b

Modelo utilizado por la expansión de consultas (parámetro expand). Se puede sobrescribir sin recompilar.

OLLAMA_SUMMARIZE_MODEL

qwen3:14b

Modelo utilizado por search_and_summarize. Se puede sobrescribir sin recompilar.

LLM_BASE_URL

(unset)

Endpoint de chat compatible con OpenAI (p. ej. vLLM, llama.cpp, LM Studio) para expand + search_and_summarize. Debe incluir la ruta de la API, p. ej. http://host:8000/v1; el servidor añade /chat/completions. Cuando se establece, tiene prioridad sobre OLLAMA_URL, de modo que se puede reutilizar un modelo ya cargado en lugar de ejecutar un modelo Ollama por separado.

LLM_MODEL

(unset)

Identificador del modelo para el backend compatible con OpenAI; sobrescribe OLLAMA_EXPAND_MODEL / OLLAMA_SUMMARIZE_MODEL cuando se establece.

LLM_API_KEY

(unset)

Token Bearer para el backend compatible con OpenAI: añade Authorization: Bearer <key> cuando se establece.

LLM_DISABLE_THINKING

true

Envía chat_template_kwargs.enable_thinking: false para que los modelos de razonamiento (p. ej. Qwen3) devuelvan una salida directa. Establézcalo en false para servidores que rechacen ese campo.

CACHE_URL

redis://localhost:6381

URL compatible con Redis: habilita el almacenamiento en caché de resultados. También acepta VALKEY_URL o REDIS_URL como alias. Funciona con Redis, Valkey y Dragonfly. El servidor degrada con elegancia si no está disponible.

CACHE_COMMAND_TIMEOUT_MS

2500

Tiempo de espera de comandos de Valkey: un backend de caché bloqueado o con picos de CPU rechaza en lugar de colgarse (cacheGet() es el primer await en cada búsqueda). Los valores no válidos o no positivos vuelven al valor predeterminado en lugar de convertirse en NaN que deshabilitaría el tiempo de espera.

CACHE_CONNECT_TIMEOUT_MS

3000

Tiempo de espera de conexión de Valkey. Mismo comportamiento de respaldo que CACHE_COMMAND_TIMEOUT_MS.

CACHE_MAX_RETRIES_PER_REQUEST

2

Máximo de reintentos por comando Valkey antes de que rechace. Mismo comportamiento de respaldo que CACHE_COMMAND_TIMEOUT_MS.

CACHE_TTL_SECONDS

3600

TTL de caché de resultados de búsqueda en segundos

FETCH_CACHE_TTL_SECONDS

86400

TTL de caché de páginas obtenidas en segundos

CRAWL_MANIFEST_TTL_SECONDS

21600

TTL de caché del manifiesto de rastreo y del contenido de la página en segundos (6 horas)

CRAWL_MAX_PAGES_DEFAULT

20

Máximo de páginas predeterminado devuelto por crawl_site cuando no se pasa max_pages

CRAWL_BFS_ENABLED

false

Establézcalo en true para habilitar el respaldo BFS en crawl_site globalmente. También se puede habilitar por llamada con el parámetro bfs.

CRAWL_BFS_MAX_DEPTH

3

Profundidad máxima de enlaces para el rastreo BFS

FIRECRAWL_CRAWL_POLL_INTERVAL_MS

2000

Intervalo de sondeo al esperar a que un trabajo de rastreo de Firecrawl se complete

FIRECRAWL_CRAWL_MAX_WAIT_MS

120000

Tiempo máximo de espera para un trabajo de rastreo de Firecrawl antes de recurrir al mapa del sitio

EXPAND_QUERIES

false

Establézcalo en true para habilitar la expansión de consultas globalmente

CRAWL4AI_URL

(unset)

URL de la instancia de Crawl4AI: habilita el respaldo de obtención de segundo nivel cuando Firecrawl falla

CRAWL4AI_API_TOKEN

(unset)

Token Bearer opcional para instancias de Crawl4AI con protección por token de API

WAYBACK_ENABLED

false

Establézcalo en true para habilitar el respaldo de nivel 4 de Wayback Machine: obtiene instantáneas archivadas cuando fallan los tres niveles

ADBLOCK_PROXY_URL

(unset)

URL de proxy HTTP para el bloqueo de anuncios de nivel 2 (Crawl4AI) y nivel 3 (fetch de Node sin procesar), p. ej. http://adblock-proxy:8118. Consulte docker/adblock-proxy/.

KIWIX_URL

(unset)

URL base de kiwix-serve (p. ej. http://localhost:8292): habilita la ruta rápida de Kiwix para Wikipedia, Stack Overflow y Arch Wiki. La función está deshabilitada y sin sobrecarga cuando no se establece.

HISTER_URL

(unset)

URL base del índice de historial de navegación de Hister: habilita la ruta rápida de Hister antes de la cascada de niveles para páginas con muro de inicio de sesión y con mucho JavaScript. Función deshabilitada y sin sobrecarga cuando no se establece.

HISTER_TOKEN

(unset)

Token Bearer para la autenticación de la API de Hister. Necesario cuando HISTER_URL está establecido y la instancia tiene autenticación por token habilitada.

YOUTUBE_TRANSCRIPT_ENABLED

true

Habilita la ruta rápida de transcripción de YouTube en fetch_url. Establézcalo en false para deshabilitarla (p. ej. si el endpoint no oficial de timedtext se rompe aguas arriba).

YOUTUBE_IGNORE_ROBOTS

false

Opta por obtener transcripciones de YouTube a pesar de que el robots.txt de YouTube prohíba /api/. Por defecto respeta robots (la ruta rápida permanece inactiva y pasa a la cascada).

REDDIT_FASTPATH_ENABLED

true

Habilita la ruta rápida de .json de Reddit en fetch_url. Establézcalo en false para deshabilitarla.

REDDIT_IGNORE_ROBOTS

false

Opta por obtener el .json de Reddit a pesar del robots.txt de Reddit (Disallow: /). Por defecto respeta robots (la ruta rápida permanece inactiva y pasa a la cascada).

SEARXNG_MCP_TRANSPORT

stdio

Modo de transporte: stdio (predeterminado, de un solo cliente) o http (servidor HTTP/SSE compartido).

SEARXNG_MCP_PORT

3001

Puerto de escucha HTTP (solo en modo de transporte HTTP).

SEARXNG_MCP_HOST

127.0.0.1

Dirección de escucha HTTP (solo en modo de transporte HTTP).

SEARXNG_MCP_AUTH_TOKEN

(unset)

Solo transporte HTTP. Cuando se establece, cada solicitud excepto GET /health debe enviar Authorization: Bearer <token> o recibirá un 401. Sin establecer (el valor predeterminado) deshabilita la verificación por completo. Establézcalo siempre que SEARXNG_MCP_HOST no sea loopback — consulte Autenticación de transporte HTTP.

HTTP_SESSION_IDLE_TIMEOUT_MS

600000

Solo transporte HTTP. Una sesión inactiva durante más tiempo que este valor es expulsada por una limpieza en segundo plano (las sesiones con una solicitud en curso están exentas, por lo que una llamada larga a crawl_site nunca se cierra a mitad de solicitud). Limita el crecimiento del mapa de sesiones de clientes eliminados a mitad de turno, que nunca activan transport.onclose.

HTTP_MAX_SESSIONS

256

Solo transporte HTTP. Límite máximo de respaldo: si el mapa de sesiones alguna vez supera esto, se expulsa la sesión inactiva usada menos recientemente, independientemente del tiempo de espera de inactividad.

NATS_USER

(unset)

Nombre de usuario de NATS para autenticación de nombre de usuario/contraseña con bcrypt, utilizado junto con NATS_PASSWORD. Se ignora si NATS_CREDS también está establecido (la autenticación JWT de archivo de credenciales tiene prioridad).

NATS_PASSWORD

(unset)

Contraseña de NATS: consulte NATS_USER.

Instalación

npm (recomendado)

npm install -g @tadmstr/searxng-mcp

O ejecútalo directamente con npx:

npx @tadmstr/searxng-mcp

Desde el código fuente

git clone https://github.com/TadMSTR/searxng-mcp.git
cd searxng-mcp
pnpm install
pnpm build

Salida: build/src/index.js

Configuración del cliente MCP

Claude Code (CLI)

El enfoque recomendado usa claude mcp add-json para registrar el servidor con soporte completo de variables de entorno:

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"
  }
}'

Esto escribe en ~/.claude.json. No agregues searxng a ~/.claude/settings.json — ese archivo no se usa para la inyección de variables de entorno MCP en 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

URLs de GitHub

Las URLs de GitHub se manejan de forma nativa sin Firecrawl. githubFetch despacha según el nombre de host:

  • Raíz del repositorio (github.com/owner/repo) — obtiene el README mediante la API de GitHub

  • Blob de archivo (github.com/owner/repo/blob/branch/path/to/file) — reescribe y obtiene el contenido sin procesar de raw.githubusercontent.com

  • Archivo sin procesar (raw.githubusercontent.com/...) — se obtiene directamente tal cual

  • API (api.github.com/...) — respuesta decodificada (campos content en base64) o impresa de forma legible como JSON

Las URLs directas de raw.githubusercontent.com y api.github.com anteriormente coincidían solo con github.com y caían en la cascada de niveles de extracción de HTML, que no puede renderizar un archivo de texto sin procesar ni una respuesta JSON simple — fallaban el 100% de las veces. Ahora toman la ruta rápida de GitHub.

Las solicitudes no autenticadas están limitadas a 60/hora. Establece GITHUB_TOKEN para elevarlo a 5,000/hora.

Seguridad

Seguridad de URLs (SSRF)

Cada solicitud saliente a una URL influenciada por el llamador o descubierta — el nivel de HTTP sin procesar, las sondas de robots.txt / llms.txt / Wayback / sitemap, la obtención de enlaces del rastreo BFS y la ruta rápida de GitHub — está protegida de dos maneras:

  1. Verificación de cadena (assertPublicUrl) — rechaza URLs que no sean HTTP(S) y literales de IP privadas/internas: RFC1918 (10.x, 192.168.x, 172.16–31.x), loopback (127.x, ::1), enlace local / metadatos de nube (169.254.x), CGNAT (100.64/10), IPv6 ULA (fc00::/7) y enlace local (fe80::/10), IPv4 mapeado, y rangos multicast/reservados.

  2. Validación DNS en el momento de la conexión — un despachador undici compartido cuyo connect.lookup valida la dirección resuelta (la exacta a la que se conecta el socket). Esto cierra la brecha de re-vinculación DNS / TOCTOU donde un nombre de host público se resuelve a una dirección privada, y se vuelve a ejecutar en cada salto de redirección, por lo que una cadena de redirecciones no puede rebotar hacia tu red interna.

Firecrawl (nivel 1) y Crawl4AI (nivel 2) resuelven y obtienen la URL objetivo por sí mismos, por lo que el despachador de tiempo de conexión anterior no puede cubrirlos. fetchPage y crawlSite llaman a assertResolvedPublic(url) — una resolución de nombre de host única que rechaza cualquier resultado privado/reservado — inmediatamente antes de enviar a cualquiera de los servicios, cerrando el caso común de re-vinculación DNS en esa ruta (ventana TOCTOU más estrecha que la protección de tiempo de conexión, ya que el servicio vuelve a resolver).

Los servicios internos configurados (Firecrawl, Crawl4AI, SearXNG, Ollama, Reranker) se alcanzan mediante sus propias URLs y no están protegidos intencionalmente.

Protección de redirecciones

Las solicitudes de HTTP sin procesar y de la ruta rápida de GitHub usan además redirect: "manual" y rechazan las respuestas 3xx directamente (el encabezado Location nunca se devuelve al llamador). Las sondas que siguen redirecciones (robots.txt, llms.txt, sitemap) están cubiertas por la validación DNS de tiempo de conexión anterior, que vuelve a verificar cada salto.

Exposición del transporte

stdio no tiene superficie de red. El transporte HTTP se vincula a 127.0.0.1 por defecto y no está autenticado en esa configuración; moverlo fuera del loopback sin establecer SEARXNG_MCP_AUTH_TOKEN expone todas las herramientas — incluyendo fetch_url de URL arbitraria y clear_cache destructivo — a cualquier cosa que pueda enrutar al puerto. Ver autenticación del transporte HTTP.

Auditoría de dependencias

CI ejecuta pnpm audit en cada push. El archivo de bloqueo (pnpm-lock.yaml) se confirma para compilaciones reproducibles y auditables.

Manejo de credenciales

El servidor no almacena ni registra credenciales. Las claves API (FIRECRAWL_API_KEY, GITHUB_TOKEN, CRAWL4AI_API_TOKEN) se leen de variables de entorno y se usan solo en solicitudes salientes a sus respectivos servicios.

Validación de entrada

Las variables de entorno se validan al inicio — RERANK_RECENCY_WEIGHT advierte sobre valores NaN, negativos o >1.0. Los parámetros numéricos de herramientas usan z.coerce.number() con restricciones de rango.

Contribuciones

Consulta CONTRIBUTING.md para instrucciones de configuración, convenciones de commits y el proceso de PR.

Pruebas de integración

Una suite de integración real de Valkey que cubre la concurrencia de dominio-BD está controlada por VALKEY_TEST_URL y se omite por completo cuando no está configurada, por lo que un pnpm test simple sigue funcionando sin Valkey presente:

VALKEY_TEST_URL=redis://:<password>@<host>:<port>/<scratch-db> pnpm test

Usa un índice de base de datos temporal — la suite escribe y elimina claves domain:* y se niega a ejecutarse contra el índice 0 o 1 como protección de seguridad.

Licencia

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