Skip to main content
Glama

searxng-mcp

Built with Claude Code CI License: MIT npm

自己ホスト型の SearXNG インスタンスを介したプライベート Web 検索のための MCP サーバーです。結果はローカル ML モデルで再ランク付けされ、全ページのコンテンツは Firecrawl で取得され、オプションの Ollama インスタンスがクエリ拡張と LLM による要約を提供します。

サードパーティの検索 API にクエリを送信せずに Web 検索を必要とする Claude Code および LibreChat エージェントでの使用を想定しています。

Claude Code と homelab-agent のマルチエージェントワークフローを使用して構築されています。このプラットフォームは、AI 支援リサーチのために searxng-mcp を本番環境で使用しているのと同じものです。

クイックスタート

実行中の SearXNG インスタンスが必要です。キャッシュバックエンドを強く推奨します。

最小構成 — Dragonfly/Valkey キャッシュバックエンドを起動し、searxng-mcp を実行します:

docker compose -f docker-compose.example.yml up -d
SEARXNG_URL=http://localhost:8081 CACHE_URL=redis://localhost:6381 npx @tadmstr/searxng-mcp

Firecrawl、Crawl4AI、Ollama、Kiwix、アドブロックプロキシ、NATS を含む完全なローカルトポロジーについては、docker-compose.full.yml を参照してください。

Related MCP server: searxng-mcp-bridge

ツール

ツール

説明

主要パラメータ

search

ローカル再ランク付けを伴う SearXNG 経由の検索。より広い結果プールを取得し、関連性で再ランク付けして上位 N 件を返します。SearXNG のネイティブな直接回答、インフォボックス、スペル修正、関連提案は、リストの上と structuredContent に表示されます。

query、num_results(1〜20)、category、time_range、domain_profile、expand、language、engines、site

search_and_fetch

検索、再ランク付け、その後フェッチカスケード(Firecrawl → Crawl4AI → 生 HTTP)を使用して上位結果の完全なコンテンツを取得します。

query、category、time_range、fetch_count(1〜3)、domain_profile、expand、language、engines、site

search_and_summarize

検索、上位結果の取得、その後 Ollama(OLLAMA_SUMMARIZE_MODEL)を介して引用付きの要約を合成します。Ollama が利用できない場合は、取得した生のコンテンツにフォールバックします。

query、fetch_count(1〜5)、category、time_range、domain_profile、expand、language、engines、site

fetch_url

任意の公開 URL から読み取り可能な Markdown を取得して抽出します。GitHub ホストは GitHub 高速パスを利用し、YouTube ビデオ URL はトランスクリプトを返し、Reddit スレッド URL は投稿とコメントを返します(いずれも robots 経由でオプトイン、下記参照)。その他はすべてフェッチカスケード(Firecrawl → Crawl4AI → 生 HTTP)を使用します。トークン予算(デフォルト約 8,000 文字)に切り詰められます。

url、domain_profile、max_tokens、target_selector、wait_for_selector

crawl_site

サイト全体をクロールし、各ページの URL/タイトル/スニペットのマニフェストを返します。最初に Firecrawl クロールを試み、次にサイトマップ解析、その後オプションの BFS にフォールバックします。ページの完全なコンテンツは Valkey にキャッシュされるため、後続の fetch_url 呼び出しはコストゼロです。

url、max_pages(デフォルト: CRAWL_MAX_PAGES_DEFAULT)、bfs(ブール値、オプトイン BFS)

clear_cache

検索キャッシュ、フェッチキャッシュ、クロールマニフェストキャッシュ、またはすべてを消去します。キャッシュされた結果が古くなる可能性のある急速に変化するトピックを調査するときに便利です。

target(search、fetch、crawl、all)

domain_stats

ドメイン機能データベース の読み取り専用ビュー。hostname を指定すると、1 つのドメインのティア別成功率と機能フラグが表示されます。指定しない場合は、追跡されているすべてのドメインにわたる集計(ティア別成功率、最悪の失敗ドメイン、表示されたがフェッチされていない数)が表示されます。プログラムによるしきい値設定のための MCP 構造化出力(structuredContent)を返します。

hostname(オプション)

パラメータ

category — general(デフォルト)、news、it、science

time_range — day、week、month、year — 公開日で結果を制限します。全期間の結果が必要な場合は省略します。

fetch_count — 完全なコンテンツを取得する上位再ランク付け結果の数(search_and_fetch のデフォルトは 1、最大 3。search_and_summarize のデフォルトは 3、最大 5)。

domain_profile — 名前付きドメインフィルタープロファイルを適用します: homelab(自己ホスト型/Linux ドキュメントを表示)または dev(Stack Overflow、MDN、npm を表示)。デフォルトのフィルターを使用する場合は省略します。

expand — true の場合、検索前に Ollama(OLLAMA_EXPAND_MODEL)を介してクエリを書き換え、再現率を向上させます。OLLAMA_URL が必要です。デフォルトは EXPAND_QUERIES 環境変数の値です。

language — BCP-47 言語コード(例: en、de)または all で特定の言語に制限します。省略すると SearXNG インスタンスのデフォルトを使用します。search、search_and_fetch、search_and_summarize で利用できます。

engines — 検索を制限するカンマ区切りの SearXNG エンジン名(例: google,duckduckgo)。そのまま転送されます。不明または無効なエンジンは、エラーになる代わりに結果が少なくなります。3 つの検索ツールすべてで利用できます。

site — 結果を 1 つのドメインまたはリストに制限します(例: github.com または ["github.com", "gitlab.com"])。site: クエリ演算子としてベストエフォートで適用されます。ほとんどのエンジン(Google、Bing、DDG、Brave)はこれを尊重しますが、一部は無視します。3 つの検索ツールすべてで利用できます。

max_tokens(fetch_url)— 返されるコンテンツのおおよそのトークン予算(文字 ≈ トークン × 4)。省略するとデフォルトの約 2,000 トークン / 8,000 文字になります。最大 10,000 トークン。

target_selector(fetch_url)— 抽出を特定の要素に限定する CSS セレクター(例: article、main .content)。Firecrawl/Crawl4AI ではネイティブに尊重され、生 HTTP 層ではクライアント側で適用されます。高速パスや一致するものがない場合は無視されます。

wait_for_selector(fetch_url)— JS レンダリングページで抽出前に待機する CSS セレクター。レンダリング層(Firecrawl/Crawl4AI)で尊重されます。生 HTTP(JS なし)では無視されます。

アーキテクチャ

MCP client (stdio)
      │
      ▼
  searxng-mcp ──────────────→ cache ($CACHE_URL)           → result cache (search 1h, fetch 24h, crawl 6h)
      │
      ├── expand (optional) →  Ollama ($OLLAMA_URL)        → rewritten query (qwen3:4b)
      ├── search ───────────→ SearXNG ($SEARXNG_URL)      → raw results
      ├── rerank ───────────→ Reranker ($RERANKER_URL)    → ranked results
      │                       (fallback: SearXNG order if reranker unavailable)
      ├── fetch content ────┬→ GitHub API (github.com)    → markdown
      │                     ├→ Kiwix ($KIWIX_URL)         → ZIM content (Wikipedia/SO/Arch Wiki, fast path)
      │                     ├→ Hister ($HISTER_URL)       → browsing-history index (login-walled/JS-heavy fast path)
      │                     ├→ Firecrawl ($FIRECRAWL_URL) → page markdown (tier 1)
      │                     ├→ Crawl4AI ($CRAWL4AI_URL)  → page markdown (tier 2, optional; via $ADBLOCK_PROXY_URL if set)
      │                     ├→ Raw HTTP + Readability     → page markdown (tier 3 fallback; via $ADBLOCK_PROXY_URL if set)
      │                     └→ Wayback Machine (opt-in)  → archived page markdown (tier 4, $WAYBACK_ENABLED)
      ├── crawl_site ───────┬→ Firecrawl crawl           → page manifest (phase 1)
      │                     ├→ Sitemap parsing           → page manifest (phase 2 fallback, fast-xml-parser)
      │                     └→ BFS crawl (opt-in)        → page manifest (phase 3, $CRAWL_BFS_ENABLED)
      └── summarize (opt.) →  Ollama ($OLLAMA_URL)        → synthesized summary ($OLLAMA_SUMMARIZE_MODEL)
flowchart TD
    entry["fetchPage(url)"]
    cache{"Valkey cache hit?"}
    cached["→ return cached { title, url, text }"]
    github{"GitHub host?\ngithub.com · raw · api"}
    gh_fetch["GitHub API / raw.githubusercontent.com / api.github.com\n→ return"]
    llms{"llms.txt domain?"}
    llms_fetch["Probe /llms-full.txt\nextract matching section\n→ return"]
    kiwix{"Kiwix host?\nKIWIX_URL set"}
    kiwix_fetch["Local Kiwix ZIM\nWikipedia · Stack Overflow · Arch Wiki\n→ cache + return"]
    pdf{".pdf URL?"}
    robots["robots.txt pre-check — tiers 1–3\ndisallowed → RobotsDisallowedError (cached 24h)"]
    tier_skip(["Per-domain tier skip\nsuccess rate <30% over ≥10 tries\nor tier_skip operator override"])
    t1["Tier 1 — Firecrawl\n$FIRECRAWL_URL"]
    t2["Tier 2 — Crawl4AI\n$CRAWL4AI_URL · optional\nadblock proxy if $ADBLOCK_PROXY_URL"]
    t3["Tier 3 — Raw HTTP + Readability\nfallback: raw HTML slice\nadblock proxy if $ADBLOCK_PROXY_URL"]
    t4["Tier 4 — Wayback Machine CDX API\narchived snapshot · WAYBACK_ENABLED=true"]
    post["Post-extraction\nJSON-LD Article · title cascade\nog:title → twitter:title → title → h1 → URL"]
    result["→ return { title, url, text }"]

    entry --> cache
    cache -->|hit| cached
    cache -->|miss| github
    github -->|yes| gh_fetch
    github -->|no| llms
    llms -->|yes| llms_fetch
    llms -->|no| kiwix
    kiwix -->|yes| kiwix_fetch
    kiwix -->|no| pdf
    pdf -->|"yes — skip tier 1"| t2
    pdf -->|no| robots
    robots --> tier_skip
    tier_skip --> t1
    t1 -->|success| post
    t1 -->|"empty / error"| t2
    t2 -->|success| post
    t2 -->|"empty / error"| t3
    t3 -->|success| post
    t3 -->|"empty / error"| t4
    t4 -->|success| result
    post --> result

    style entry fill:#ffffff,stroke:#333333,color:#000000
    style cache fill:#ffffff,stroke:#333333,color:#000000
    style cached fill:#ffffff,stroke:#333333,color:#000000
    style github fill:#dae8fc,stroke:#6c8ebf,color:#000000
    style gh_fetch fill:#dae8fc,stroke:#6c8ebf,color:#000000
    style llms fill:#dae8fc,stroke:#6c8ebf,color:#000000
    style llms_fetch fill:#dae8fc,stroke:#6c8ebf,color:#000000
    style kiwix fill:#fff9c4,stroke:#b8860b,color:#000000
    style kiwix_fetch fill:#fff9c4,stroke:#b8860b,color:#000000
    style pdf fill:#ffffff,stroke:#333333,color:#000000
    style robots fill:#ffffff,stroke:#333333,color:#000000
    style tier_skip fill:#f5f5f5,stroke:#666666,color:#000000
    style t1 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
    style t2 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
    style t3 fill:#d5e8d4,stroke:#5a8a4a,color:#000000
    style t4 fill:#f8cecc,stroke:#a03030,color:#000000
    style post fill:#e1d5e7,stroke:#7a5a8a,color:#000000
    style result fill:#ffffff,stroke:#333333,color:#000000

SearXNG と Firecrawl は必須です。Crawl4AI、Valkey、Ollama、Kiwix、および再ランク付けはオプションです。サーバーは、これらのいずれかが利用できない場合でもグレースフルに劣化します。

アドブロッキング

searxng-mcp は、フェッチ層グループごとに 1 つずつ、2 つの独立したアドブロッキングサイドカーを使用します:

サイドカー

層

メカニズム

docker/puppeteer-adblock/

層 1(Firecrawl)

CDP レベルのインターセプト — 完全な HTTPS フィルタリング、同じブラウザプロセス

docker/adblock-proxy/

層 2+3(Crawl4AI、生フェッチ)

HTTP フォワードプロキシ — プレーン HTTP 広告ドメインをフィルタリング

層 1 — Puppeteer アドブロック

Firecrawl が使用する firecrawl-puppeteer サービスは、@ghostery/adblocker-puppeteer をアップストリームの trieve/puppeteer-service-ts に重ねるカスタムイメージ(docker/puppeteer-adblock/)を実行します。EasyList + EasyPrivacy は起動時にロードされ、168 時間ごとに更新されます。ブロッカーは Firecrawl が作成するすべてのページに適用されます。広告の多いサイトのフェッチを高速化し、レンダリングされた DOM サイズを縮小します。

環境変数:

変数

デフォルト

説明

ADBLOCK_DISABLE

未設定

true に設定すると、フィルターのロードを完全にスキップします。

ADBLOCK_FILTERS_URL

EasyList + EasyPrivacy

フィルターリスト URL のカンマ区切りリスト。

ADBLOCK_REFRESH_HOURS

168

ブロッカーが設定された URL から再構築する頻度。

ベースイメージは SHA256 ダイジェストで固定されています。変更をデプロイするには、サービスを再構築して再起動します:

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

ドメインごとのバイパス: domains.json は、将来のオペレーターオーバーライドのために adblock_skip スロットを予約しています。配線はまだ実装されていません。Firecrawl がカスタムヘッダーを puppeteer-service に転送する必要がありますが、これは現在の API の一部ではありません。スコープクリープ項目 I として追跡されています。

層 2+3 — アドブロックプロキシ

ADBLOCK_PROXY_URL(例: http://adblock-proxy:8118)を設定すると、Crawl4AI と生の Node fetch リクエストが、広告とトラッカーリクエストをフィルタリングする HTTP フォワードプロキシを経由します。HTTPS CONNECT トンネルは変更されずに通過します。MITM はないため、フィルタリングはプレーン HTTP 広告ドメインにのみ適用されます。層 1 の puppeteer フックは、その層の完全な HTTPS フィルタリングをすでに処理しています。プロキシは、層 2 と 3 で漏れるものをカバーします。

サービス定義、構成オプション、デプロイ手順については、docker/adblock-proxy/ を参照してください(docker-compose.full.yml に含まれています)。

データ駆動型層ルーティング

フェッチカスケードを呼び出す前に、searxng-mcp はドメインの tier_stats_30d(ドメイン機能データベース を参照)を読み取り、少なくとも 10 回の試行で成功率が 30% 未満の層をスキップします。コールドスタートドメイン(10 回未満の試行)はデフォルトのカスケードを維持します。各スキップは reason: low_success_rate を含む searxng.fetch.tier.skipped NATS イベントを発行し、searxng_fetch_total{outcome=skipped} をインクリメントします。

オペレーターオーバーライド。 domains.json に tier_skip マップを追加して、統計に関係なく層を強制的にスキップします:

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

tier_skip キーは、ベアドメイン(example.com はドメインとすべてのサブドメインに一致)またはドメイン + パスプレフィックス(example.com/api/)にできます。ファイルはホットリロードされます。再起動は不要です。手動オーバーライドは reason: operator_override を発行します。

コンテンツタイプ高速パス

構造化された非HTMLコンテンツ(application/json、任意の*+json、XML、YAML、TOML、CSV、またはtext/plain)を提供するURLは、HEADプローブで検出され、完全なFirecrawl/Crawl4AIカスケードではなく、生のHTTP層に直接ルーティングされます。JSONはフェンス付きコードブロック内でプリティプリントされて返されます。以前は、ヘッドレスブラウザにJSON APIレスポンスやCDNアセットをレンダリングするよう依頼すると空のマークダウンが返されたため、APIおよびCDNエンドポイント(registry.npmjs.org、api.osv.dev、cdn.jsdelivr.netなど)は単に失敗していました。

保証事項:

  • プローブはフェイルオープンです。到達不能なホスト、HEADを拒否するサーバー、または読み取り不能/解析不能なContent-Typeヘッダーはすべて、通常のカスケードにそのままフォールスルーします。

  • application/xhtml+xmlは意図的に除外されています。これはブラウザ向けのマークアップであり、構造化データではありません。

  • サーバーがtext/plainと誤ってラベル付けしたHTMLは、生のテキストブロックとしてダンプされるのではなく、HTMLとして解析されます。

ドメイン機能データベース

各フェッチは、searxng-mcpがターゲットドメインについて学習した内容をValkeyのdomain:<hostname>(90日TTL、schema_version 5)に記録します。レコードごとにキャプチャされるもの:

  • tier_stats_30d.{tier1,tier2,tier3,tier4,github}.{attempts, ok, fail, last_fail_reason, window_start_ms} — 30日間のローリングウィンドウにおけるティアごとのフェッチ成功率。カットオフは読み取り時に適用され、ティアルーティングの決定とdomain_statsレポートの両方で共有されるため、両者が矛盾することはありません。一度フェッチされてからアイドル状態のままのドメインは、次の書き込みまで古い数値が残るのではなく、実際に空のウィンドウを報告します。tier4(Wayback Machine)スロットは、WAYBACK_ENABLED=trueの場合にのみ記録されます。githubスロットは、GitHub高速パス(raw.githubusercontent.com / api.github.com / github.com READMEフェッチ)を記録します。これはティアカスケードをバイパスしますが、ここでは追跡されます。schema_versionのバンプは既存のレコードを新規に再構築します。現在アイドル状態のドメインの累積ウィンドウは破棄されます(1→2、2→3、3→4、4→5のバンプ全体で前例があります)。

  • capabilities.metadata_fetch.{attempts, ok, fail, last_fail_reason} — メタデータサイドチャネルフェッチ(fetchRawHtmlForMetadata、JSON-LD/og:titleサンプリングに使用)の成功/失敗。これは「このドメインにそもそも到達可能か」に答えるものであり、「フルコンテンツ配信が成功したか」ではないため、tier_stats_30dとは別に追跡されます。

  • capabilities.seen_in_search.{count, last_seen_ms} — ドメインがsearch結果にどのくらい頻繁に現れるか。searxSearch()によってすべてのリターンパス(キャッシュヒットを含む)でファイアアンドフォーゲットで書き込まれ、フェッチは実行されないため、ドメインはフェッチされる前に追跡できます。

  • capabilities.robots_txt.{present, fetched, allows_us} — robots.txtの存在と、それが私たちを許可するかどうか

  • capabilities.llms_full_txt.{present, size_bytes, last_checked} — ドメインが/llms-full.txtを提供するかどうか

  • capabilities.json_ld_article.{sampled, present, last_sampled_at} — ページがArticleスキーマのJSON-LDをまったく持っているかどうか(Schema.orgのArticle/NewsArticle/BlogPosting/TechArticleおよびScholarlyArticle/OpinionNewsArticle/LiveBlogPostingなどのサブタイプ。ベア名または完全修飾されたhttps://schema.org/... @typeで一致)。そのスキーマに抽出可能な本文テキストがあるかどうかとは無関係です。多くのサイトはarticleBodyのない見出し/メタデータJSON-LDを公開しており、これは抽出後処理が実際にそれを使用するかどうかとは別の懸念事項です。

  • capabilities.og_title.{sampled, present, last_sampled_at} — <meta property="og:title">についても同様

  • preferred_strategy — 現在は、存在するプローブが着地したときにllms_full_txtに設定されます。将来のフェーズではこれを使用してティアカスケードをスキップします。

バンドルされたCLIでレコードを検査するか、エージェントからdomain_statsツール(単一ドメインまたは集計、ツールを参照)を介してクエリします:

pnpm dump-domain docs.anthropic.com

dump-domainは、ウィンドウが期限切れになった場合と、ティアにデータがまったくない場合を区別し、両方を同じように表示することはありません。

同じホスト名に対する同時更新(1回のフェッチ中に並行して発火するティア試行、robotsプローブ、抽出後サンプルレコーダー)は、サーバー側のLua compare-and-setと、単一プロセス自身のライター間の競合を除去するプロセス内のキーごとのキューを組み合わせて直列化され、CASはプロセス間の真に同時の書き込みのみを調停する必要があります。v3.17.0より前のバージョンでは、共有接続に対するWATCH/MULTI/EXECの読み取り-変更-書き込みを使用していましたが、これは実際には同時ライターを直列化しません。その結果、v3.17.0より前に収集されたデータは大幅に不完全でした。アップグレードすると、スキーマバンプを介して既存のティア統計が破棄されます。アップグレード直後はdomain_statsがほぼ空に読み取られ、その後数日かけて再充填されると予想されます。

ドメインDBの永続化

ドメインDBは90日TTLと30日間のローリングウィンドウの下でValkeyにのみ存在するため、キャッシュフラッシュまたはTTLの期限切れにより、再取得にコストがかかる機能学習が消去されます。2つのCLIがそれを永続化します:

pnpm domain-db-maintenance   # SCAN all domain:* records → write a dated JSON snapshot (+ prune) and emit OTel gauges
pnpm restore-domain-db       # re-seed the domain-db from the newest snapshot after a flush
  • domain-db-maintenance はスタンドアロンのジョブです(cronまたはPM2 cron-restartを介してスケジュールで実行します。searxng-mcpはそれぞれが発火する複数の並行するエージェントごとのstdio子プロセスとして実行されるため、プロセス内タイマーとしては実行しないでください)。1つの境界付きSCANが両方の出力を供給します:永続的な日付付きスナップショットと、OTEL_EXPORTER_OTLP_ENDPOINTが設定されている場合は、終了前に強制フラッシュされるゲージ(searxng_domains_tracked、searxng_domains_failing、searxng_domain_tier_success_ratio{tier})。

  • restore-domain-db は、欠落しているキー、またはライブレコードがスナップショットよりも厳密に古いキー(last_fetchを比較)のみを再シードします。より新しいか等しいライブレコードを上書きすることはないため、ライブで部分的に入力されたValkey(例:自動フラッシュリカバリのためのサービス起動シーケンス内)に対して実行しても安全です。

環境変数

デフォルト

目的

DOMAIN_DB_SNAPSHOT_DIR

./domain-db-snapshots

日付付きスナップショットが書き込まれる/読み取られる場所。デプロイメントでは永続パス(appdataまたはNFSマウント)に設定します。

DOMAIN_DB_SNAPSHOT_RETENTION

14

保持するスナップショットの数。古いものはメンテナンス実行ごとに削除されます。

llms.txt高速パス

domains.json(llms_txt配列)のホワイトリスト登録されたドキュメントドメインに対して、fetchPageは最初に<origin>/llms-full.txtを試し、任意のティアを呼び出す前に要求されたURLに一致するセクションを抽出します。これにより、十分に計装されたドキュメントサイトに対してpuppeteerを実行することを回避し、クリーンなマークダウンセクションを直接返します。プローブの結果と完全な本文はValkey(llms:<origin>:full、存在する/存在しないで24時間/7日)にキャッシュされます。デフォルトのホワイトリスト:docs.anthropic.com、docs.openai.com、docs.stripe.com、docs.crawl4ai.com、docs.firecrawl.dev、docs.cursor.com。domains.jsonを編集して拡張します。ファイルはホットリロードされます。

Kiwix高速パス

KIWIX_URLが設定されている場合、既知のオフライン対応ホストに対するフェッチリクエストはFirecrawl/Crawl4AIカスケードの前にインターセプトされ、ローカルのKiwix ZIMアーカイブから提供されます。これにより、Wikipediaのようなサイト(ヘッドレススクレイパーをブロックする)の100%のティア1失敗率が排除され、外部ネットワークトラフィックゼロでクリーンで読み取り可能なコンテンツが返されます。

サポートされているホストとZIMブック(kiwix-serveは--nodatealiases / -zで実行する必要があります):

ホスト

ZIMブック

en.wikipedia.org、wikipedia.org

wikipedia_en_all_mini

stackoverflow.com

stackoverflow.com_en_all

wiki.archlinux.org

archlinux_en_all_maxi

Kiwixパスはllms-txt高速パスの後、robotsゲートの前に実行されます。Kiwixリクエストが失敗するか空を返した場合、完全なティアカスケードが通常どおり実行されます。KIWIX_URLが設定されていない場合、この機能はオーバーヘッドを追加しません。isKiwixHost()は即座にfalseを返します。

KIWIX_URLをkiwix-serveのベースURL(例:http://localhost:8292)に設定します。

YouTubeおよびReddit高速パス

fetch_urlはYouTubeビデオURL(youtube.com、youtu.be)とRedditスレッドURLを認識し、レンダリングされたページをスクレイピングする代わりに直接提供できます:

  • YouTube — ウォッチページからビデオのキャプショントラックを抽出し、トランスクリプトを返します。YOUTUBE_TRANSCRIPT_ENABLED(デフォルトオン)で有効になります。

  • Reddit — 公開.jsonビューをフェッチし、投稿とトップコメントを標準の{title, url, text}形式で返します。HTTP 429ではフォールスルーします。REDDIT_FASTPATH_ENABLED(デフォルトオン)で有効になります。

どちらも非公式で文書化されていないエンドポイント(YouTubeのtimedtext API、Redditの.json)に依存しています。ベストエフォートでSLAはありません。どちらも上流の変更で壊れる可能性があるため、キルスイッチがあります。ミスが発生すると、リクエストは通常のティアカスケードにフォールスルーします(それでもYouTubeページのタイトル/説明を取得できます)。

robots.txt: 両方のエンドポイントはサイトのrobots.txtによって禁止されています(Redditはすべてを禁止し、YouTubeはトランスクリプトが存在する/api/を禁止します)。デフォルトでは、これらの高速パスはそれを尊重して休止状態のままで、カスケードにフォールスルーします。独自のインスタンスでは、YOUTUBE_IGNORE_ROBOTS=true / REDDIT_IGNORE_ROBOTS=trueで直接フェッチをオプトインできます。

サイトクローリング

crawl_siteはサイト全体をクロールし、見つかった各ページのURL/タイトル/スニペットのマニフェストを返します。3フェーズの戦略カスケードを使用します:

  1. Firecrawlクロール — Firecrawlにクロールジョブを送信し(/crawlエンドポイント)、完了するまでポーリングし、完全なページリストを返します。FIRECRAWL_CRAWL_POLL_INTERVAL_MSとFIRECRAWL_CRAWL_MAX_WAIT_MSによって制御されます。

  2. サイトマップ解析 — Firecrawlが失敗するか空を返した場合、/sitemap.xml(およびリンクされたサイトマップ)をフェッチし、タイトル/スニペット付きのURLを抽出します。サイトマップXML解析にはfast-xml-parserを使用します。

  3. BFSクロール(オプトイン) — サイトマップ解析も失敗した場合、指定されたURLからCRAWL_BFS_MAX_DEPTHリンクホップまで幅優先クロールを実行します。CRAWL_BFS_ENABLED=trueまたはbfsツールパラメータがtrueの場合にのみ実行されます。

クロール中にフェッチされた完全なページコンテンツはValkeyにキャッシュされます(TTL:CRAWL_MANIFEST_TTL_SECONDS、デフォルト6時間)。マニフェスト内の任意のURLに対する後続のfetch_url呼び出しは、キャッシュから即座に返されます。フォローアップ読み取りのフェッチオーバーヘッドはゼロです。

マニフェストキャッシュはclear_cache(target="crawl")でクリアできます。

Wayback Machineフォールバック

WAYBACK_ENABLED=trueの場合、3つの主要なティアすべてが失敗したときに、4番目のティアがWayback Machine CDX APIにアーカイブされたスナップショットを照会します。返されたコンテンツには、発信元ヘッダー([Archived snapshot – <timestamp> – <original_url>])がプレフィックスとして付けられ、呼び出し元はコンテンツが現在のページ状態を反映していない可能性があることを認識できます。

フェッチ品質

任意のティアが生のHTMLを含むコンテンツを返した後、抽出後パスがタイトルと本文の品質を向上させます:

  • JSON-LD Article抽出 — Schema.orgのArticle / NewsArticle / BlogPosting / TechArticleブロックは、ティア1のクロームスクレイピングよりもクリーンなheadlineとarticleBodyを提供します(スクリプトタグごとに1 MBのサイズ上限)。

  • タイトルカスケード — og:title → twitter:title → <title>(パブリッシャーサフィックス削除付き)→ 最初の<h1> → URLの順にフォールバックします。

  • ティア2 Readability比較 — Crawl4AIがマークダウンを返す場合、JSDOM+Readabilityもその生のHTMLに対して実行され、そのテキストがより長い場合(またはCrawl4AIが500文字未満を返す場合は無条件に)優先されます。

回復力

  • キャッシュは検索を決してハングさせません。 Valkey クライアントは CACHE_COMMAND_TIMEOUT_MS/CACHE_CONNECT_TIMEOUT_MS/CACHE_MAX_RETRIES_PER_REQUEST によって制限されます(設定 を参照)。ストールまたは CPU スパイクしたキャッシュバックエンドは、永久にハングする代わりにコマンドを拒否します。既存のフェイルソフト処理は、その拒否をスローではなくキャッシュミス(ライブで提供)に格下げします。キャッシュ接続失敗、クライアントエラー、コマンドごとのエラーは、スロットルされた [searxng-mcp] stderr 行を出力します(キーごとに重複排除されるため、持続的な障害は洪水ではなく定期的なパンくずリストを残します)。stderr は、デプロイされた PM2 プロセスに配線された唯一のテレメトリシンクです。

  • プロセスクラッシュハンドラ — uncaughtException はログを記録してから終了コード 1 で終了します(クリーンな PM2 再起動)。unhandledRejection はログを記録して続行し、共有プロセスを静かにクラッシュさせません。

  • グレースフルデグラデーション警告 — リランカーのフォールバックと Ollama/LLM の expand + summarize フォールバックは、品質を静かに低下させるとき(リランカーが利用不可、LLM バックエンドに到達不能)に、それぞれスロットルされた stderr 行を 1 行出力します。

  • バージョンは単一ソース で、実行時に package.json から取得されます(src/version.ts)。McpServer のバージョン、OTel トレーサー/メーターのバージョン、および送信 USER_AGENT はすべてそれを追跡するため、独立してドリフトすることはありません。

可観測性(オプトイン)

トレーシング、メトリクス、イベント公開は完全にオプトインです。以下の環境変数が設定されていない場合、サーバーは可観測性のオーバーヘッドがゼロで、実行時に OpenTelemetry や NATS パッケージを読み込むことはありません。

OpenTelemetry(トレース + メトリクス) — OTEL_EXPORTER_OTLP_ENDPOINT をコレクターの HTTP エンドポイントに設定すると、サーバーは以下を出力します:

  • スパン(リクエストごと):tool.<name> → expand_query? → searxng_request → rerank → fetch(×N)→ tier1_firecrawl | tier2_crawl4ai | tier3_rawfetch → post_extract。さらに search_and_summarize 用の summarize_llm。

  • カウンター:searxng_search_total{profile, expand}、searxng_fetch_total{tier, outcome}、searxng_cache_total{namespace, outcome}、searxng_errors_total{stage, error_type}。

  • ヒストグラム:searxng_search_duration_seconds{profile}、searxng_fetch_duration_seconds{tier, outcome}。

標準の OTEL 環境変数が適用されます(OTEL_SERVICE_NAME はデフォルトで searxng-mcp)。

NATS イベント — NATS_URL(例:nats://localhost:4222)を設定すると、サーバーはすべての検索、フェッチ、キャッシュヒット/ミス、robots スキップ、エラーに対して構造化イベントを公開します。認証は NATS_CREDS(JWT クレデンシャルファイル)または NATS_USER/NATS_PASSWORD(bcrypt ユーザー名/パスワード)を介して行われます。両方が設定されている場合はクレデンシャルファイル認証が優先されます。サブジェクト:

Subject

When

searxng.search.requested

検索ツールが呼び出されたとき

searxng.search.completed

検索が返されたとき(ソース、レイテンシ、リランク適用を含む)

searxng.fetch.requested

fetchPage が呼び出されたとき

searxng.fetch.tier.miss

ティアが空を返したか例外を投げたとき

searxng.fetch.tier.skipped

robots.txt で禁止されたとき

searxng.fetch.completed

フェッチが解決したとき(tier_served、text_len、レイテンシを含む)

searxng.cache.hit / .miss

Valkey ルックアップのたびに

searxng.error

ステージタグ付きエラー

各エンベロープには request_id と(OTel が有効な場合)trace_id が含まれるため、サブスクライバーは 2 つのストリームを結合できます。サブジェクトプレフィックスは NATS_SUBJECT_PREFIX で上書き可能です。検索クエリは search.* イベントを通じて流れます。下流のコンシューマーは PII のスクラビングに責任があります。

ポライトネス

  • 正直な User-Agent — 送信リクエストは searxng-mcp/<version> (+https://github.com/TadMSTR/searxng-mcp; personal research) として識別されます。

  • robots.txt 準拠 — /robots.txt はオリジンごとに 1 回取得され、Valkey の robots:<origin> キーで 24 時間キャッシュされます。禁止されたパスはティアが実行される前にスキップされ、skipped_robots url=… reason=… としてログに記録されます。

トランスポート

stdio(デフォルト)— Claude Code MCP プラグインおよび LibreChat の stdio 設定と互換性があります。

HTTP — SEARXNG_MCP_TRANSPORT=http を設定すると、マルチクライアントデプロイメントや Docker ベースのセットアップに適した共有 HTTP/SSE サーバーとして実行されます。SEARXNG_MCP_HOST:SEARXNG_MCP_PORT(デフォルト 127.0.0.1:3001)にバインドします:

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

Claude Code を HTTP サーバーに対して登録する:

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

セッションは Mcp-Session-Id ヘッダーでキー付けされるため、複数のクライアントが同じ共有プロセスに同時に接続できます。アイドルセッションは HTTP_SESSION_IDLE_TIMEOUT_MS 後に掃除され、HTTP_MAX_SESSIONS でハードキャップされます — 設定 を参照してください。

HTTP トランスポート認証

HTTP トランスポートはデフォルトでは認証なしです。これはデフォルトで 127.0.0.1 にバインドするため安全です。SEARXNG_MCP_HOST を他の値(コンテナで実行するために必要な 0.0.0.0 を含む)に変更する場合は、SEARXNG_MCP_AUTH_TOKEN も設定してください:

SEARXNG_MCP_AUTH_TOKEN=$(openssl rand -hex 32)

設定されている場合、GET /health を除くすべてのリクエストは、RFC 6750 のベアラークレデンシャルとしてトークンを保持する必要があります:

Authorization: Bearer <token>

それ以外のもの(ヘッダーなし、異なるスキーム、間違ったトークン)は、WWW-Authenticate: Bearer と JSON-RPC エラーボディを伴う 401 を受け取ります。応答は 3 つのケースすべてで同一であり、提示されたクレデンシャルを決してエコーしません。トークンは SHA-256 ダイジェストとして比較されるため、比較は定数時間であり、長さ情報を漏らしません。

認証済みサーバーを Claude Code に登録する:

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

変数を未設定のままにすると、以前の動作が正確に維持されるため、stdio ユーザーと既存のループバックバインド HTTP デプロイメントは変更を必要としません。呼び出し元ごとの認可モデルはありません。単一のトークンはサーバーへのアクセスを認証し、特定のクライアント ID を認証するわけではありません。起動時に、トークンなしの非ループバックバインドは警告をログに記録します。

GET /health は意図的にチェックから除外されています。 これはコンテナのヘルスチェックとモニタリングの liveness プローブであり、入力を受け取らず、その応答(status、cache、sessions)には秘密情報が含まれません。

GET /health — 認証なしの liveness プローブで、MCP エンドポイントとともに localhost にバインドされています。制限付きキャッシュコマンドタイムアウトを通じて Valkey に ping を送信し(チェック自体がハングすることはありません)、以下を返します:

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

または、キャッシュバックエンドに到達できない場合:

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

sessions はライブ HTTP セッション数です。キャッシュバックエンドを直接計測せずに MCP 側から劣化したキャッシュを検出するためのシステム管理者モニタリングに役立ちます。

前提条件

  • Node.js 20+

  • pnpm(または npm)

  • 実行中の SearXNG インスタンス

  • 実行中の Firecrawl インスタンス

  • Jina 互換の /v1/rerank エンドポイントを公開する実行中のリランカー(オプション)

  • 実行中の Valkey または Redis 互換インスタンス(オプション、結果キャッシュ用)

  • qwen3:4b および/または qwen3:14b をプルした実行中の Ollama インスタンス(オプション、クエリ拡張と要約用)

SearXNG

SearXNG は JSON 出力形式を有効にする必要があります。settings.yml で:

search:
  formats:
    - html
    - json

リランカー

リランカーは Jina 互換の /v1/rerank エンドポイントを公開する必要があります。軽量な FlashRank ラッパーが適しています — homelab-agent の docker/reranker/ リファレンスを参照してください。

Firecrawl

Firecrawl 互換のインスタンスならどれでも動作します。ローカルの firecrawl-simple デプロイメントで十分です。インスタンスが認証を必要とする場合は FIRECRAWL_API_KEY を設定してください(認証をスキップするローカルデプロイメントではデフォルトで placeholder-local になります)。

Crawl4AI

Crawl4AI は、Firecrawl が空のコンテンツを返す場合(ボットブロックされたページ、JS 多用サイト)に使用されるオプションの第 2 ティアフェッチフォールバックです。有効にするには CRAWL4AI_URL を設定します。未設定の場合、カスケードは生の HTTP フェッチにスキップします。

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

インスタンスが API トークン認証を必要とする場合は、CRAWL4AI_API_TOKEN を設定します。

search_and_summarize パスでは、Crawl4AI リクエストはノイズフィルタリングされたコンテンツ抽出に fit_markdown を使用します。他の呼び出し元(search_and_fetch、fetch_url)は raw_markdown を使用します。

Kiwix(オプション)

kiwix-serve は ZIM アーカイブを HTTP で提供します。必要な ZIM ファイルをダウンロードし、--nodatealiases(-z)を付けて kiwix-serve を実行して、ブック名を安定させてください:

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

サポートされている各ホストに必要な ZIM ファイル:

  • Wikipedia: wikipedia_en_all_mini(または maxi)

  • Stack Overflow: stackoverflow.com_en_all

  • Arch Wiki: archlinux_en_all_maxi

ZIM ファイルは library.kiwix.org からダウンロードできます。

Hister(オプション)

Hister は Firefox 拡張機能によって入力されるブラウジング履歴インデックスです。HISTER_URL が設定されている場合、fetchPage はティアカスケードを呼び出す前に履歴インデックスをチェックします。ログイン壁や JS 多用ページでスクレイパーが失敗する場合に役立ちます。

HISTER_URL に Hister インスタンスのベース URL を設定し、ベアラートークン認証が必要な場合は HISTER_TOKEN を設定します。

Valkey / Redis

Redis 互換のインスタンスならどれでも動作します。Valkey が推奨されます。検索結果は 1 時間キャッシュされ、フェッチされたページは 24 時間キャッシュされます。利用できない場合、サーバーはキャッシュなしで動作します。

Ollama

expand と search_and_summarize に必要です。必要なモデルをプルしてください:

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

think: false の動作は自動的に処理されます — 追加の Ollama 設定は不要です。

設定

すべてのサービス URL は環境変数で設定可能です。

Variable

Default

Description

SEARXNG_URL

http://localhost:8081

SearXNGインスタンスのURL

FIRECRAWL_URL

http://localhost:3002

FirecrawlインスタンスのURL

RERANKER_URL

http://localhost:8787

RerankerインスタンスのURL

FIRECRAWL_API_KEY

placeholder-local

Firecrawl APIキー(必要な場合)

GITHUB_TOKEN

(unset)

GitHub個人アクセストークン — レート制限を60から5,000リクエスト/時に引き上げます

OLLAMA_URL

(unset)

Ollama APIベースURL — expandとsearch_and_summarizeに必要です

OLLAMA_API_KEY

(unset)

認証付きOllamaプロキシ用のBearerトークン — 設定するとAuthorization: Bearer <key>ヘッダーを追加します

OLLAMA_EXPAND_MODEL

qwen3:4b

クエリ拡張(expandパラメータ)で使用されるモデル。再ビルドせずに上書きできます。

OLLAMA_SUMMARIZE_MODEL

qwen3:14b

search_and_summarizeで使用されるモデル。再ビルドせずに上書きできます。

LLM_BASE_URL

(unset)

expand + search_and_summarize用のOpenAI互換チャットエンドポイント(例:vLLM、llama.cpp、LM Studio)。APIパスを含める必要があります — 例:http://host:8000/v1 — サーバーは/chat/completionsを追加します。設定するとOLLAMA_URLより優先されるため、別のOllamaモデルを実行する代わりに、すでに読み込まれているモデルを再利用できます。

LLM_MODEL

(unset)

OpenAI互換バックエンドのモデルID。設定するとOLLAMA_EXPAND_MODEL / OLLAMA_SUMMARIZE_MODELを上書きします。

LLM_API_KEY

(unset)

OpenAI互換バックエンド用のBearerトークン — 設定するとAuthorization: Bearer <key>を追加します。

LLM_DISABLE_THINKING

true

chat_template_kwargs.enable_thinking: falseを送信して、推論モデル(例:Qwen3)が直接出力を返すようにします。そのフィールドを拒否するサーバーの場合はfalseに設定します。

CACHE_URL

redis://localhost:6381

Redis互換URL — 結果のキャッシュを有効にします。エイリアスとしてVALKEY_URLまたはREDIS_URLも受け付けます。Redis、Valkey、Dragonflyで動作します。利用できない場合はサーバーはグレースフルに劣化します。

CACHE_COMMAND_TIMEOUT_MS

2500

Valkeyコマンドタイムアウト — 停止/CPUスパイクしたキャッシュバックエンドはハングせずに拒否します(cacheGet()はすべての検索で最初のawaitです)。無効/非正の値はタイムアウトを無効にするNaNになる代わりにデフォルトにフォールバックします。

CACHE_CONNECT_TIMEOUT_MS

3000

Valkey接続タイムアウト。CACHE_COMMAND_TIMEOUT_MSと同じフォールバック動作。

CACHE_MAX_RETRIES_PER_REQUEST

2

Valkeyコマンドが拒否するまでの最大再試行回数。CACHE_COMMAND_TIMEOUT_MSと同じフォールバック動作。

CACHE_TTL_SECONDS

3600

検索結果キャッシュのTTL(秒)

FETCH_CACHE_TTL_SECONDS

86400

取得したページキャッシュのTTL(秒)

CRAWL_MANIFEST_TTL_SECONDS

21600

クロールマニフェストとページコンテンツキャッシュのTTL(秒)(6時間)

CRAWL_MAX_PAGES_DEFAULT

20

max_pagesが渡されない場合にcrawl_siteが返すデフォルトの最大ページ数

CRAWL_BFS_ENABLED

false

crawl_siteでBFSフォールバックをグローバルに有効にするにはtrueに設定します。bfsパラメータで呼び出しごとに有効にすることもできます。

CRAWL_BFS_MAX_DEPTH

3

BFSクロールの最大リンクホップ深度

FIRECRAWL_CRAWL_POLL_INTERVAL_MS

2000

Firecrawlクロールジョブの完了を待つときのポーリング間隔

FIRECRAWL_CRAWL_MAX_WAIT_MS

120000

サイトマップにフォールバックする前にFirecrawlクロールジョブを待つ最大時間

EXPAND_QUERIES

false

クエリ拡張をグローバルに有効にするにはtrueに設定します

CRAWL4AI_URL

(unset)

Crawl4AIインスタンスURL — Firecrawlが失敗した場合の第2層フェッチフォールバックを有効にします

CRAWL4AI_API_TOKEN

(unset)

APIトークン保護付きのCrawl4AIインスタンス用のオプションのBearerトークン

WAYBACK_ENABLED

false

Wayback Machine第4層フォールバックを有効にするにはtrueに設定します — 3つの層すべてが失敗した場合にアーカイブされたスナップショットを取得します

ADBLOCK_PROXY_URL

(unset)

第2層(Crawl4AI)と第3層(生のNode fetch)の広告ブロック用HTTPプロキシURL — 例:http://adblock-proxy:8118。docker/adblock-proxy/を参照してください。

KIWIX_URL

(unset)

kiwix-serveベースURL(例:http://localhost:8292)— Wikipedia、Stack Overflow、Arch WikiのKiwix高速パスを有効にします。未設定の場合は機能が無効になり、オーバーヘッドはゼロです。

HISTER_URL

(unset)

Hister閲覧履歴インデックスのベースURL — ログイン必須ページやJS多用ページに対して、層カスケードの前にHister高速パスを有効にします。未設定の場合は機能が無効になり、オーバーヘッドはゼロです。

HISTER_TOKEN

(unset)

Hister API認証用のBearerトークン。HISTER_URLが設定され、インスタンスでトークン認証が有効な場合に必要です。

YOUTUBE_TRANSCRIPT_ENABLED

true

fetch_urlでYouTubeトランスクリプト高速パスを有効にします。無効にするにはfalseに設定します(例:非公式のtimedtextエンドポイントが上流で壊れた場合)。

YOUTUBE_IGNORE_ROBOTS

false

YouTubeのrobots.txtが/api/を禁止していても、YouTubeトランスクリプトの取得をオプトインします。デフォルトはrobotsを尊重します(高速パスは休止状態のまま、カスケードにフォールスルーします)。

REDDIT_FASTPATH_ENABLED

true

fetch_urlでReddit .json高速パスを有効にします。無効にするにはfalseに設定します。

REDDIT_IGNORE_ROBOTS

false

Redditのrobots.txt(Disallow: /)にもかかわらず、Reddit .jsonの取得をオプトインします。デフォルトはrobotsを尊重します(高速パスは休止状態のまま、カスケードにフォールスルーします)。

SEARXNG_MCP_TRANSPORT

stdio

トランスポートモード:stdio(デフォルト、シングルクライアント)またはhttp(共有HTTP/SSEサーバー)。

SEARXNG_MCP_PORT

3001

HTTPリッスンポート(HTTPトランスポートモードのみ)。

SEARXNG_MCP_HOST

127.0.0.1

HTTPリッスンアドレス(HTTPトランスポートモードのみ)。

SEARXNG_MCP_AUTH_TOKEN

(unset)

HTTPトランスポートのみ。設定すると、GET /healthを除くすべてのリクエストはAuthorization: Bearer <token>を送信するか401を受け取る必要があります。未設定(デフォルト)の場合はチェックが完全に無効になります。SEARXNG_MCP_HOSTがループバックでない場合は必ず設定してください — HTTPトランスポート認証を参照してください。

HTTP_SESSION_IDLE_TIMEOUT_MS

600000

HTTPトランスポートのみ。これより長くアイドル状態のセッションはバックグラウンドスイープによって追い出されます(処理中のリクエストがあるセッションは除外されるため、長時間のcrawl_site呼び出しがリクエスト中に閉じられることはありません)。ターン途中で強制終了され、transport.oncloseを発火しないクライアントによるセッションマップの増加を制限します。

HTTP_MAX_SESSIONS

256

HTTPトランスポートのみ。ハードキャップのバックストップ — セッションマップがこれを超えた場合、アイドルタイムアウトに関係なく、最も最近使用されていないアイドルセッションが追い出されます。

NATS_USER

(unset)

bcryptユーザー名/パスワード認証用のNATSユーザー名。NATS_PASSWORDと一緒に使用します。NATS_CREDSも設定されている場合は無視されます(credsファイルJWT認証が優先されます)。

NATS_PASSWORD

(unset)

NATSパスワード — NATS_USERを参照してください。

インストール

npm(推奨)

npm install -g @tadmstr/searxng-mcp

または npx で直接実行する:

npx @tadmstr/searxng-mcp

ソースから

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

出力: build/src/index.js

MCP クライアント設定

Claude Code (CLI)

推奨される方法は、claude mcp add-json を使用して、完全な環境変数サポートでサーバーを登録することです:

claude mcp add-json searxng --scope user '{
  "command": "npx",
  "args": ["-y", "@tadmstr/searxng-mcp"],
  "env": {
    "SEARXNG_URL": "http://localhost:8081",
    "FIRECRAWL_URL": "http://localhost:3002",
    "RERANKER_URL": "http://localhost:8787",
    "OLLAMA_URL": "http://localhost:11434",
    "CACHE_URL": "redis://localhost:6379",
    "CACHE_TTL_SECONDS": "3600",
    "FETCH_CACHE_TTL_SECONDS": "86400",
    "EXPAND_QUERIES": "false",
    "CRAWL4AI_URL": "http://localhost:11235"
  }
}'

これは ~/.claude.json に書き込まれます。~/.claude/settings.json に searxng を追加しないでください。そのファイルは Claude Code での MCP 環境変数の注入には使用されません。

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "searxng": {
      "command": "npx",
      "args": ["-y", "@tadmstr/searxng-mcp"],
      "env": {
        "SEARXNG_URL": "http://localhost:8081",
        "FIRECRAWL_URL": "http://localhost:3002",
        "RERANKER_URL": "http://localhost:8787",
        "OLLAMA_URL": "http://localhost:11434",
        "CACHE_URL": "redis://localhost:6379",
        "CRAWL4AI_URL": "http://localhost:11235"
      }
    }
  }
}

LibreChat (librechat.yaml)

mcpServers:
  searxng:
    type: stdio
    command: node
    args:
      - /path/to/searxng-mcp/build/src/index.js
    env:
      SEARXNG_URL: http://localhost:8081
      FIRECRAWL_URL: http://localhost:3002
      RERANKER_URL: http://localhost:8787
      OLLAMA_URL: http://localhost:11434
      CACHE_URL: redis://localhost:6379
      CRAWL4AI_URL: http://localhost:11235

GitHub URL

GitHub URL は Firecrawl を使わずにネイティブに処理されます。githubFetch はホスト名に基づいて振り分けます:

  • リポジトリルート (github.com/owner/repo) — GitHub API 経由で README を取得します

  • ファイル blob (github.com/owner/repo/blob/branch/path/to/file) — raw.githubusercontent.com に書き換えて raw コンテンツを取得します

  • Raw ファイル (raw.githubusercontent.com/...) — そのまま直接取得します

  • API (api.github.com/...) — レスポンスをデコード(base64 の content フィールド)または JSON として整形出力します

以前は、raw.githubusercontent.com と api.github.com の直接 URL は github.com のみにマッチし、HTML スクレイピングの階層カスケードにフォールスルーしていました。そのカスケードは raw テキストファイルや素の JSON レスポンスをレンダリングできないため、100% 失敗していました。現在は GitHub の高速パスを利用します。

未認証のリクエストは 1 時間あたり 60 回に制限されています。GITHUB_TOKEN を設定すると、1 時間あたり 5,000 回に引き上げられます。

セキュリティ

URL の安全性(SSRF)

呼び出し元が影響を与える、または検出された URL へのすべてのアウトバウンドフェッチ(raw-HTTP 層、robots.txt / llms.txt / Wayback / sitemap プローブ、BFS クロールのリンク取得、GitHub 高速パス)は、2 つの方法で保護されています:

  1. 文字列チェック (assertPublicUrl) — 非 HTTP(S) URL とプライベート/内部 IP リテラル を拒否します: RFC1918 (10.x, 192.168.x, 172.16–31.x)、ループバック (127.x, ::1)、リンクローカル / クラウドメタデータ (169.254.x)、CGNAT (100.64/10)、IPv6 ULA (fc00::/7) とリンクローカル (fe80::/10)、IPv4 マップ、マルチキャスト/予約範囲。

  2. 接続時 DNS 検証 — 共有 undici ディスパッチャーで、その connect.lookup が 解決された アドレス(ソケットが接続する正確なアドレス)を検証します。これにより、公開ホスト名がプライベートアドレスに解決される DNS リバインディング / TOCTOU のギャップを閉じ、すべてのリダイレクトホップ で再実行されるため、リダイレクトチェーンが内部ネットワークに跳ね返ることはありません。

Firecrawl(tier1)と Crawl4AI(tier2)はターゲット URL を自分で解決して取得するため、上記の接続時ディスパッチャーではカバーできません。fetchPage と crawlSite は、いずれかのサービスにディスパッチする直前に assertResolvedPublic(url) を呼び出します。これは、プライベートまたは予約済みの結果を拒否する 1 回限りのホスト名解決であり、そのパスでの一般的な DNS リバインディングのケースを閉じます(サービスが再解決するため、接続時ガードよりも TOCTOU ウィンドウが狭い)。

設定された内部サービス(Firecrawl、Crawl4AI、SearXNG、Ollama、Reranker)は、それぞれの URL で到達され、意図的にガードされていません。

リダイレクト保護

raw-HTTP と GitHub 高速パスのフェッチは、さらに redirect: "manual" を使用し、3xx レスポンスを完全に拒否します(Location ヘッダーは呼び出し元にエコーバックされません)。リダイレクトを追跡するプローブ(robots.txt、llms.txt、sitemap)は、上記の接続時 DNS 検証でカバーされ、各ホップを再チェックします。

トランスポートの露出

stdio にはネットワーク面がありません。HTTP トランスポートはデフォルトで 127.0.0.1 にバインドされ、その構成では認証されません。SEARXNG_MCP_AUTH_TOKEN を設定せずにループバックから外すと、任意の URL の fetch_url や破壊的な clear_cache を含むすべてのツールが、そのポートにルーティングできるものに公開されます。HTTP トランスポート認証 を参照してください。

依存関係の監査

CI はプッシュのたびに pnpm audit を実行します。ロックファイル(pnpm-lock.yaml)は、再現可能で監査可能なビルドのためにコミットされています。

資格情報の取り扱い

サーバーは資格情報を保存またはログ記録しません。API キー(FIRECRAWL_API_KEY、GITHUB_TOKEN、CRAWL4AI_API_TOKEN)は環境変数から読み取られ、それぞれのサービスへのアウトバウンドリクエストでのみ使用されます。

入力検証

環境変数は起動時に検証されます。RERANK_RECENCY_WEIGHT は NaN、負の値、または 1.0 を超える値に対して警告します。数値ツールパラメータは、範囲制約付きの z.coerce.number() を使用します。

コントリビューション

セットアップ手順、コミット規約、PR プロセスについては CONTRIBUTING.md を参照してください。

統合テスト

ドメイン DB の並行性をカバーする実際の Valkey 統合スイートは VALKEY_TEST_URL でゲートされ、未設定の場合は完全にスキップされるため、Valkey が存在しなくても通常の pnpm test は動作します:

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

スクラッチデータベースインデックス を使用してください。スイートは domain:* キーを書き込みおよび削除し、安全ガードとしてインデックス 0 または 1 に対しては実行を拒否します。

ライセンス

MIT

Available Tools

7 tools
clear_cacheA

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

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

TDQS

A3.9/5.0
Behavior3/5

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

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

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

Conciseness4/5

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

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

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

Completeness4/5

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

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

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

Parameters3/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines4/5

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

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

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

crawl_siteA

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

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

TDQS

A4/5.0
Behavior4/5

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

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

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

Conciseness5/5

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

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

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

Completeness4/5

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

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

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

Parameters3/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines3/5

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

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

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

domain_statsDomain capability statsA

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

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

Output Schema

ParametersJSON Schema
NameRequiredDescription
modeYes
foundYes
recordYes
hostnameYes
aggregateYes
unavailableNo

TDQS

A4.6/5.0
Behavior4/5

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

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

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

Conciseness5/5

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

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

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

Completeness5/5

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

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

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

Parameters5/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines4/5

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

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

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

fetch_urlA

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

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

TDQS

A4.4/5.0
Behavior4/5

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

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

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

Conciseness5/5

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

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

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

Completeness4/5

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

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

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

Parameters4/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines4/5

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

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

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

search_and_fetchA

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

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

TDQS

A4.4/5.0
Behavior5/5

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

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

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

Conciseness5/5

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

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

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

Completeness4/5

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

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

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

Parameters3/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines4/5

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

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

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

search_and_summarizeA

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

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

TDQS

A5/5.0
Behavior5/5

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

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

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

Conciseness5/5

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

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

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

Completeness5/5

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

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

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

Parameters5/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines5/5

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

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

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

Tool Schema Changelog

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

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

TDQS

A4.4/5.0

Scored across 7 tools

Disambiguation5/5

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

Naming Consistency4/5

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

Tool Count5/5

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

Completeness5/5

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

Maintenance

ActivityActive
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for web search and content extraction using DuckDuckGo or SearXNG, with Playwright-based fetching and LLM-powered data extraction.
    140
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A minimal MCP server that exposes a private SearXNG instance as a search tool over streamable-HTTP, enabling web search from the llama.cpp WebUI or any compatible MCP client.
    4
    MIT
  • A
    license
    Not graded
    quality
    F
    maintenance
    Offline-first MCP server for web search and content fetching. It requires no external API keys and uses local models for intent classification, optional cross-lingual search, semantic re-ranking, and direct-answer extraction.
    ISC
  • A
    license
    Not graded
    quality
    D
    maintenance
    A fully local MCP server that provides web search via self-hosted SearXNG and page-to-markdown conversion (static and JS-rendered), all aggregated behind a single endpoint for use with AI assistants.
    MIT