Skip to main content
Glama

@rwese/web-search-mcp

Web search powered by your own SearXNG instance. Search from the terminal, from an AI agent (MCP), or from pi — every search is saved as a reusable session on disk.

New here? No install needed — run it straight from npm with npx (see below), set SEARXNG_URL, and search. The CLI guide covers everything, with example output for every command.

Install from the npm registry (bins land on your PATH):

npm install -g @rwese/web-search-mcp
export SEARXNG_URL=https://search.example.com   # required
web-search --doctor

Or run without installing (subsequent runs reuse the npx cache):

export SEARXNG_URL=https://search.example.com   # required
npx -y -p @rwese/web-search-mcp web-search --doctor

Output when your setup is healthy:

web-search doctor
✓ config: SEARXNG_URL=https://search.wze.nope.at timeout=10000ms store=/home/you/.local/share/web-search/sessions
✓ store-dir: /home/you/.local/share/web-search/sessions writable
✓ searxng: https://search.wze.nope.at reachable (32 categories, 86 enabled engines)
✓ engines: 86 enabled: wikipedia, arxiv, github, stackoverflow, youtube, ...
✓ search: probe query "test" returned 36 result(s) (3 unresponsive engine(s))
✓ llm: not configured (skipped)
6/6 checks passed

The LLM check only matters for --use-ai (see AI answers). Without LLM config it reports "skipped" and still passes.

Prefer a local checkout? git clone the repo, then pnpm install + pnpm build (or just npx web-search … inside the checkout — npx resolves the local bins with no install step).

CLI — usage

After npm install -g @rwese/web-search-mcp the web-search bin is on your PATH. Without the global install, prefix every command with npx -y -p @rwese/web-search-mcp (fetches the package on first use; subsequent runs reuse the npx cache):

export SEARXNG_URL=https://search.example.com   # required, once per shell
web-search "<query>" [flags]

Bare bin name below stands for that npx … web-search prefix:

web-search "<query>" [flags]          search (AI answer when the LLM is configured, raw with --no-ai)
web-search --session <id> [--json] [--use-ai]  re-read a saved session (raw unless --use-ai)
web-search --doctor [--json]          validate your setup
web-search --help                     show help (includes the version)
web-search --version                  print the version and exit

Flag

Meaning

--categories <csv>

restrict to categories, e.g. --categories general,news

--engines <csv>

restrict to engines, e.g. --engines wikipedia,arxiv

--language <code>

e.g. --language en, --language de

--time-range <x>

day | month | year (e.g. day for news from the last 24h)

--safesearch <n>

0 off, 1 moderate, 2 strict

--page <n>

result page number

--session <id>

show a saved session instead of searching (no query allowed)

--use-ai

answer with the AI loop (default when the LLM is configured)

--no-ai

list raw results even when the LLM is configured

--debug

verbose logging to stderr (never pollutes stdout/JSON)

--doctor

validate setup (takes no query, no other flags except --json)

--json

full structured output (default is readable markdown)

--help

show help (includes the version)

--version

print the version and exit

Exit codes: 0 success (even with zero results), 1 runtime error, 2 usage error (e.g. query combined with --session).

web-search "what is kubernetes"

Output is readable markdown showing the top 10 hits (the full result set is always saved — see sessions):

**Session:** cc6a54d5-what-is-kubernetes
## Search results for "what is kubernetes" (27)
1. [Overview - Kubernetes](https://kubernetes.io/docs/concepts/overview/)
   Kubernetes is a portable, extensible, open source platform for managing containerized workloads and services ...
   *Engines: google cse, braveapi, exaapi · Category: general*
2. [What is Kubernetes? - Red Hat](https://www.redhat.com/en/topics/containers/what-is-kubernetes)
   The core concepts of Kubernetes center around clusters, nodes, and pods working together ...
   *Engines: google cse, braveapi, exaapi · Category: general*
3. [Kubernetes - Wikipedia](https://en.wikipedia.org/wiki/Kubernetes)
   Kubernetes, also known as K8s, is an open-source container orchestration system for automating software deployment, scaling, and management. ...
   *Engines: google cse, braveapi, exaapi · Category: general*

Refine with filters — all flags combine freely:

web-search "fusion breakthrough" --categories news --time-range day --language en
web-search "kubernetes ingress" --engines stackoverflow,github --page 2

A query and --session are mutually exclusive. --doctor takes neither.

JSON output

Add --json to any search for the full structured response — every result with title, url, snippet, publishedDate, score, engines, category, plus suggestions, answers, corrections, infoboxes, and unresponsiveEngines:

web-search "what is kubernetes" --json
{
  "query": "what is kubernetes",
  "sessionId": "5416a500-what-is-kubernetes",
  "results": [
    {
      "title": "Overview - Kubernetes",
      "url": "https://kubernetes.io/docs/concepts/overview/",
      "snippet": "Kubernetes is a portable, extensible, open source platform for managing containerized workloads and services ...",
      "publishedDate": "2026-05-30T00:00:00+00:00",
      "score": 9,
      "engines": ["google cse", "braveapi", "exaapi"],
      "category": "general"
    },
    {
      "title": "Kubernetes",
      "url": "https://kubernetes.io/",
      "snippet": "Kubernetes, also known as K8s, is an open source system for automating deployment, scaling, and management of containerized applications. ...",
      "publishedDate": null,
      "score": 2.7,
      "engines": ["google cse", "braveapi", "exaapi"],
      "category": "general"
    }
  ],
  "suggestions": [],
  "answers": [],
  "corrections": [],
  "infoboxes": [],
  "unresponsiveEngines": [
    ["brave", "Suspended: too many requests"],
    ["duckduckgo", "CAPTCHA"]
  ]
}

unresponsiveEngines are normal: some backends fail non-fatally on any given search. They print as warning: lines on stderr and never pollute stdout, so --json stays machine-readable.

Sessions

Every search persists an immutable session on disk and prints its id in the **Session:** line. Re-read it any time — from any surface (CLI, MCP, pi):

NPX="web-search"
$NPX --session cc6a54d5-what-is-kubernetes
$NPX --session cc6a54d5-what-is-kubernetes --json

Sessions live under $XDG_DATA_HOME/web-search/sessions/ (fallback ~/.local/share/web-search/sessions/) in folders named <8 hex>-<query slug>, e.g. cc6a54d5-what-is-kubernetes.

AI answers

A plain web-search "<query>" answers via the plan → search → aggregate → synthesize loop whenever the LLM is configured (model + API key). The planner breaks the request into 1–5 focused search queries and picks shared categories/engines from your instance's live config. Each query runs in order and saves its own session. The top 10 results per session are aggregated into a Markdown prompt, under search-session and search-query headings, then synthesized into one answer with globally numbered footnote citations.

The markdown output prints every **Session:** id and search query plus a Raw results: web-search --session <id> hint so the raw hits stay reviewable. --json carries sessions (query/id pairs), plan (including queries), and summary; sessionId remains the first search's id for existing consumers.

Setup (needs an OpenAI-compatible endpoint in addition to SearXNG):

export OPENAI_BASE_URL=https://litellm.void.cold.at/v1
export OPENAI_MODEL=deepseek-v4-flash
export OPENAI_API_KEY=<key>   # env wins; or openai.apiKey in the XDG config file (mode 0600)
web-search "latest pi 5 news" --use-ai
**Session:** 9be21cc4-latest-pi-5-news
Search query: "latest pi 5 news"

The Raspberry Pi 5 ... [^1] ... [^2]

Sources
[^1]: [Title one](https://example.com/one)
[^2]: [Title two](https://example.com/two)

Raw results: web-search --session 9be21cc4-latest-pi-5-news

Pass --no-ai for the raw top-10 result list instead, or --use-ai to force the AI answer explicitly. --session <id> stays raw unless --use-ai is passed. Explicit flags always override the AI plan, e.g. --use-ai --language de --engines wikipedia forces those choices.

Debugging

--debug (or WEB_SEARCH_DEBUG=1) prints verbose diagnostics — request URLs and timing, session persistence, and with --use-ai the plan decision, tool calls, and model-call counts. It always goes to stderr, so piping stdout to jq keeps working.

Configuration

Highest precedence first:

  1. Environment variables / CLI flags (SEARXNG_URL, SEARXNG_TIMEOUT_MS, OPENAI_*)

  2. XDG config file $XDG_CONFIG_HOME/web-search/config.json (fallback ~/.config/web-search/config.json)

  3. Built-in defaults

$PWD/.env (gitignored, copy from .env.example) is loaded by the CLI/MCP entrypoints before startup; the core itself never loads dotenv.

Variable

Required

Default

Purpose

SEARXNG_URL

yes

SearXNG instance base URL; fails fast without it

SEARXNG_TIMEOUT_MS

no

10000

per-request timeout in ms

WEB_SEARCH_DEBUG

no

0

verbose stderr logging; 1 to enable

WEB_SEARCH_RECURSION_LIMIT

no

50

LangChain graph recursion cap for the --use-ai summarizer (default maxModelCalls * 4 + 10); raise if you hit "Recursion limit ... reached"

OPENAI_BASE_URL

for --use-ai

OpenAI-compatible endpoint

OPENAI_MODEL

for --use-ai

model name (e.g. deepseek-v4-flash)

OPENAI_API_KEY

for --use-ai

key; env wins, openai.apiKey in the XDG config file is the fallback (mode 0600, single line)

PORT

no

3000

MCP --http port when no port arg is given

XDG config file example:

{
  "searxngUrl": "https://search.example.com",
  "timeoutMs": 10000,
  "storeDir": "/custom/path/to/sessions",
  "debug": false,
  "openai": {
    "baseUrl": "https://litellm.void.cold.at/v1",
    "model": "deepseek-v4-flash",
    "apiKey": "sk-..."
  }
}

Any standard SearXNG instance works — no instance-side setup needed. The core uses the JSON API plus /config (the --use-ai planner constrains its category/engine picks to what /config actually offers).

Agent surfaces (MCP + pi)

All surfaces share the core and the session store, so sessions are interchangeable. Pick whichever your harness speaks.

1. MCP via stdio (Claude Code, opencode, pi):

{
  "mcpServers": {
    "web-search": {
      "command": "npx",
      "args": ["-y", "-p", "@rwese/web-search-mcp", "web-search-mcp"],
      "env": {
        "SEARXNG_URL": "https://search.example.com",
        "OPENAI_API_KEY": "sk-..."
      }
    }
  }
}

2. MCP via streamable HTTP (shared server for multiple agents):

web-search-mcp --http 3000   # or PORT=3000 ... web-search-mcp --http
# endpoint: POST/GET/DELETE http://localhost:3000/mcp
{
  "mcpServers": {
    "web-search": { "type": "streamable-http", "url": "http://localhost:3000/mcp" }
  }
}

3. pi extension (package.json already declares ./extensions via the "pi" key):

pi install npm:@rwese/web-search-mcp   # then use the search tool

Tool contract (search — the only tool): required query: string; optional categories: string[], engines: string[] (default: all available engines), language: string, timeRange: day|month|year, safeSearch: 0|1|2, pageNo: number, maxResults: number (default 10, max 50). Returns lean markdown: a Session: id line, then title / url / snippet / engine / category per result. Agent guidance:

  • maxResults only trims the rendered summary — the full result set stays in the persisted session on disk.

  • Re-read full detail via the CLI (--session <id>) or the session store; there is no search_details MCP tool yet (deferred until the MCP server is in active use).

  • unresponsiveEngines are data, not errors — some backends failed non-fatally.

  • For AI-answer behavior from an agent, run the CLI rather than reimplementing the loop; explicit flags always override the AI plan. When the LLM is configured the CLI answers with AI by default — pass --no-ai for raw results.

Development

See DEVELOPMENT.md for build commands, architecture, the --use-ai / --doctor internals, and contributor pointers.