@rwese/web-search-mcp
Agent-agnostic web search: a SearXNG-backed search core shared by a CLI, an MCP server, and a pi extension wrapper.
Package details
Install @rwese/web-search-mcp from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@rwese/web-search-mcp- Package
@rwese/web-search-mcp- Version
0.6.0- Published
- Sep 10, 2026
- Downloads
- 465/mo · 465/wk
- Author
- rwese
- License
- MIT
- Types
- extension
- Size
- 302 KB
- Dependencies
- 9 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@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), setSEARXNG_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 clonethe repo, thenpnpm install+pnpm build(or justnpx 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> [--page <n>] [--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 (search: next SearXNG page; --session: page of stored results) |
--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).
Search
web-search "what is kubernetes"
Output is readable markdown showing 15 hits per page (the full result set is
always saved — see sessions; page past it with
--session <id> --page <n>):
**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
--sessionare mutually exclusive.--doctortakes 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"]
]
}
unresponsiveEnginesare normal: some backends fail non-fatally on any given search. They print aswarning:lines on stderr and never pollute stdout, so--jsonstays 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 --page 2
$NPX --session cc6a54d5-what-is-kubernetes --json
When a session's results span more than one page (page size = maxResults,
15 by default), the markdown footer reads Showing X of Y page Z, to get further results: web-search --session <id> --page <Z+1>. A live search's
--page <n> means something else — it asks SearXNG for the next result page
(a new session), whereas paging a --session re-read walks the
already-stored results.
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 15 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 summarizer may run
additional targeted searches through its search tool (budget: 10 per answer,
configurable); the follow-up sessions and their results extend the global
numbering.
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-15 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
Interactive runs (TTY, human-readable output) print one progress line per
step to stderr — planning, each search, synthesizing — so the long --use-ai
loop is never silent. Piped stderr and --json stay silent, so scripts and
jq keep working.
--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:
- Environment variables / CLI flags (
SEARXNG_URL,SEARXNG_TIMEOUT_MS,OPENAI_*) - XDG config file
$XDG_CONFIG_HOME/web-search/config.json(fallback~/.config/web-search/config.json) - 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" |
WEB_SEARCH_MAX_RESULTS |
no | 15 |
display cap for markdown summaries and session overviews (never truncates the persisted session) |
WEB_SEARCH_MAX_AGENT_SEARCHES |
no | 10 |
follow-up search tool budget per --use-ai answer; when exhausted the agent gets search volume exhausted (HTTP 429) |
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,
"maxResults": 15,
"maxAgentSearches": 10,
"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 15, max 50). Returns lean
markdown: a Session: id line, then title / url / snippet / engine /
category per result. Agent guidance:
maxResultsonly 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 nosearch_detailsMCP tool yet (deferred until the MCP server is in active use). unresponsiveEnginesare 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-aifor raw results.
Development
See DEVELOPMENT.md for build commands, architecture, the
--use-ai / --doctor internals, and contributor pointers.