searxng-mcp
searxng-mcp
자체 호스팅 SearXNG 인스턴스를 통한 비공개 웹 검색용 MCP 서버입니다. 결과는 로컬 ML 모델로 재순위화되고, 전체 페이지 콘텐츠는 Firecrawl을 통해 가져오며, 선택적 Ollama 인스턴스는 쿼리 확장 및 LLM 합성 요약을 제공합니다.
Claude Code 및 LibreChat 에이전트와 함께 사용하도록 설계되었으며, 제3자 검색 API에 쿼리를 보내지 않고 웹 검색이 필요한 경우에 적합합니다.
Claude Code와 homelab-agent의 멀티 에이전트 워크플로우로 구축되었습니다. 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-mcpFirecrawl, Crawl4AI, Ollama, Kiwix, 광고 차단 프록시 및 NATS를 포함한 전체 로컬 토폴로지는 docker-compose.full.yml을 참조하세요.
Related MCP server: searxng-mcp-bridge
도구
도구 | 설명 | 주요 매개변수 |
| 로컬 재순위화로 SearXNG를 통해 검색합니다. 더 넓은 결과 풀을 가져와 관련성별로 재순위화하고 상위 N개를 반환합니다. SearXNG의 기본 직접 답변, 정보 상자, 맞춤법 수정 및 관련 제안은 목록 위와 |
|
| 검색, 재순위화 후 가져오기 캐스케이드(Firecrawl → Crawl4AI → 원시 HTTP)를 사용하여 상위 결과의 전체 콘텐츠를 가져옵니다. |
|
| 검색, 상위 결과 가져오기 후 Ollama( |
|
| 공개 URL에서 읽을 수 있는 마크다운을 가져와 추출합니다. GitHub 호스트는 GitHub 빠른 경로를 사용하고, YouTube 비디오 URL은 트랜스크립트를 반환하며, Reddit 스레드 URL은 게시물+댓글을 반환합니다(둘 다 아래의 robots를 통해 옵트인). 그 외에는 가져오기 캐스케이드(Firecrawl → Crawl4AI → 원시 HTTP)를 사용합니다. 토큰 예산(기본 ~8,000자)으로 잘립니다. |
|
| 전체 사이트를 크롤링하고 각 페이지의 URL/제목/스니펫 매니페스트를 반환합니다. 먼저 Firecrawl 크롤링을 시도하고, 실패하면 사이트맵 파싱, 그 다음 선택적 BFS로 대체합니다. 전체 페이지 콘텐츠는 Valkey에 캐시되므로 후속 |
|
| 검색 캐시, 가져오기 캐시, 크롤링 매니페스트 캐시 또는 전체를 제거합니다. 캐시된 결과가 오래되었을 수 있는 빠르게 변화하는 주제를 조사할 때 유용합니다. |
|
| 도메인 기능 데이터베이스의 읽기 전용 보기입니다. |
|
매개변수
category — general (기본), news, it, science
time_range — day, week, month, year — 게시 날짜별로 결과를 제한합니다. 전체 기간 결과는 생략합니다.
fetch_count — 전체 콘텐츠를 가져올 상위 재순위화 결과 수 (기본 1, search_and_fetch의 최대 3; 기본 3, search_and_summarize의 최대 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). 그대로 전달됩니다. 알 수 없거나 비활성화된 엔진은 오류 대신 더 적은 결과로 저하됩니다. 세 가지 검색 도구 모두에서 사용할 수 있습니다.
site — 결과를 하나의 도메인 또는 목록(예: github.com 또는 ["github.com", "gitlab.com"])으로 제한합니다. site: 쿼리 연산자로 최선의 노력으로 적용됩니다. 대부분의 엔진(Google, Bing, DDG, Brave)은 이를 존중하지만 일부는 무시합니다. 세 가지 검색 도구 모두에서 사용할 수 있습니다.
max_tokens (fetch_url) — 반환된 콘텐츠의 대략적인 토큰 예산(문자 ≈ 토큰 × 4). 생략하면 기본 ~2,000토큰 / 8,000자; 최대 10,000토큰.
target_selector (fetch_url) — 특정 요소로 추출 범위를 지정하는 CSS 선택자(예: article, main .content). Firecrawl/Crawl4AI에서 기본적으로 지원되며 원시 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:#000000SearXNG와 Firecrawl이 필요합니다. Crawl4AI, Valkey, Ollama, Kiwix 및 재순위화기는 선택 사항입니다. 서버는 이러한 항목 중 하나라도 사용할 수 없을 때 정상적으로 저하됩니다.
광고 차단
searxng-mcp는 가져오기 계층 그룹당 하나씩 두 개의 독립적인 광고 차단 사이드카를 사용합니다:
사이드카 | 계층 | 메커니즘 |
| 계층 1 (Firecrawl) | CDP 수준 차단 — 전체 HTTPS 필터링, 동일한 브라우저 프로세스 |
| 계층 2+3 (Crawl4AI, 원시 가져오기) | HTTP 전달 프록시 — 일반 HTTP 광고 도메인 필터링 |
계층 1 — Puppeteer 광고 차단
Firecrawl이 사용하는 firecrawl-puppeteer 서비스는 업스트림 trieve/puppeteer-service-ts 위에 @ghostery/adblocker-puppeteer를 계층화하는 사용자 지정 이미지(docker/puppeteer-adblock/)를 실행합니다. EasyList + EasyPrivacy는 시작 시 로드되고 168시간마다 새로 고쳐집니다. 차단기는 Firecrawl이 생성하는 모든 페이지에 적용됩니다. 광고가 많은 사이트의 가져오기 속도를 높이고 렌더링된 DOM 크기를 줄입니다.
환경 변수:
변수 | 기본값 | 설명 |
| 설정 안 됨 |
|
| EasyList + EasyPrivacy | 쉼표로 구분된 필터 목록 URL 목록. |
|
| 차단기가 구성된 URL에서 다시 빌드하는 주기. |
기본 이미지는 SHA256 다이제스트로 고정됩니다. 변경 사항을 배포하려면 서비스를 다시 빌드하고 다시 시작합니다:
docker compose -f ~/docker/firecrawl-simple/docker-compose.yml up -d --build firecrawl-puppeteer도메인별 우회: domains.json은 향후 운영자 재정의를 위해 adblock_skip 슬롯을 예약합니다. 배선은 아직 구현되지 않았습니다. Firecrawl이 사용자 지정 헤더를 puppeteer-service로 전달해야 하며, 이는 현재 API의 일부가 아닙니다. 범위 확장 항목 I로 추적됩니다.
계층 2+3 — 광고 차단 프록시
ADBLOCK_PROXY_URL(예: http://adblock-proxy:8118)을 설정하여 광고 및 추적기 요청을 필터링하는 HTTP 전달 프록시를 통해 Crawl4AI 및 원시 Node fetch 요청을 라우팅합니다. 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 콘텐츠(URL) — application/json, 모든 *+json, XML, YAML, TOML, CSV 또는 text/plain — 는 HEAD 프로브를 통해 감지되어 전체 Firecrawl/Crawl4AI 캐스케이드 대신 원시 HTTP 계층으로 직접 라우팅됩니다. JSON은 펜스 처리된 코드 블록 안에 보기 좋게 출력되어 반환됩니다. 이전에는 헤드리스 브라우저가 JSON API 응답이나 CDN 자산을 렌더링하도록 요청하면 빈 마크다운이 반환되어 API 및 CDN 엔드포인트(registry.npmjs.org, api.osv.dev, cdn.jsdelivr.net, …)가 단순히 실패했습니다.
보장 사항:
프로브는 실패 시 열림(fail-open) 입니다. 연결할 수 없는 호스트,
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.comREADME 가져오기)를 기록합니다.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()에 의해 파이어 앤 포겟(fire-and-forget)으로 기록되므로 도메인은 가져오기 전에 추적될 수 있습니다.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.orgArticle/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.comdump-domain은 만료된 창과 데이터가 전혀 없는 계층을 구별하여 둘 다 동일하게 표시하지 않습니다.
동일한 호스트 이름에 대한 동시 업데이트(한 번의 가져오기 중에 병렬로 실행되는 계층 시도, robots 프로브, 추출 후 샘플 레코더)는 서버 측 Lua 비교-설정(CAS)을 통해 직렬화되며, 단일 프로세스의 자체 작성자 간의 경합을 제거하는 프로세스 내 키별 대기열과 쌍을 이루므로 CAS는 프로세스 간의 실제 동시 쓰기만 중재하면 됩니다. v3.17.0 이전 버전은 공유 연결에 대해 WATCH/MULTI/EXEC 읽기-수정-쓰기를 사용했지만 실제로 동시 작성자를 직렬화하지 않으므로 v3.17.0 이전에 수집된 데이터는 결과적으로 상당히 불완전했습니다. 업그레이드하면 스키마 범프를 통해 기존 계층 통계가 폐기됩니다. 업그레이드 직후 domain_stats가 거의 비어 있는 것으로 읽히고 며칠에 걸쳐 다시 채워질 것으로 예상하세요.
도메인 DB 영속성
도메인 DB는 90일 TTL과 30일 이동 창 아래 Valkey에만 존재하므로 캐시 플러시 또는 TTL 만료로 인해 다시 획득하는 데 비용이 많이 드는 기능 학습이 지워집니다. 두 개의 CLI가 이를 영구적으로 만듭니다:
pnpm domain-db-maintenance # SCAN all domain:* records → write a dated JSON snapshot (+ prune) and emit OTel gauges
pnpm restore-domain-db # re-seed the domain-db from the newest snapshot after a flushdomain-db-maintenance는 독립 실행형 작업입니다(크론 또는 PM2 크론 재시작을 통해 일정에 따라 실행 — searxng-mcp가 각각 실행하는 여러 동시 에이전트별 stdio 자식 프로세스로 실행되므로 프로세스 내 타이머가 아님). 하나의 제한된SCAN이 두 출력을 모두 제공합니다: 내구성 있는 날짜 스냅샷과OTEL_EXPORTER_OTLP_ENDPOINT가 설정된 경우 종료 전에 강제 플러시되는 게이지(searxng_domains_tracked,searxng_domains_failing,searxng_domain_tier_success_ratio{tier})입니다.restore-domain-db는 누락되었거나 라이브 레코드가 스냅샷보다 엄격히 오래된 키만 다시 시드합니다(last_fetch비교). 더 새롭거나 동일한 라이브 레코드를 덮어쓰지 않으므로 라이브, 부분적으로 채워진 Valkey(예: 자동 플러시 복구를 위한 서비스 부팅 시퀀스)에 대해 실행해도 안전합니다.
환경 변수 | 기본값 | 용도 |
|
| 날짜 스냅샷이 기록/읽히는 위치. 배포 시 내구성 있는 경로(앱데이터 또는 NFS 마운트)로 설정합니다. |
|
| 유지할 스냅샷 수. 오래된 것은 각 유지 관리 실행 시 정리됩니다. |
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 책 |
|
|
|
|
|
|
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단계 전략 캐스케이드를 사용합니다:
Firecrawl 크롤 — Firecrawl(
/crawl엔드포인트)에 크롤 작업을 보내고 완료될 때까지 폴링한 다음 전체 페이지 목록을 반환합니다.FIRECRAWL_CRAWL_POLL_INTERVAL_MS및FIRECRAWL_CRAWL_MAX_WAIT_MS로 제어됩니다.사이트맵 구문 분석 — Firecrawl이 실패하거나 빈 결과를 반환하면
/sitemap.xml(및 연결된 사이트맵)을 가져와 제목/스니펫이 있는 URL을 추출합니다. 사이트맵 XML 구문 분석에는fast-xml-parser를 사용합니다.BFS 크롤(옵트인) — 사이트맵 구문 분석도 실패하면 주어진 URL에서 시작하여
CRAWL_BFS_MAX_DEPTH링크 홉까지 너비 우선 크롤을 수행합니다.CRAWL_BFS_ENABLED=true또는bfs도구 매개변수가true일 때만 실행됩니다.
크롤 중에 가져온 전체 페이지 콘텐츠는 Valkey에 캐시됩니다(TTL: CRAWL_MANIFEST_TTL_SECONDS, 기본 6시간). 매니페스트의 모든 URL에 대한 후속 fetch_url 호출은 캐시에서 즉시 반환됩니다. 후속 읽기에 대한 가져오기 오버헤드가 0입니다.
매니페스트 캐시는 clear_cache(target="crawl")로 지울 수 있습니다.
Wayback Machine 폴백
WAYBACK_ENABLED=true이면 세 가지 주요 계층이 모두 실패할 때 네 번째 계층이 Wayback Machine CDX API에서 보관된 스냅샷을 쿼리합니다. 반환된 콘텐츠에는 출처 헤더([Archived snapshot – <timestamp> – <original_url>])가 접두사로 붙어 호출자가 콘텐츠가 현재 페이지 상태를 반영하지 않을 수 있음을 알 수 있습니다.
가져오기 품질
모든 계층이 원시 HTML이 포함된 콘텐츠를 반환한 후 추출 후 패스가 제목과 본문 품질을 개선합니다:
JSON-LD Article 추출 — Schema.org
Article/NewsArticle/BlogPosting/TechArticle블록은 계층 1 크롬 스크래핑보다 더 깨끗한headline및articleBody를 제공합니다(스크립트 태그당 1MB 크기 제한).제목 캐스케이드 —
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에 의해 제한된다(Configuration 참조). 중단되거나 CPU가 급증한 캐시 백엔드는 이제 영원히 대기하는 대신 명령을 거부한다 — 기존의 fail-soft 처리는 그 거부를 예외를 던지는 대신 캐시 미스(라이브 서빙)로 격하시킨다. 캐시 연결 실패, 클라이언트 오류, 명령별 오류는 스로틀링된[searxng-mcp]stderr 라인을 출력한다(키별로 중복 제거되어 지속적인 장애 시 홍수가 아닌 주기적인 단서를 남긴다) — stderr는 배포된 PM2 프로세스에 연결된 유일한 텔레메트리 싱크다.프로세스 크래시 핸들러 —
uncaughtException은 로그를 남긴 후 exit 1로 종료한다(깔끔한 PM2 재시작);unhandledRejection은 공유 프로세스를 조용히 크래시시키는 대신 로그를 남기고 계속 실행한다.점진적 성능 저하 경고 — reranker 폴백과 Ollama/LLM 확장 + 요약 폴백은 품질을 조용히 저하시킬 때(reranker 사용 불가, LLM 백엔드 연결 불가) 각각 스로틀링된 stderr 라인을 하나씩 출력한다.
버전은 단일 소스로 런타임 시
package.json에서 가져온다(src/version.ts) —McpServer버전, OTel tracer/meter 버전, 아웃바운드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 creds 파일) 또는 NATS_USER/NATS_PASSWORD(bcrypt 사용자 이름/비밀번호)로 인증한다 — 둘 다 설정된 경우 creds 파일 인증이 우선한다. 주제:
주제 | 발행 시점 |
| 검색 도구 호출 시 |
| 검색 반환 시(소스, 지연 시간, rerank 적용 여부 포함) |
|
|
| 티어가 빈 결과를 반환하거나 예외를 던진 경우 |
| robots.txt가 금지한 경우 |
| 페치 해결 시( |
| 모든 Valkey 조회 시 |
| 단계 태그가 지정된 오류 |
각 봉투에는 request_id와(OTel이 활성화된 경우) trace_id가 포함되어 구독자가 두 스트림을 연결할 수 있다. 주제 접두사는 NATS_SUBJECT_PREFIX로 재정의할 수 있다. 검색 쿼리는 search.* 이벤트를 통해 흐른다 — 다운스트림 소비자가 PII 정리를 책임진다.
예의
정직한 User-Agent — 아웃바운드 요청은
searxng-mcp/<version> (+https://github.com/TadMSTR/searxng-mcp; personal research)로 식별된다.robots.txt 준수 —
/robots.txt는 오리진당 한 번 가져와서 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-mcpHTTP 서버에 대해 Claude Code에 등록:
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에서 하드 상한이 적용된다 — Configuration 참조.
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을 받는다. 응답은 세 경우 모두 동일하며 제시된 자격 증명을 절대 에코하지 않는다. 토큰은 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는 의도적으로 검사에서 제외된다. 컨테이너 헬스체크이자 모니터링 활성 프로브이며, 입력을 받지 않고, 응답(status, cache, sessions)에 비밀이 포함되지 않는다.
GET /health — 인증되지 않은 활성 프로브로, MCP 엔드포인트와 함께 localhost에 바인딩된다. 제한된 캐시 명령 시간 초과를 통해 Valkey를 핑하므로(검사 자체가 절대 중단될 수 없다) 다음을 반환한다:
{"status": "ok", "cache": "up", "sessions": 3}또는 캐시 백엔드에 연결할 수 없는 경우:
{"status": "degraded", "cache": "degraded", "sessions": 3}sessions는 실시간 HTTP 세션 수다. 캐시 백엔드를 직접 계측하지 않고 MCP 측에서 저하된 캐시를 감지하는 데 유용한 시스템 관리자 모니터링 용도다.
사전 요구 사항
Node.js 20+
pnpm(또는 npm)
실행 중인 SearXNG 인스턴스
실행 중인 Firecrawl 인스턴스
Jina 호환
/v1/rerank엔드포인트를 노출하는 실행 중인 reranker(선택 사항)실행 중인 Valkey 또는 Redis 호환 인스턴스(선택 사항, 결과 캐싱용)
qwen3:4b및/또는qwen3:14b모델이 pull된 실행 중인 Ollama 인스턴스(선택 사항, 쿼리 확장 및 요약용)
SearXNG
SearXNG는 JSON 출력 형식이 활성화되어 있어야 한다. settings.yml에서:
search:
formats:
- html
- jsonReranker
reranker는 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는 HTTP를 통해 ZIM 아카이브를 제공한다. 필요한 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_allArch 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에 필요하다. 필요한 모델을 pull하라:
ollama pull qwen3:4b # query expansion
ollama pull qwen3:14b # summarizationthink: false 동작은 자동으로 처리된다 — 추가 Ollama 구성이 필요 없다.
구성
모든 서비스 URL은 환경 변수로 구성할 수 있다.
Variable | Default | Description |
|
| SearXNG 인스턴스 URL |
|
| Firecrawl 인스턴스 URL |
|
| Reranker 인스턴스 URL |
|
| Firecrawl API 키(필요한 경우) |
| (설정 안 됨) | GitHub 개인 액세스 토큰 — 요청 한도를 시간당 60회에서 5,000회로 증가 |
| (설정 안 됨) | Ollama API 기본 URL — |
| (설정 안 됨) | 인증된 Ollama 프록시용 Bearer 토큰 — 설정 시 |
|
| 쿼리 확장( |
|
|
|
| (설정 안 됨) |
|
| (설정 안 됨) | OpenAI 호환 백엔드용 모델 ID; 설정 시 |
| (설정 안 됨) | OpenAI 호환 백엔드용 Bearer 토큰 — 설정 시 |
|
| 추론 모델(예: Qwen3)이 직접 출력을 반환하도록 |
|
| Redis 호환 URL — 결과 캐싱 활성화. |
|
| Valkey 명령 타임아웃 — 중단되거나 CPU가 급증한 캐시 백엔드는 멈추는 대신 거부함( |
|
| Valkey 연결 타임아웃. |
|
| 거부 전 Valkey 명령당 최대 재시도 횟수. |
|
| 검색 결과 캐시 TTL(초) |
|
| 가져온 페이지 캐시 TTL(초) |
|
| 크롤 매니페스트 및 페이지 콘텐츠 캐시 TTL(초, 6시간) |
|
|
|
|
|
|
|
| BFS 크롤의 최대 링크 홉 깊이 |
|
| Firecrawl 크롤 작업 완료 대기 시 폴링 간격 |
|
| 사이트맵으로 대체하기 전 Firecrawl 크롤 작업 대기 최대 시간 |
|
| 쿼리 확장을 전역적으로 활성화하려면 |
| (설정 안 됨) | Crawl4AI 인스턴스 URL — Firecrawl 실패 시 2차 가져오기 대체 활성화 |
| (설정 안 됨) | API 토큰 보호가 있는 Crawl4AI 인스턴스용 선택적 Bearer 토큰 |
|
| Wayback Machine 4차 대체를 활성화하려면 |
| (설정 안 됨) | 2차(Crawl4AI) 및 3차(원시 Node fetch) 광고 차단용 HTTP 프록시 URL — 예: |
| (설정 안 됨) | kiwix-serve 기본 URL(예: |
| (설정 안 됨) | Hister 브라우징 기록 인덱스 기본 URL — 로그인 장벽 및 JS 과다 페이지에 대해 계층 캐스케이드 전에 Hister 빠른 경로 활성화. 설정하지 않으면 기능이 비활성화되고 오버헤드가 없음. |
| (설정 안 됨) | Hister API 인증용 Bearer 토큰. |
|
|
|
|
| YouTube의 |
|
|
|
|
| Reddit의 |
|
| 전송 모드: |
|
| HTTP 수신 포트(HTTP 전송 모드 전용). |
|
| HTTP 수신 주소(HTTP 전송 모드 전용). |
| (설정 안 됨) | HTTP 전송 전용. 설정 시 |
|
| HTTP 전송 전용. 이 시간보다 오래 유휴 상태인 세션은 백그라운드 정리로 제거됨(진행 중인 요청이 있는 세션은 제외되므로 긴 |
|
| HTTP 전송 전용. 하드 상한 백스톱 — 세션 맵이 이 값을 초과하면 유휴 타임아웃과 관계없이 가장 오래 사용되지 않은 유휴 세션이 제거됨. |
| (설정 안 됨) | bcrypt 사용자 이름/비밀번호 인증용 NATS 사용자 이름, |
| (설정 안 됨) | NATS 비밀번호 — |
설치
npm (권장)
npm install -g @tadmstr/searxng-mcp또는 npx로 직접 실행:
npx @tadmstr/searxng-mcp소스에서 빌드
git clone https://github.com/TadMSTR/searxng-mcp.git
cd searxng-mcp
pnpm install
pnpm build출력: build/src/index.js
MCP 클라이언트 구성
Claude Code (CLI)
권장 방법은 claude mcp add-json을 사용하여 전체 환경 변수 지원과 함께 서버를 등록하는 것입니다:
claude mcp add-json searxng --scope user '{
"command": "npx",
"args": ["-y", "@tadmstr/searxng-mcp"],
"env": {
"SEARXNG_URL": "http://localhost:8081",
"FIRECRAWL_URL": "http://localhost:3002",
"RERANKER_URL": "http://localhost:8787",
"OLLAMA_URL": "http://localhost:11434",
"CACHE_URL": "redis://localhost:6379",
"CACHE_TTL_SECONDS": "3600",
"FETCH_CACHE_TTL_SECONDS": "86400",
"EXPAND_QUERIES": "false",
"CRAWL4AI_URL": "http://localhost:11235"
}
}'이 명령은 ~/.claude.json에 기록합니다. searxng을 ~/.claude/settings.json에 추가하지 마십시오 — 해당 파일은 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:11235GitHub 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.githubusercontent.com/...) — 그대로 직접 가져옵니다API (
api.github.com/...) — 응답을 디코딩하거나(base64content필드) JSON으로 보기 좋게 출력합니다
직접 raw.githubusercontent.com 및 api.github.com URL은 이전에 github.com과만 일치하여 HTML 스크래핑 계층 캐스케이드로 넘어갔는데, 이는 원본 텍스트 파일이나 순수 JSON 응답을 렌더링할 수 없어 100% 실패했습니다. 이제 GitHub 고속 경로를 사용합니다.
인증되지 않은 요청은 시간당 60회로 제한됩니다. GITHUB_TOKEN을 설정하면 시간당 5,000회로 상향됩니다.
보안
URL 안전성 (SSRF)
호출자에 의해 영향을 받거나 발견된 URL에 대한 모든 아웃바운드 요청 — 원시 HTTP 계층, robots.txt / llms.txt / Wayback / sitemap 프로브, BFS 크롤 링크 가져오기, GitHub 고속 경로 — 은 두 가지 방식으로 보호됩니다:
문자열 검사 (
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-매핑, 멀티캐스트/예약 범위.연결 시 DNS 검증 — 공유 undici 디스패처의
connect.lookup이 해석된 주소(소켓이 실제로 연결하는 정확한 주소)를 검증합니다. 이는 공개 호스트 이름이 사설 주소로 해석되는 DNS 리바인딩 / TOCTOU 간격을 차단하며, 모든 리다이렉트 홉에서 다시 실행되므로 리다이렉트 체인이 내부 네트워크로 튈 수 없습니다.
Firecrawl (tier1) 및 Crawl4AI (tier2)는 대상 URL을 자체적으로 해석하고 가져오므로 위의 연결 시 디스패처가 이들을 커버할 수 없습니다. fetchPage와 crawlSite는 두 서비스 중 하나로 디스패치하기 직전에 assertResolvedPublic(url) — 사설/예약 결과를 거부하는 일회성 호스트 이름 해석 — 을 호출하여 해당 경로의 일반적인 DNS 리바인딩 사례를 차단합니다(연결 시 가드보다 좁은 TOCTOU 창, 서비스가 다시 해석하므로).
구성된 내부 서비스(Firecrawl, Crawl4AI, SearXNG, Ollama, Reranker)는 자체 URL로 접근되며 의도적으로 보호되지 않습니다.
리다이렉트 보호
원시 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 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