searxng-mcp
searxng-mcp
Ein MCP-Server für private Websuche über eine selbst gehostete SearXNG-Instanz. Ergebnisse werden von einem lokalen ML-Modell neu gerankt, Vollseiteninhalte werden über Firecrawl abgerufen, und eine optionale Ollama-Instanz bietet Query-Expansion und LLM-synthetisierte Zusammenfassungen.
Entwickelt für die Verwendung mit Claude Code und LibreChat-Agenten, die Websuche benötigen, ohne Suchanfragen an eine Drittanbieter-Such-API zu senden.
Erstellt mit Claude Code unter Verwendung des Multi-Agenten-Workflows von homelab-agent – derselben Plattform, die searxng-mcp in Produktion für KI-gestützte Recherche einsetzt.
Schnellstart
Eine laufende SearXNG-Instanz ist erforderlich. Ein Cache-Backend wird dringend empfohlen.
Minimaler Stack – Starten Sie ein Dragonfly/Valkey-Cache-Backend und führen Sie searxng-mcp aus:
docker compose -f docker-compose.example.yml up -d
SEARXNG_URL=http://localhost:8081 CACHE_URL=redis://localhost:6381 npx @tadmstr/searxng-mcpFür eine vollständige lokale Topologie einschließlich Firecrawl, Crawl4AI, Ollama, Kiwix, dem Adblock-Proxy und NATS siehe docker-compose.full.yml.
Related MCP server: searxng-mcp-bridge
Werkzeuge
Werkzeug | Beschreibung | Wichtige Parameter |
| Suche über SearXNG mit lokalem Re-Ranking. Ruft einen breiteren Ergebnispool ab, rankt nach Relevanz neu und gibt die Top N zurück. SearXNGs native direkte Antworten, Infoboxen, Rechtschreibkorrekturen und verwandte Vorschläge werden oberhalb der Liste und in |
|
| Suche, Re-Ranking, dann Abruf des vollständigen Inhalts der Top-Ergebnisse über die Fetch-Kaskade (Firecrawl → Crawl4AI → rohes HTTP). |
|
| Suche, Abruf der Top-Ergebnisse, dann Synthese einer Zusammenfassung mit Zitaten über Ollama ( |
|
| Abruf und Extraktion von lesbarem Markdown von jeder öffentlichen URL. GitHub-Hosts nehmen den GitHub-Schnellpfad; YouTube-Video-URLs geben das Transkript zurück und Reddit-Thread-URLs geben Beitrag + Kommentare zurück (beide opt-in über robots, siehe unten); alle anderen verwenden die Fetch-Kaskade (Firecrawl → Crawl4AI → rohes HTTP). Auf ein Token-Budget gekürzt (Standard ~8.000 Zeichen). |
|
| Durchsucht eine gesamte Website und gibt ein Manifest mit URL/Titel/Auszug für jede Seite zurück. Versucht zuerst Firecrawl-Crawl, fällt auf Sitemap-Parsing zurück, dann optional BFS. Vollständige Seiteninhalte werden in Valkey zwischengespeichert, sodass nachfolgende |
|
| Leert den Suchcache, den Fetch-Cache, den Crawl-Manifest-Cache oder alles. Nützlich bei der Recherche zu sich schnell ändernden Themen, bei denen zwischengespeicherte Ergebnisse veraltet sein können. |
|
| Schreibgeschützte Ansicht der Domain-Fähigkeitsdatenbank. Mit |
|
Parameter
category — general (Standard), news, it, science
time_range — day, week, month, year — begrenzt Ergebnisse nach Veröffentlichungsdatum. Weglassen für Ergebnisse aller Zeiten.
fetch_count — Anzahl der Top-Reranked-Ergebnisse, für die der vollständige Inhalt abgerufen wird (Standard 1, max 3 für search_and_fetch; Standard 3, max 5 für search_and_summarize).
domain_profile — wendet ein benanntes Domain-Filterprofil an: homelab (zeigt selbst gehostete/Linux-Dokumente) oder dev (zeigt Stack Overflow, MDN, npm). Weglassen für Standardfilter.
expand — wenn true, wird die Abfrage vor der Suche über Ollama (OLLAMA_EXPAND_MODEL) umgeschrieben, um die Trefferquote zu verbessern. Erfordert OLLAMA_URL. Standardmäßig der Wert der Umgebungsvariable EXPAND_QUERIES.
language — BCP-47-Sprachcode (z. B. en, de) oder all, um auf eine bestimmte Sprache zu beschränken. Weglassen, um die Standardeinstellung der SearXNG-Instanz zu verwenden. Verfügbar bei search, search_and_fetch und search_and_summarize.
engines — durch Kommas getrennte SearXNG-Engine-Namen, um die Suche einzuschränken (z. B. google,duckduckgo). Wird unverändert weitergegeben; unbekannte/deaktivierte Engines führen zu weniger Ergebnissen statt zu Fehlern. Verfügbar bei allen drei Suchwerkzeugen.
site — beschränkt Ergebnisse auf eine Domain oder eine Liste (z. B. github.com oder ["github.com", "gitlab.com"]). Wird nach bestem Wissen als site:-Query-Operator angewendet – die meisten Engines (Google, Bing, DDG, Brave) beachten ihn, einige ignorieren ihn. Verfügbar bei allen drei Suchwerkzeugen.
max_tokens (fetch_url) — ungefähres Token-Budget für zurückgegebenen Inhalt (Zeichen ≈ Token × 4). Weglassen für das Standardbudget von ~2.000 Token / 8.000 Zeichen; max 10.000 Token.
target_selector (fetch_url) — CSS-Selektor, um die Extraktion auf ein bestimmtes Element zu beschränken (z. B. article, main .content). Wird nativ von Firecrawl/Crawl4AI unterstützt und clientseitig auf der rohen HTTP-Ebene angewendet; wird von Schnellpfaden ignoriert und wenn er nichts findet.
wait_for_selector (fetch_url) — CSS-Selektor, auf den vor der Extraktion gewartet wird, für JS-gerenderte Seiten. Wird von den Rendering-Ebenen (Firecrawl/Crawl4AI) unterstützt; wird bei rohem HTTP ignoriert (kein JS).
Architektur
MCP client (stdio)
│
▼
searxng-mcp ──────────────→ cache ($CACHE_URL) → result cache (search 1h, fetch 24h, crawl 6h)
│
├── expand (optional) → Ollama ($OLLAMA_URL) → rewritten query (qwen3:4b)
├── search ───────────→ SearXNG ($SEARXNG_URL) → raw results
├── rerank ───────────→ Reranker ($RERANKER_URL) → ranked results
│ (fallback: SearXNG order if reranker unavailable)
├── fetch content ────┬→ GitHub API (github.com) → markdown
│ ├→ Kiwix ($KIWIX_URL) → ZIM content (Wikipedia/SO/Arch Wiki, fast path)
│ ├→ Hister ($HISTER_URL) → browsing-history index (login-walled/JS-heavy fast path)
│ ├→ Firecrawl ($FIRECRAWL_URL) → page markdown (tier 1)
│ ├→ Crawl4AI ($CRAWL4AI_URL) → page markdown (tier 2, optional; via $ADBLOCK_PROXY_URL if set)
│ ├→ Raw HTTP + Readability → page markdown (tier 3 fallback; via $ADBLOCK_PROXY_URL if set)
│ └→ Wayback Machine (opt-in) → archived page markdown (tier 4, $WAYBACK_ENABLED)
├── crawl_site ───────┬→ Firecrawl crawl → page manifest (phase 1)
│ ├→ Sitemap parsing → page manifest (phase 2 fallback, fast-xml-parser)
│ └→ BFS crawl (opt-in) → page manifest (phase 3, $CRAWL_BFS_ENABLED)
└── summarize (opt.) → Ollama ($OLLAMA_URL) → synthesized summary ($OLLAMA_SUMMARIZE_MODEL)flowchart TD
entry["fetchPage(url)"]
cache{"Valkey cache hit?"}
cached["→ return cached { title, url, text }"]
github{"GitHub host?\ngithub.com · raw · api"}
gh_fetch["GitHub API / raw.githubusercontent.com / api.github.com\n→ return"]
llms{"llms.txt domain?"}
llms_fetch["Probe /llms-full.txt\nextract matching section\n→ return"]
kiwix{"Kiwix host?\nKIWIX_URL set"}
kiwix_fetch["Local Kiwix ZIM\nWikipedia · Stack Overflow · Arch Wiki\n→ cache + return"]
pdf{".pdf URL?"}
robots["robots.txt pre-check — tiers 1–3\ndisallowed → RobotsDisallowedError (cached 24h)"]
tier_skip(["Per-domain tier skip\nsuccess rate <30% over ≥10 tries\nor tier_skip operator override"])
t1["Tier 1 — Firecrawl\n$FIRECRAWL_URL"]
t2["Tier 2 — Crawl4AI\n$CRAWL4AI_URL · optional\nadblock proxy if $ADBLOCK_PROXY_URL"]
t3["Tier 3 — Raw HTTP + Readability\nfallback: raw HTML slice\nadblock proxy if $ADBLOCK_PROXY_URL"]
t4["Tier 4 — Wayback Machine CDX API\narchived snapshot · WAYBACK_ENABLED=true"]
post["Post-extraction\nJSON-LD Article · title cascade\nog:title → twitter:title → title → h1 → URL"]
result["→ return { title, url, text }"]
entry --> cache
cache -->|hit| cached
cache -->|miss| github
github -->|yes| gh_fetch
github -->|no| llms
llms -->|yes| llms_fetch
llms -->|no| kiwix
kiwix -->|yes| kiwix_fetch
kiwix -->|no| pdf
pdf -->|"yes — skip tier 1"| t2
pdf -->|no| robots
robots --> tier_skip
tier_skip --> t1
t1 -->|success| post
t1 -->|"empty / error"| t2
t2 -->|success| post
t2 -->|"empty / error"| t3
t3 -->|success| post
t3 -->|"empty / error"| t4
t4 -->|success| result
post --> result
style entry fill:#ffffff,stroke:#333333,color:#000000
style cache fill:#ffffff,stroke:#333333,color:#000000
style cached fill:#ffffff,stroke:#333333,color:#000000
style github fill:#dae8fc,stroke:#6c8ebf,color:#000000
style gh_fetch fill:#dae8fc,stroke:#6c8ebf,color:#000000
style llms fill:#dae8fc,stroke:#6c8ebf,color:#000000
style llms_fetch fill:#dae8fc,stroke:#6c8ebf,color:#000000
style kiwix fill:#fff9c4,stroke:#b8860b,color:#000000
style kiwix_fetch fill:#fff9c4,stroke:#b8860b,color:#000000
style pdf fill:#ffffff,stroke:#333333,color:#000000
style robots fill:#ffffff,stroke:#333333,color:#000000
style tier_skip fill:#f5f5f5,stroke:#666666,color:#000000
style t1 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
style t2 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
style t3 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
style t4 fill:#f8cecc,stroke:#a03030,color:#000000
style post fill:#e1d5e7,stroke:#7a5a8a,color:#000000
style result fill:#ffffff,stroke:#333333,color:#000000SearXNG und Firecrawl sind erforderlich. Crawl4AI, Valkey, Ollama, Kiwix und der Reranker sind optional – der Server degradiert elegant, wenn eine dieser Komponenten nicht verfügbar ist.
Werbeblockierung
searxng-mcp verwendet zwei unabhängige Werbeblocker-Seitencar, einen pro Fetch-Ebenen-Gruppe:
Sidecar | Ebene | Mechanismus |
| Ebene 1 (Firecrawl) | CDP-Ebene-Interception – vollständige HTTPS-Filterung, gleicher Browserprozess |
| Ebenen 2+3 (Crawl4AI, roher Fetch) | HTTP-Forward-Proxy – filtert reine HTTP-Werbedomains |
Ebene 1 – Puppeteer-Adblock
Der von Firecrawl verwendete firecrawl-puppeteer-Dienst läuft mit einem benutzerdefinierten Image (docker/puppeteer-adblock/), das @ghostery/adblocker-puppeteer über das Upstream-trieve/puppeteer-service-ts legt. EasyList + EasyPrivacy werden beim Start geladen und alle 168 Stunden aktualisiert; der Blocker wird auf jede Seite angewendet, die Firecrawl erstellt. Beschleunigt das Abrufen werbelastiger Websites und verkleinert die gerenderte DOM-Größe.
Umgebungsvariablen:
Var | Standard | Beschreibung |
| nicht gesetzt | Auf |
| EasyList + EasyPrivacy | Durch Kommas getrennte Liste von Filterlisten-URLs. |
|
| Kadenz, mit der der Blocker aus den konfigurierten URLs neu aufbaut. |
Das Basis-Image ist per SHA256-Digest gepinnt. Um eine Änderung bereitzustellen, den Dienst neu bauen und neu starten:
docker compose -f ~/docker/firecrawl-simple/docker-compose.yml up -d --build firecrawl-puppeteerPer-Domain-Umgehung: domains.json reserviert einen adblock_skip-Slot für zukünftige Operator-Overrides. Die Verdrahtung ist noch nicht implementiert – sie würde erfordern, dass Firecrawl einen benutzerdefinierten Header an den puppeteer-service weiterleitet, was nicht Teil seiner aktuellen API ist. Wird als Scope-Creep-Punkt I verfolgt.
Ebenen 2+3 – Adblock-Proxy
Setzen Sie ADBLOCK_PROXY_URL (z. B. http://adblock-proxy:8118), um Crawl4AI- und rohe Node-Fetch-Anfragen durch einen HTTP-Forward-Proxy zu leiten, der Werbe- und Tracker-Anfragen filtert. HTTPS-CONNECT-Tunnel werden unverändert durchgereicht – kein MITM, daher gilt die Filterung nur für reine HTTP-Werbedomains. Der Ebene-1-Puppeteer-Hook übernimmt bereits die vollständige HTTPS-Filterung für diese Ebene; der Proxy deckt ab, was auf den Ebenen 2 und 3 durchsickert.
Siehe docker/adblock-proxy/ für die Dienstdefinition, Konfigurationsoptionen und Bereitstellungsanweisungen (in docker-compose.full.yml enthalten).
Datengetriebenes Ebenen-Routing
Vor dem Aufruf der Fetch-Kaskade liest searxng-mcp die tier_stats_30d der Domain (siehe Domain-Fähigkeitsdatenbank) und überspringt jede Ebene mit einer Erfolgsrate unter 30 % bei mindestens 10 Versuchen. Cold-Start-Domains (<10 Versuche) behalten die Standardkaskade. Jedes Überspringen sendet ein searxng.fetch.tier.skipped-NATS-Ereignis mit reason: low_success_rate und erhöht searxng_fetch_total{outcome=skipped}.
Operator-Override. Fügen Sie eine tier_skip-Map zu domains.json hinzu, um Ebenen unabhängig von Statistiken zu überspringen:
{
"tier_skip": {
"example-bot-blocked.com": ["tier1"],
"another-site.example": ["tier1", "tier2"]
}
}tier_skip-Schlüssel können nackte Domains sein (example.com passt auf die Domain und alle Subdomains) oder Domain + Pfad-Präfix (example.com/api/). Die Datei wird heiß neu geladen – kein Neustart erforderlich. Manuelle Overrides senden reason: operator_override.
Schnellpfad für Inhaltstypen
Eine URL, die strukturierte, nicht-HTML-Inhalte ausliefert — application/json, jedes *+json, XML, YAML, TOML, CSV oder text/plain — wird über einen HEAD-Probe erkannt und direkt an die Raw-HTTP-Ebene weitergeleitet, anstatt die vollständige Firecrawl/Crawl4AI-Kaskade zu durchlaufen. JSON wird hübsch formatiert innerhalb eines Codeblocks zurückgegeben. Bisher führte die Aufforderung an einen Headless-Browser, eine JSON-API-Antwort oder ein CDN-Asset zu rendern, zu leerem Markdown, sodass API- und CDN-Endpunkte (registry.npmjs.org, api.osv.dev, cdn.jsdelivr.net, …) einfach fehlschlugen.
Garantien:
Der Probe ist fail-open. Ein nicht erreichbarer Host, ein Server, der
HEADverweigert, oder ein nicht lesbarer/nicht parsebarerContent-Type-Header fallen alle unverändert auf die normale Kaskade zurück.application/xhtml+xmlist bewusst ausgeschlossen — das ist Markup für einen Browser, keine strukturierten Daten.HTML, das ein Server fälschlicherweise als
text/plainkennzeichnet, wird weiterhin als HTML geparst und nicht als roher Textblock ausgegeben.
Domain-Fähigkeitsdatenbank
Jeder Abruf zeichnet auf, was searxng-mcp über die Zieldomain lernt, in Valkey unter domain:<hostname> (90-Tage-TTL, schema_version 5). Pro Datensatz erfasst:
tier_stats_30d.{tier1,tier2,tier3,tier4,github}.{attempts, ok, fail, last_fail_reason, window_start_ms}— Abruferfolgsrate pro Ebene über ein rollierendes 30-Tage-Fenster. Der Cutoff wird beim Lesen angewendet, gemeinsam für Tier-Routing-Entscheidungen unddomain_stats-Berichte, sodass die beiden nicht widersprechen können — eine Domain, die einmal abgerufen und dann inaktiv gelassen wird, meldet ein wirklich leeres Fenster, anstatt dass veraltete Zahlen bis zum nächsten Schreibvorgang überleben. Dertier4-Slot (Wayback Machine) wird nur aufgezeichnet, wennWAYBACK_ENABLED=trueist. Dergithub-Slot zeichnet den GitHub-Schnellpfad auf (raw.githubusercontent.com/api.github.com/github.comREADME-Abrufe), der die Tier-Kaskade umgeht, aber hier trotzdem verfolgt wird. Einschema_version-Bump baut vorhandene Datensätze frisch auf — akkumulierte Fenster für derzeit inaktive Domains werden verworfen (präzedenzlos über die 1→2-, 2→3-, 3→4-, 4→5-Bumps).capabilities.metadata_fetch.{attempts, ok, fail, last_fail_reason}— Erfolg/Fehlschlag des Metadaten-Seitenkanal-Abrufs (fetchRawHtmlForMetadata, verwendet für JSON-LD/og:title-Stichproben). Getrennt vontier_stats_30dverfolgt, da es die Frage beantwortet „Ist diese Domain überhaupt erreichbar", nicht „War die Volltext-Zustellung erfolgreich".capabilities.seen_in_search.{count, last_seen_ms}— wie oft die Domain insearch-Ergebnissen erscheint. Wird vonsearxSearch()auf jedem Rückgabepfad (einschließlich Cache-Treffern) ohne durchgeführten Abruf fire-and-forget geschrieben, sodass eine Domain verfolgt werden kann, bevor sie jemals abgerufen wurde.capabilities.robots_txt.{present, fetched, allows_us}— Vorhandensein von robots.txt und ob sie uns erlaubtcapabilities.llms_full_txt.{present, size_bytes, last_checked}— ob die Domain/llms-full.txtausliefertcapabilities.json_ld_article.{sampled, present, last_sampled_at}— ob die Seite überhaupt Article-Schema-JSON-LD trägt (Schema.orgArticle/NewsArticle/BlogPosting/TechArticleund Untertypen wieScholarlyArticle/OpinionNewsArticle/LiveBlogPosting, abgeglichen nach bloßem Namen oder voll qualifiziertemhttps://schema.org/...@type), unabhängig davon, ob dieses Schema extrahierbaren Fließtext hatte — viele Websites veröffentlichen Headline/Metadaten-JSON-LD ohnearticleBody, was ein anderes Anliegen ist als die Post-Extraktion, die es tatsächlich verwendet.capabilities.og_title.{sampled, present, last_sampled_at}— dasselbe für<meta property="og:title">preferred_strategy— derzeit aufllms_full_txtgesetzt, wenn ein vorhandener Probe landet; zukünftige Phasen werden dies verwenden, um die Tier-Kaskade zu überspringen
Untersuchen Sie einen Datensatz mit dem gebündelten CLI oder fragen Sie ihn von einem Agenten über das domain_stats-Tool ab (einzelne Domain oder aggregiert; siehe Tools):
pnpm dump-domain docs.anthropic.comdump-domain unterscheidet ein abgelaufenes Fenster von einer Ebene, die überhaupt keine Daten hat, anstatt beide gleich darzustellen.
Gleichzeitige Aktualisierungen für denselben Hostnamen (die Tier-Versuchs-, Robots-Probe- und Post-Extract-Sample-Recorder, die während eines Abrufs parallel feuern) werden über einen serverseitigen Lua-Compare-and-Set serialisiert, gepaart mit einer prozessinternen Pro-Key-Warteschlange, die die Konkurrenz zwischen den eigenen Schreibern eines einzelnen Prozesses entfernt, sodass das CAS nur wirklich gleichzeitige Schreibvorgänge über Prozesse hinweg schlichten muss. Versionen vor v3.17.0 verwendeten ein WATCH/MULTI/EXEC-Read-Modify-Write gegen eine gemeinsame Verbindung, was gleichzeitige Schreiber tatsächlich nicht serialisiert — Daten, die vor v3.17.0 gesammelt wurden, waren dadurch erheblich unvollständig. Ein Upgrade verwirft vorhandene Tier-Statistiken über den Schema-Bump; erwarten Sie, dass domain_stats unmittelbar nach dem Upgrade fast leer liest und sich in den folgenden Tagen wieder auffüllt.
Domain-DB-Persistenz
Die Domain-DB lebt nur in Valkey unter einer 90-Tage-TTL und 30-Tage-Rolling-Fenstern, sodass ein Cache-Flush oder TTL-Ablauf Fähigkeitslernen löscht, dessen Wiederbeschaffung teuer ist. Zwei CLIs machen sie dauerhaft:
pnpm domain-db-maintenance # SCAN all domain:* records → write a dated JSON snapshot (+ prune) and emit OTel gauges
pnpm restore-domain-db # re-seed the domain-db from the newest snapshot after a flushdomain-db-maintenanceist ein eigenständiger Job (führen Sie ihn planmäßig über Cron oder einen PM2-Cron-Restart aus — nicht als In-Process-Timer, da searxng-mcp als mehrere gleichzeitige Per-Agent-stdio-Kinder läuft, die ihn jeweils auslösen würden). Ein begrenzterSCANspeist beide Ausgaben: einen dauerhaften datierten Snapshot und, wennOTEL_EXPORTER_OTLP_ENDPOINTgesetzt ist, Gauges (searxng_domains_tracked,searxng_domains_failing,searxng_domain_tier_success_ratio{tier}), die vor dem Beenden erzwungen geflusht werden.restore-domain-dbseedet nur Schlüssel neu, die fehlen oder deren Live-Datensatz strikt älter als der Snapshot ist (vergleichtlast_fetch) — es überschreibt niemals einen frischeren oder gleichwertigen Live-Datensatz, sodass es sicher gegen ein laufendes, teilweise befülltes Valkey ausgeführt werden kann (z. B. in einer Dienst-Startsequenz zur automatischen Flush-Wiederherstellung).
Env-Variable | Standard | Zweck |
|
| Wo datierte Snapshots geschrieben/gelesen werden. In der Bereitstellung auf einen dauerhaften Pfad (Appdata oder NFS-Mount) setzen. |
|
| Wie viele Snapshots aufbewahrt werden; ältere werden bei jedem Wartungslauf entfernt. |
llms.txt-Schnellpfad
Für Whitelist-Dokumentationsdomains in domains.json (llms_txt-Array) versucht fetchPage zuerst <origin>/llms-full.txt und extrahiert den Abschnitt, der der angeforderten URL entspricht, bevor eine Ebene aufgerufen wird. Dies vermeidet das Ausführen von Puppeteer gegen gut instrumentierte Doku-Sites und liefert direkt einen sauberen Markdown-Abschnitt. Probe-Ergebnisse und der vollständige Text werden in Valkey zwischengespeichert (llms:<origin>:full, 24 h / 7 d für vorhanden/nicht vorhanden). Standard-Whitelist: docs.anthropic.com, docs.openai.com, docs.stripe.com, docs.crawl4ai.com, docs.firecrawl.dev, docs.cursor.com. Erweitern Sie durch Bearbeiten von domains.json — die Datei wird heiß neu geladen.
Kiwix-Schnellpfad
Wenn KIWIX_URL gesetzt ist, werden Abrufanfragen für bekannte offline-fähige Hosts
vor der Firecrawl/Crawl4AI-Kaskade abgefangen und aus dem lokalen Kiwix-ZIM-Archiv bedient. Dies eliminiert die 100%ige Tier-1-Fehlerrate für Sites wie Wikipedia (die Headless-Scraper blockiert) und liefert sauberen lesbaren Inhalt mit null externem Netzwerkverkehr.
Unterstützte Hosts und ZIM-Bücher (kiwix-serve muss mit --nodatealiases / -z laufen):
Host | ZIM-Buch |
|
|
|
|
|
|
Der Kiwix-Pfad läuft nach dem llms-txt-Schnellpfad und vor dem Robots-Gate. Wenn die Kiwix-Anfrage fehlschlägt oder leer zurückkommt, läuft die vollständige Tier-Kaskade wie gewohnt. Wenn KIWIX_URL nicht gesetzt ist, fügt die Funktion null Overhead hinzu — isKiwixHost() gibt sofort false zurück.
Setzen Sie KIWIX_URL auf Ihre kiwix-serve-Basis-URL (z. B. http://localhost:8292).
YouTube- und Reddit-Schnellpfade
fetch_url erkennt YouTube-Video-URLs (youtube.com, youtu.be) und Reddit-Thread-URLs und kann sie direkt bedienen, anstatt die gerenderte Seite zu scrapen:
YouTube — extrahiert die Untertitelspur des Videos aus der Watch-Seite und gibt das Transkript zurück. Aktiviert durch
YOUTUBE_TRANSCRIPT_ENABLED(Standard: an).Reddit — ruft die öffentliche
.json-Ansicht ab und gibt den Beitrag plus Top-Kommentare in der Standardform{title, url, text}zurück; fällt bei HTTP 429 durch. Aktiviert durchREDDIT_FASTPATH_ENABLED(Standard: an).
Beide verlassen sich auf inoffizielle, undokumentierte Endpunkte (YouTubes timedtext-API, Reddits .json) — Best-Effort ohne SLA; jeder kann bei einer Upstream-Änderung brechen, daher die Kill-Switches. Bei jedem Fehlschlag fällt die Anfrage auf die normale Tier-Kaskade zurück (die weiterhin Titel/Beschreibung einer YouTube-Seite erhalten kann).
robots.txt: beide Endpunkte sind durch die robots.txt der Sites nicht erlaubt (Reddit verbietet alles; YouTube verbietet /api/, wo das Transkript liegt). Standardmäßig respektieren diese Schnellpfade das und bleiben inaktiv, fallen auf die Kaskade zurück. Auf Ihrer eigenen Instanz können Sie sich mit YOUTUBE_IGNORE_ROBOTS=true / REDDIT_IGNORE_ROBOTS=true für direkte Abrufe entscheiden.
Site-Crawling
crawl_site crawlt eine gesamte Site und gibt ein Manifest mit URL/Titel/Snippet für jede gefundene Seite zurück. Es verwendet eine dreiphasige Strategiekaskade:
Firecrawl-Crawl — sendet einen Crawl-Job an Firecrawl (
/crawl-Endpunkt), pollt bis zum Abschluss und gibt die vollständige Seitenliste zurück. Gesteuert durchFIRECRAWL_CRAWL_POLL_INTERVAL_MSundFIRECRAWL_CRAWL_MAX_WAIT_MS.Sitemap-Parsing — wenn Firecrawl fehlschlägt oder leer zurückgibt, ruft es
/sitemap.xml(und verknüpfte Sitemaps) ab und extrahiert URLs mit Titeln/Snippets. Verwendetfast-xml-parserfür das Sitemap-XML-Parsing.BFS-Crawl (opt-in) — wenn auch das Sitemap-Parsing fehlschlägt, führt es einen Breitensuche-Crawl ab der gegebenen URL bis zu
CRAWL_BFS_MAX_DEPTHLink-Hops durch. Läuft nur, wennCRAWL_BFS_ENABLED=trueoder derbfs-Toolparametertrueist.
Vollständiger Seiteninhalt, der während des Crawls abgerufen wird, wird in Valkey zwischengespeichert (TTL: CRAWL_MANIFEST_TTL_SECONDS, Standard 6 Stunden). Nachfolgende fetch_url-Aufrufe für jede URL im Manifest geben sofort aus dem Cache zurück — null Abruf-Overhead für Folge-Lesevorgänge.
Der Manifest-Cache kann mit clear_cache(target="crawl") geleert werden.
Wayback-Machine-Fallback
Wenn WAYBACK_ENABLED=true ist, fragt eine vierte Ebene die Wayback-Machine-CDX-API nach einem archivierten Snapshot ab, wenn alle drei Hauptebenen fehlschlagen. Der zurückgegebene Inhalt wird mit einem Provenienz-Header ([Archived snapshot – <timestamp> – <original_url>]) versehen, damit Aufrufer wissen, dass der Inhalt möglicherweise nicht den aktuellen Seitenzustand widerspiegelt.
Abrufqualität
Nachdem eine Ebene Inhalte mit rohem HTML zurückgegeben hat, verbessert ein Post-Extraktions-Pass Titel- und Textqualität:
JSON-LD-Article-Extraktion — Schema.org
Article/NewsArticle/BlogPosting/TechArticle-Blöcke liefern sauberereheadlineundarticleBodyals Tier-1-Chrome-Scraping (größenbegrenzt auf 1 MB pro Script-Tag).Titel-Kaskade — fällt zurück über
og:title→twitter:title→<title>(mit Publisher-Suffix-Entfernung) → erstes<h1>→ URL.Tier-2-Readability-Vergleich — wenn Crawl4AI Markdown zurückgibt, läuft JSDOM+Readability auch über sein rohes HTML und wird bevorzugt, wenn sein Text länger ist (oder bedingungslos, wenn Crawl4AI weniger als 500 Zeichen zurückgibt).
Resilienz
Der Cache hängt nie eine Suche auf. Der Valkey-Client ist durch
CACHE_COMMAND_TIMEOUT_MS/CACHE_CONNECT_TIMEOUT_MS/CACHE_MAX_RETRIES_PER_REQUESTbegrenzt (siehe Konfiguration). Ein blockierter oder CPU-überlasteter Cache-Backend lehnt den Befehl jetzt ab, anstatt für immer zu hängen – die bestehende Fail-Soft-Behandlung stuft diese Ablehnung zu einem Cache-Miss (Live-Bedienung) herab, anstatt einen Fehler zu werfen. Cache-Verbindungsfehler, Client-Fehler und Befehlsfehler erzeugen eine gedrosselte[searxng-mcp]-stderr-Zeile (pro Schlüssel dedupliziert, sodass ein anhaltender Ausfall einen periodischen Brotkrumen hinterlässt, keine Flut) – stderr ist die einzige Telemetrie-Senke, die im bereitgestellten PM2-Prozess verdrahtet ist.Prozessabsturz-Handler –
uncaughtExceptionprotokolliert und beendet mit Exit-Code 1 (sauberer PM2-Neustart);unhandledRejectionprotokolliert und fährt fort, anstatt den gemeinsamen Prozess still zu crashen.Warnungen zur Graceful-Degradation – der Reranker-Fallback und die Ollama/LLM-Expand- und -Summarize-Fallbacks geben jeweils eine gedrosselte stderr-Zeile aus, wenn sie die Qualität stillschweigend verschlechtern (Reranker nicht verfügbar, LLM-Backend nicht erreichbar).
Die Version stammt aus einer einzigen Quelle – zur Laufzeit aus
package.json(src/version.ts) – dieMcpServer-Version, die OTel-Tracer/Meter-Version und der ausgehendeUSER_AGENTfolgen alle dieser Version, sodass sie nicht unabhängig voneinander abweichen können.
Beobachtbarkeit (opt-in)
Tracing, Metriken und Ereignisveröffentlichung sind vollständig opt-in – wenn keine der unten genannten Umgebungsvariablen gesetzt ist, hat der Server keinerlei Beobachtbarkeits-Overhead und lädt die OpenTelemetry- oder NATS-Pakete zur Laufzeit nie.
OpenTelemetry (Traces + Metriken) – setzen Sie OTEL_EXPORTER_OTLP_ENDPOINT auf den HTTP-Endpunkt Ihres Collectors und der Server emittiert:
Spans (pro Anfrage):
tool.<name>→expand_query? →searxng_request→rerank→fetch(×N) →tier1_firecrawl|tier2_crawl4ai|tier3_rawfetch→post_extract; plussummarize_llmfürsearch_and_summarize.Zähler:
searxng_search_total{profile, expand},searxng_fetch_total{tier, outcome},searxng_cache_total{namespace, outcome},searxng_errors_total{stage, error_type}.Histogramme:
searxng_search_duration_seconds{profile},searxng_fetch_duration_seconds{tier, outcome}.
Standard-OTEL-Umgebungsvariablen gelten (OTEL_SERVICE_NAME standardmäßig searxng-mcp).
NATS-Ereignisse – setzen Sie NATS_URL (z. B. nats://localhost:4222) und der Server veröffentlicht ein strukturiertes Ereignis bei jeder Suche, jedem Fetch, jedem Cache-Hit/Miss, jedem Robots-Skip und jedem Fehler. Authentifizierung über NATS_CREDS (eine JWT-Creds-Datei) oder NATS_USER/NATS_PASSWORD (bcrypt-Benutzername/Passwort) – Creds-Datei-Auth gewinnt, wenn beide gesetzt sind. Subjects:
Subject | Wann |
| Suchtool aufgerufen |
| Suche zurückgegeben (mit Quellen, Latenz, Rerank angewendet) |
|
|
| Eine Stufe gab leer zurück oder warf einen Fehler |
| robots.txt nicht erlaubt |
| Fetch aufgelöst (mit |
| Bei jedem Valkey-Lookup |
| Stufenmarkierte Fehler |
Jedes Envelope enthält request_id und (wenn OTel aktiviert ist) trace_id, sodass Abonnenten die beiden Streams verbinden können. Subject-Präfix über NATS_SUBJECT_PREFIX überschreibbar. Suchanfragen fließen durch search.*-Ereignisse – nachgelagerte Konsumenten sind für jegliches PII-Scrubbing verantwortlich.
Höflichkeit
Ehrlicher User-Agent – ausgehende Anfragen identifizieren sich als
searxng-mcp/<version> (+https://github.com/TadMSTR/searxng-mcp; persönliche Forschung).robots.txt-Konformität –
/robots.txtwird einmal pro Ursprung abgerufen und 24 Stunden in Valkey unterrobots:<origin>zwischengespeichert. Nicht erlaubte Pfade werden übersprungen, bevor irgendeine Stufe läuft, und alsskipped_robots url=… reason=…protokolliert.
Transport
stdio (Standard) – kompatibel mit Claude Code MCP-Plugin und LibreChat-stdio-Konfiguration.
HTTP – setzen Sie SEARXNG_MCP_TRANSPORT=http, um als gemeinsamer HTTP/SSE-Server zu laufen, der für Multi-Client-Bereitstellungen oder Docker-basierte Setups geeignet ist. Bindet an SEARXNG_MCP_HOST:SEARXNG_MCP_PORT (Standard 127.0.0.1:3001):
SEARXNG_MCP_TRANSPORT=http SEARXNG_MCP_PORT=3001 npx @tadmstr/searxng-mcpRegistrieren Sie sich mit Claude Code gegen einen HTTP-Server:
claude mcp add-json searxng --scope user '{
"type": "http",
"url": "http://localhost:3001/mcp"
}'Sitzungen werden über den Mcp-Session-Id-Header identifiziert, sodass mehrere Clients gleichzeitig mit demselben gemeinsamen Prozess verbunden sein können. Leerlauf-Sitzungen werden nach HTTP_SESSION_IDLE_TIMEOUT_MS entfernt und hart auf HTTP_MAX_SESSIONS begrenzt – siehe Konfiguration.
HTTP-Transport-Authentifizierung
Der HTTP-Transport ist standardmäßig unauthentifiziert, was nur sicher ist, weil er standardmäßig an 127.0.0.1 bindet. Wenn Sie SEARXNG_MCP_HOST auf etwas anderes ändern – einschließlich 0.0.0.0, was für den Betrieb in einem Container erforderlich ist – setzen Sie auch SEARXNG_MCP_AUTH_TOKEN:
SEARXNG_MCP_AUTH_TOKEN=$(openssl rand -hex 32)Wenn gesetzt, muss jede Anfrage außer GET /health das Token als RFC 6750-Bearer-Anmeldedaten tragen:
Authorization: Bearer <token>Alles andere – kein Header, ein anderes Schema, ein falsches Token – erhält 401 mit WWW-Authenticate: Bearer und einem JSON-RPC-Fehlertext. Die Antwort ist in allen drei Fällen identisch und gibt die präsentierten Anmeldedaten nie zurück. Tokens werden als SHA-256-Digests verglichen, sodass der Vergleich in konstanter Zeit erfolgt und keine Längeninformationen preisgibt.
Registrieren eines authentifizierten Servers mit Claude Code:
claude mcp add-json searxng --scope user '{
"type": "http",
"url": "http://localhost:3001/mcp",
"headers": {"Authorization": "Bearer <token>"}
}'Wenn die Variable nicht gesetzt ist, bleibt das bisherige Verhalten exakt erhalten, sodass stdio-Benutzer und bestehende Loopback-gebundene HTTP-Bereitstellungen keine Änderung benötigen. Es gibt kein Pro-Aufrufer-Autorisierungsmodell – ein einzelnes Token authentifiziert den Zugriff auf den Server, nicht eine bestimmte Client-Identität. Beim Start wird eine Warnung protokolliert, wenn eine Nicht-Loopback-Bindung ohne Token erfolgt.
GET /health ist bewusst von der Prüfung ausgenommen. Es ist der Container-Healthcheck und die Monitoring-Liveness-Probe, nimmt keine Eingaben entgegen und seine Antwort (status, cache, sessions) enthält keine Geheimnisse.
GET /health – unauthentifizierte Liveness-Probe, lokal an den MCP-Endpunkt gebunden. Pingt Valkey über das begrenzte Cache-Befehls-Timeout (damit die Prüfung selbst nie hängen kann) und gibt zurück:
{"status": "ok", "cache": "up", "sessions": 3}oder, wenn das Cache-Backend nicht erreichbar ist:
{"status": "degraded", "cache": "degraded", "sessions": 3}sessions ist die aktuelle HTTP-Sitzungsanzahl. Nützlich für Sysadmin-Überwachung, um einen degradierten Cache von der MCP-Seite aus zu erkennen, ohne das Cache-Backend direkt zu instrumentieren.
Voraussetzungen
Node.js 20+
pnpm (oder npm)
Eine laufende SearXNG-Instanz
Eine laufende Firecrawl-Instanz
Ein laufender Reranker, der einen Jina-kompatiblen
/v1/rerank-Endpunkt bereitstellt (optional)Eine laufende Valkey- oder Redis-kompatible Instanz (optional, für Ergebnis-Caching)
Eine laufende Ollama-Instanz mit
qwen3:4bund/oderqwen3:14b(optional, für Query-Expansion und Zusammenfassung)
SearXNG
SearXNG muss das JSON-Ausgabeformat aktiviert haben. In settings.yml:
search:
formats:
- html
- jsonReranker
Der Reranker muss einen Jina-kompatiblen /v1/rerank-Endpunkt bereitstellen. Ein leichtgewichtiger FlashRank-Wrapper funktioniert gut – siehe die docker/reranker/-Referenz in homelab-agent.
Firecrawl
Jede Firecrawl-kompatible Instanz funktioniert. Die lokale firecrawl-simple-Bereitstellung ist ausreichend. Setzen Sie FIRECRAWL_API_KEY, wenn Ihre Instanz Authentifizierung erfordert (Standard placeholder-local für lokale Bereitstellungen, die Auth überspringen).
Crawl4AI
Crawl4AI ist ein optionaler Fallback der zweiten Stufe, der verwendet wird, wenn Firecrawl leeren Inhalt zurückgibt (bot-blockierte Seiten, JS-lastige Websites). Setzen Sie CRAWL4AI_URL, um es zu aktivieren. Wenn nicht gesetzt, überspringt die Kaskade zu rohem HTTP-Fetch.
docker run -d -p 11235:11235 unclecode/crawl4ai:0.8.6Wenn Ihre Instanz API-Token-Authentifizierung erfordert, setzen Sie CRAWL4AI_API_TOKEN.
Auf dem search_and_summarize-Pfad verwenden Crawl4AI-Anfragen fit_markdown für rauschgefilterte Inhaltsextraktion. Andere Aufrufer (search_and_fetch, fetch_url) verwenden raw_markdown.
Kiwix (optional)
kiwix-serve dient ZIM-Archive über HTTP. Laden Sie die erforderlichen ZIM-Dateien herunter und führen Sie kiwix-serve mit --nodatealiases (-z) aus, damit Buchnamen stabil sind:
kiwix-serve --port 8292 --nodatealiases /path/to/zims/Erforderliche ZIM-Dateien für jeden unterstützten Host:
Wikipedia:
wikipedia_en_all_mini(odermaxi)Stack Overflow:
stackoverflow.com_en_allArch Wiki:
archlinux_en_all_maxi
ZIM-Dateien können von library.kiwix.org heruntergeladen werden.
Hister (optional)
Hister ist ein Browserverlauf-Index, der von einer Firefox-Erweiterung befüllt wird. Wenn HISTER_URL gesetzt ist, prüft fetchPage den Verlaufsindex, bevor die Stufenkaskade aufgerufen wird – nützlich für Login-geschützte und JS-lastige Seiten, bei denen Scraper fehlschlagen.
Setzen Sie HISTER_URL auf die Basis-URL Ihrer Hister-Instanz und HISTER_TOKEN, wenn Bearer-Token-Authentifizierung erforderlich ist.
Valkey / Redis
Jede Redis-kompatible Instanz. Valkey wird empfohlen. Suchergebnisse werden 1 Stunde lang zwischengespeichert; abgerufene Seiten 24 Stunden. Wenn nicht verfügbar, arbeitet der Server ohne Caching.
Ollama
Erforderlich für expand und search_and_summarize. Ziehen Sie die erforderlichen Modelle:
ollama pull qwen3:4b # query expansion
ollama pull qwen3:14b # summarizationDas think: false-Verhalten wird automatisch gehandhabt – keine zusätzliche Ollama-Konfiguration erforderlich.
Konfiguration
Alle Dienst-URLs sind über Umgebungsvariablen konfigurierbar.
Variable | Standard | Beschreibung |
|
| URL der SearXNG-Instanz |
|
| URL der Firecrawl-Instanz |
|
| URL der Reranker-Instanz |
|
| Firecrawl-API-Schlüssel (falls erforderlich) |
| (unset) | Persönlicher GitHub-Zugriffstoken – erhöht das Ratelimit von 60 auf 5.000 Anfragen/Stunde |
| (unset) | Basis-URL der Ollama-API – erforderlich für |
| (unset) | Bearer-Token für authentifizierte Ollama-Proxys – fügt bei Festlegung den Header |
|
| Modell, das für die Query-Erweiterung verwendet wird ( |
|
| Modell, das für |
| (unset) | OpenAI-kompatibler Chat-Endpunkt (z. B. vLLM, llama.cpp, LM Studio) für |
| (unset) | Modell-ID für das OpenAI-kompatible Backend; überschreibt |
| (unset) | Bearer-Token für das OpenAI-kompatible Backend – fügt bei Festlegung |
|
| Sendet |
|
| Redis-kompatible URL – aktiviert Ergebnis-Caching. Akzeptiert auch |
|
| Valkey-Befehlstimeout – ein blockiertes/CPU-spitzen Cache-Backend lehnt ab, anstatt zu hängen ( |
|
| Valkey-Verbindungstimeout. Gleiches Fallback-Verhalten wie |
|
| Maximale Wiederholungen pro Valkey-Befehl, bevor er abgelehnt wird. Gleiches Fallback-Verhalten wie |
|
| TTL des Suchergebnis-Caches in Sekunden |
|
| TTL des abgerufenen Seiten-Caches in Sekunden |
|
| TTL des Crawl-Manifests und des Seiteninhalts-Caches in Sekunden (6 Stunden) |
|
| Standardmäßige maximale Seitenanzahl, die von |
|
| Setzen Sie es auf |
|
| Maximale Link-Hop-Tiefe für BFS-Crawl |
|
| Abfrageintervall beim Warten auf den Abschluss eines Firecrawl-Crawl-Jobs |
|
| Maximale Wartezeit auf einen Firecrawl-Crawl-Job, bevor auf Sitemap zurückgegriffen wird |
|
| Setzen Sie es auf |
| (unset) | URL der Crawl4AI-Instanz – aktiviert den Fetch-Fallback der zweiten Stufe, wenn Firecrawl fehlschlägt |
| (unset) | Optionaler Bearer-Token für Crawl4AI-Instanzen mit API-Token-Schutz |
|
| Setzen Sie es auf |
| (unset) | HTTP-Proxy-URL für Adblocking der Stufe 2 (Crawl4AI) und Stufe 3 (roher Node-Fetch) – z. B. |
| (unset) | Basis-URL von kiwix-serve (z. B. |
| (unset) | Basis-URL des Hister-Browserverlauf-Indexes – aktiviert den Hister-Schnellpfad vor der Stufenkaskade für Login-geschützte und JS-lastige Seiten. Feature deaktiviert und ohne Overhead, wenn nicht gesetzt. |
| (unset) | Bearer-Token für die Hister-API-Authentifizierung. Erforderlich, wenn |
|
| Aktiviert den YouTube-Transkript-Schnellpfad in |
|
| Opt-in zum Abrufen von YouTube-Transkripten trotz des Verbots von |
|
| Aktiviert den Reddit- |
|
| Opt-in zum Abrufen von Reddit |
|
| Transportmodus: |
|
| HTTP-Listen-Port (nur HTTP-Transportmodus). |
|
| HTTP-Listen-Adresse (nur HTTP-Transportmodus). |
| (unset) | Nur HTTP-Transport. Wenn gesetzt, muss jede Anfrage außer |
|
| Nur HTTP-Transport. Eine Sitzung, die länger als diese Zeit inaktiv ist, wird durch einen Hintergrund-Sweep entfernt (Sitzungen mit laufender Anfrage sind ausgenommen, sodass ein langer |
|
| Nur HTTP-Transport. Harte Obergrenze als Sicherheitsnetz – wenn die Sitzungszuordnung diesen Wert überschreitet, wird die am längsten nicht verwendete inaktive Sitzung entfernt, unabhängig vom Inaktivitäts-Timeout. |
| (unset) | NATS-Benutzername für die bcrypt-Benutzername/Passwort-Authentifizierung, verwendet zusammen mit |
| (unset) | NATS-Passwort – siehe |
Installation
npm (empfohlen)
npm install -g @tadmstr/searxng-mcpOder direkt mit npx ausführen:
npx @tadmstr/searxng-mcpAus dem Quellcode
git clone https://github.com/TadMSTR/searxng-mcp.git
cd searxng-mcp
pnpm install
pnpm buildAusgabe: build/src/index.js
MCP-Client-Konfiguration
Claude Code (CLI)
Der empfohlene Ansatz verwendet claude mcp add-json, um den Server mit vollständiger Unterstützung für Umgebungsvariablen zu registrieren:
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"
}
}'Dies schreibt in ~/.claude.json. Fügen Sie searxng nicht zu ~/.claude/settings.json hinzu – diese Datei wird in Claude Code nicht für die Injektion von MCP-Umgebungsvariablen verwendet.
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"searxng": {
"command": "npx",
"args": ["-y", "@tadmstr/searxng-mcp"],
"env": {
"SEARXNG_URL": "http://localhost:8081",
"FIRECRAWL_URL": "http://localhost:3002",
"RERANKER_URL": "http://localhost:8787",
"OLLAMA_URL": "http://localhost:11434",
"CACHE_URL": "redis://localhost:6379",
"CRAWL4AI_URL": "http://localhost:11235"
}
}
}
}LibreChat (librechat.yaml)
mcpServers:
searxng:
type: stdio
command: node
args:
- /path/to/searxng-mcp/build/src/index.js
env:
SEARXNG_URL: http://localhost:8081
FIRECRAWL_URL: http://localhost:3002
RERANKER_URL: http://localhost:8787
OLLAMA_URL: http://localhost:11434
CACHE_URL: redis://localhost:6379
CRAWL4AI_URL: http://localhost:11235GitHub-URLs
GitHub-URLs werden nativ ohne Firecrawl verarbeitet. githubFetch verteilt anhand des Hostnamens:
Repo-Root (
github.com/owner/repo) — ruft die README über die GitHub-API ab.Datei-Blob (
github.com/owner/repo/blob/branch/path/to/file) — schreibt um und ruft Rohinhalt vonraw.githubusercontent.comab.Rohdatei (
raw.githubusercontent.com/...) — wird direkt unverändert abgerufen.API (
api.github.com/...) — Antwort dekodiert (base64-content-Felder) oder als JSON hübsch formatiert.
Direkte raw.githubusercontent.com- und api.github.com-URLs stimmten zuvor nur mit github.com überein und fielen in die HTML-Scraping-Stufenkaskade, die eine Rohtextdatei oder eine nackte JSON-Antwort nicht rendern kann – sie schlugen zu 100 % fehl. Sie nehmen jetzt den GitHub-Schnellpfad.
Nicht authentifizierte Anfragen sind auf 60/Stunde begrenzt. Setzen Sie GITHUB_TOKEN, um dies auf 5.000/Stunde zu erhöhen.
Sicherheit
URL-Sicherheit (SSRF)
Jeder ausgehende Abruf einer vom Aufrufer beeinflussten oder entdeckten URL – die Roh-HTTP-Ebene, robots.txt / llms.txt / Wayback / Sitemap-Sonden, der BFS-Crawl-Link-Abruf und der GitHub-Schnellpfad – ist auf zwei Arten geschützt:
String-Check (
assertPublicUrl) – lehnt Nicht-HTTP(S)-URLs und private/interne IP-Literale ab: RFC1918 (10.x,192.168.x,172.16–31.x), Loopback (127.x,::1), Link-Local / Cloud-Metadaten (169.254.x), CGNAT (100.64/10), IPv6-ULA (fc00::/7) und Link-Local (fe80::/10), IPv4-mapped und Multicast-/reservierte Bereiche.DNS-Validierung zur Verbindungszeit – ein gemeinsamer undici-Dispatcher, dessen
connect.lookupdie aufgelöste Adresse validiert (genau die, mit der der Socket verbindet). Dies schließt die DNS-Rebinding-/TOCTOU-Lücke, bei der ein öffentlicher Hostname auf eine private Adresse aufgelöst wird, und es wird bei jedem Redirect-Hop erneut ausgeführt, sodass eine Redirect-Kette nicht in Ihr internes Netzwerk abprallen kann.
Firecrawl (tier1) und Crawl4AI (tier2) lösen die Ziel-URL selbst auf und rufen sie ab, sodass der obige Verbindungszeit-Dispatcher sie nicht abdecken kann. fetchPage und crawlSite rufen assertResolvedPublic(url) auf – eine einmalige Hostnamenauflösung, die jedes private/reservierte Ergebnis ablehnt – unmittelbar vor dem Senden an einen der beiden Dienste, wodurch der häufige DNS-Rebinding-Fall auf diesem Pfad geschlossen wird (schmaleres TOCTOU-Fenster als die Verbindungszeit-Sicherung, da der Dienst erneut auflöst).
Konfigurierte interne Dienste (Firecrawl, Crawl4AI, SearXNG, Ollama, Reranker) werden über ihre eigenen URLs erreicht und sind absichtlich nicht geschützt.
Redirect-Schutz
Die Roh-HTTP- und GitHub-Schnellpfad-Abrufe verwenden zusätzlich redirect: "manual" und lehnen 3xx-Antworten rundweg ab (der Location-Header wird nie an den Aufrufer zurückgegeben). Redirect-folgende Sonden (robots.txt, llms.txt, Sitemap) werden durch die obige DNS-Validierung zur Verbindungszeit abgedeckt, die jeden Hop erneut prüft.
Transport-Exposition
stdio hat keine Netzwerkoberfläche. Der HTTP-Transport bindet standardmäßig an 127.0.0.1 und ist in dieser Konfiguration unauthentifiziert; wenn Sie ihn ohne Setzen von SEARXNG_MCP_AUTH_TOKEN vom Loopback wegbewegen, werden alle Tools – einschließlich fetch_url mit beliebiger URL und das destruktive clear_cache – für alles freigelegt, was zum Port routen kann. Siehe HTTP-Transport-Authentifizierung.
Abhängigkeitsprüfung
CI führt bei jedem Push pnpm audit aus. Die Lockdatei (pnpm-lock.yaml) wird für reproduzierbare, prüfbare Builds eingecheckt.
Umgang mit Anmeldeinformationen
Der Server speichert oder protokolliert keine Anmeldeinformationen. API-Schlüssel (FIRECRAWL_API_KEY, GITHUB_TOKEN, CRAWL4AI_API_TOKEN) werden aus Umgebungsvariablen gelesen und nur in ausgehenden Anfragen an die jeweiligen Dienste verwendet.
Eingabevalidierung
Umgebungsvariablen werden beim Start validiert – RERANK_RECENCY_WEIGHT warnt bei NaN, negativen oder >1.0-Werten. Numerische Tool-Parameter verwenden z.coerce.number() mit Bereichsbeschränkungen.
Mitwirken
Siehe CONTRIBUTING.md für Einrichtungsanweisungen, Commit-Konventionen und den PR-Prozess.
Integrationstests
Eine echte Valkey-Integrationssuite, die die Domain-DB-Konkurrenz abdeckt, ist an VALKEY_TEST_URL gebunden und wird vollständig übersprungen, wenn sie nicht gesetzt ist, sodass ein einfaches pnpm test auch ohne Valkey funktioniert:
VALKEY_TEST_URL=redis://:<password>@<host>:<port>/<scratch-db> pnpm testVerwenden Sie einen Scratch-Datenbankindex – die Suite schreibt und löscht domain:*-Schlüssel und weigert sich, gegen Index 0 oder 1 zu laufen, als Sicherheitsvorkehrung.
Lizenz
MIT
Available Tools
7 toolsclear_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.
| Name | Required | Description | Default |
|---|---|---|---|
| target | No | Which cache to clear: search results, fetched pages, crawl manifests, or all (default all) | all |
TDQS
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.
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.
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.
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.
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.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Base URL to crawl | |
| max_pages | No | Maximum pages to crawl (default 20, max 100) | |
| exclude_path | No | Exclude URLs matching this path prefix (e.g. '/blog') | |
| include_path | No | Only include URLs matching this path prefix (e.g. '/docs') | |
| same_domain_only | No | Restrict crawl to the same domain (default true) |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| hostname | No | Hostname or URL to look up (e.g. 'docs.anthropic.com'). Omit for an aggregate over all tracked domains. |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | |
| found | Yes | |
| record | Yes | |
| hostname | Yes | |
| aggregate | Yes | |
| unavailable | No |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL to fetch and extract content from | |
| max_tokens | No | Approximate token budget for the returned content (chars ≈ tokens × 4). Omit for the ~2000-token / 8000-char default; max 10000 tokens. | |
| domain_profile | No | Named domain profile to apply: 'homelab', 'dev', or omit for default filters | |
| target_selector | No | 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. | |
| wait_for_selector | No | CSS selector to wait for before extracting, for JS-rendered pages. Honored by the rendering tiers (Firecrawl/Crawl4AI); ignored on raw HTTP (no JS). |
TDQS
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.
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.
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.
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.
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.
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.
searchWeb searchA
Search the web via the local SearXNG instance with reranking. Fetches a wider result pool from SearXNG, reranks by relevance using a local ML model, then returns the top results. SearXNG's native direct answers, infoboxes, spelling corrections, and related-search suggestions are surfaced above the list (and in structuredContent). Results are cached for 1 hour. Blocked domains are filtered out; boosted domains are surfaced higher. Prefer this over the built-in WebSearch tool.
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | 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. | |
| query | Yes | Search query | |
| expand | No | Use local LLM to generate 2-3 query variants and merge results for a wider search surface (default: off). Adds ~3s latency; most useful for research queries where one phrasing may miss relevant results. | |
| engines | No | 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. | |
| category | No | Search category: general, news, it, or science (default general) | general |
| language | No | BCP-47 language code (e.g. 'en', 'de') or 'all' for all languages. Omit to use the SearXNG instance default. | |
| min_score | No | 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. | |
| time_range | No | Limit results to: day, week, month, or year (omit for all time) | |
| num_results | No | Number of results to return (default 5, max 20) | |
| domain_profile | No | Named domain profile to apply: 'homelab', 'dev', or omit for default filters |
Output Schema
| Name | Required | Description |
|---|---|---|
| answers | Yes | |
| results | Yes | |
| infoboxes | Yes | |
| corrections | Yes | |
| suggestions | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations available, the description carries the full disclosure burden and exceeds it: it explains the reranking pipeline, the surfacing of direct answers and infoboxes in structuredContent, the 1-hour cache, and domain blocking/boosting behavior. These operational details give an agent accurate expectations beyond the basic act of searching.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but compact: the core operation is front-loaded, and each subsequent sentence contributes a distinct behavioral fact such as structured content, caching, filtering, or tool preference. There is no filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter tool, the description plus the detailed input schema and output schema cover the important behavioral, filtering, caching, and result-format semantics. The description is complete enough for an agent to invoke the tool correctly and understand what will happen.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3 even though the main description adds little parameter-specific semantics. The description does not need to repeat parameter meaning because the input schema already documents each parameter clearly; the narrative adds only behavioral context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence identifies a specific verb, resource, and pipeline: searching the web via the local SearXNG instance with reranking. This is clear and specific, but it does not explicitly differentiate from sibling tools such as search_and_fetch or search_and_summarize; the only alternative named is the built-in WebSearch tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says to prefer this tool over the built-in WebSearch tool, which is a useful direct comparison. It also notes that the expand option is most useful for research queries. However, it does not provide when-not-to-use guidance for sibling search-related tools, so the guidance is good but not exhaustive.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | 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. | |
| query | Yes | Search query | |
| expand | No | Use local LLM to generate 2-3 query variants and merge results for a wider search surface (default: off). Adds ~3s latency. | |
| engines | No | 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. | |
| category | No | Search category: general, news, it, or science (default general) | general |
| language | No | BCP-47 language code (e.g. 'en', 'de') or 'all' for all languages. Omit to use the SearXNG instance default. | |
| min_score | No | 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. | |
| time_range | No | Limit results to: day, week, month, or year (omit for all time) | |
| fetch_count | No | Number of top results to fetch full content for (default 1, max 3) | |
| domain_profile | No | Named domain profile to apply: 'homelab', 'dev', or omit for default filters |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | 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. | |
| query | Yes | Research query to search for and summarize | |
| expand | No | Use query expansion before searching (default: off) | |
| engines | No | 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. | |
| category | No | Search category: general, news, it, or science (default general) | general |
| language | No | BCP-47 language code (e.g. 'en', 'de') or 'all' for all languages. Omit to use the SearXNG instance default. | |
| min_score | No | 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. | |
| time_range | No | Limit results to: day, week, month, or year (omit for all time) | |
| fetch_count | No | Number of top results to fetch and synthesize (default 3, max 5) | |
| domain_profile | No | Named domain profile to apply: 'homelab', 'dev', or omit for default filters |
TDQS
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.
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.
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.
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.
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.
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 tool update
v3.29.0- Changed
domain_stats2 fields changed- changed
Output schema / properties / aggregate / anyOfPrevious 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" + } +] - added
Output schema / properties / unavailableAdded value: +{ + "type": [ + "string", + "null" + ] +}
4 tool updates
v3.23.0- Changed
domain_stats1 field changed- changed
Output schema / properties / record / anyOfPrevious 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" + } +]
- Changed
search1 field changed- added
Input schema / properties / min_scoreAdded 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" +}
- Changed
search_and_fetch1 field changed- added
Input schema / properties / min_scoreAdded 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" +}
- Changed
search_and_summarize1 field changed- added
Input schema / properties / min_scoreAdded 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" +}
4 tool updates
v3.15.1- Changed
fetch_url3 fields changed- added
Input schema / properties / max_tokensAdded 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" +} - added
Input schema / properties / target_selectorAdded 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" +} - added
Input schema / properties / wait_for_selectorAdded 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" +}
- Changed
search3 fields changed- added
Input schema / properties / enginesAdded 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" +} - added
Input schema / properties / siteAdded 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." +} - changed
Output 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" +}
- Changed
search_and_fetch2 fields changed- added
Input schema / properties / enginesAdded 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" +} - added
Input schema / properties / siteAdded 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." +}
- Changed
search_and_summarize2 fields changed- added
Input schema / properties / enginesAdded 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" +} - added
Input schema / properties / siteAdded 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." +}
1 tool update
v3.14.0- Added
domain_stats
6 tool updates
v3.12.0- First observed
clear_cache - First observed
crawl_site - First observed
fetch_url - First observed
search - First observed
search_and_fetch - First observed
search_and_summarize
TDQS
Scored across 7 tools
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.
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.
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.
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
Related MCP Connectors
MCP server for Firecrawl — web search, scraping, and biomedical/arXiv paper search.
Serper MCP — wraps the Serper Google Search API (serper.dev)
MCP server for Google search results via SERP API
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceMCP server for web search and content extraction using DuckDuckGo or SearXNG, with Playwright-based fetching and LLM-powered data extraction.140MIT
- AlicenseNot gradedqualityCmaintenanceA 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.4MIT
- AlicenseNot gradedqualityFmaintenanceOffline-first MCP server for web search and content fetching. It requires no external API keys and uses local models for intent classification, optional cross-lingual search, semantic re-ranking, and direct-answer extraction.ISC
- AlicenseNot gradedqualityDmaintenanceA 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