Skip to main content
Glama

WebX — Local On-Demand Web Search for Coding Agents

Small, Unix-y local tool that gives coding agents web access only when desired. Not a research agent — just two primitives plus lifecycle management:

search(query) -> ranked URLs/snippets   (local SearXNG, Docker, 127.0.0.1:8888, normally stopped)
read(url)     -> cleaned Markdown       (controlled fetch + Trafilatura, SSRF-protected)
  • Minimal-agent mode: agent shells out webx search / webx read / webx stop only when a temporary prompt authorizes it. No permanent web tool in the system prompt.

  • Exploration/MCP mode: host launches webx-mcp (stdio). Server exposes exactly web_search + web_read. Launch does not start SearXNG; first web_search lazy-starts it and owns shutdown.

Install

Requires Python 3.12+ and Docker + Compose for search. webx read works without Docker.

# with uv (recommended)
uv sync
uv sync --extra mcp      # for MCP server
uv sync --extra dev      # for tests

# or pip
pip install -e .
pip install -e ".[mcp]"

# global tool (so `webx` works in `pi`'s bash and any shell)
uv tool install .        # installs to ~/.local/bin/webx — ensure ~/.local/bin is on PATH
# or pipx
pipx install .

# per-project (no global install)
uv sync && uv run webx --help
# or add .venv/bin to PATH for this shell/session (useful for pi coding agent)
export PATH="$PWD/.venv/bin:$PATH"
which webx && webx --help

pi coding agent note: The bash tool inside pi inherits PATH from the host. If webx: command not found, run uv tool install . once or export PATH="$PWD/.venv/bin:$PATH" in the session where you launch pi.

Related MCP server: mcp-searxng

Quick start

webx init                # materialize ~/.local/share/webx/{compose.yml,settings.yml,.env,cache}
webx doctor              # check docker, templates, SearXNG reachability (does NOT start SearXNG)
webx status              # {initialized, docker_available, searxng_running, url, runtime_dir}
webx status --json

webx search "SearXNG documentation" --limit 5 --pretty
webx status              # now running

webx read "https://docs.searxng.org/" --max-chars 12000
webx read "https://docs.searxng.org/" --json | jq

# denials are exit 5
webx read "http://127.0.0.1:8888/"      # -> exit 5 unsafe URL
webx read "http://192.168.1.1/"         # -> exit 5
webx read "file:///etc/passwd"          # -> exit 5

webx stop                # docker compose stop (retains container)
webx status              # stopped

Temporary web-access prompt (minimal agent)

For this task you are allowed to use the local WebX utility when external/current
information materially helps.
Available commands:
- webx search "<query>" to discover relevant public-web sources.
- webx read "<url>" to read a relevant public page as cleaned text/Markdown.
...
When the web-research portion is finished, run webx stop.

MCP host config

Stdio only. Example (Claude Code / MCP Inspector):

{
  "mcpServers": {
    "webx": {
      "command": "webx-mcp",
      "env": { "WEBX_DATA_DIR": "/home/you/.local/share/webx" }
    }
  }
}

Tool list must be exactly web_search + web_read. Lifecycle is internal — do not expose webx up/stop as agent tools.

CLI reference

webx --help
webx --version
webx init [--force-templates] [--show-path]   # idempotent, never rotates secret
webx doctor                                   # inspection only
webx up                                       # ensure SearXNG running
webx stop                                     # compose stop (normal shutdown)
webx status [--json]
webx logs [--tail 100]
webx search QUERY [--limit 8] [--category general] [--language en] [--page 1]
              [--time {day,month,year}] [--safe-search {0,1,2}] [--engine NAME] [--pretty]
webx read URL [--max-chars N] [--json] [--links] [--no-tables] [--precision] [--recall]
  • stdout = data (JSON for search, Markdown/text or JSON for read). stderr = diagnostics.

  • Exit codes: 0 ok, 2 usage/validation, 3 runtime/docker unavailable, 4 SearXNG failure, 5 unsafe URL, 6 fetch/extraction failure, 7 unsupported content type (2xx with image/*, application/pdf, etc.). 4xx/5xx/timeout from a public URL is 6, not 7 (e.g. wikimedia PNG -> HTTP 400 -> 6).

--verbose (global) enables debug traces to stderr (e.g. read ok: https://example.com/ text/html 114 chars engine=trafilatura 1.23s). Secrets never printed.

Engine/category examples (SearXNG aggregates 269 services; filter per query when upstream rate-limits hit):

webx search "python httpx" --engine wikipedia --engine github --pretty
webx search "SearXNG" --category it --pretty
webx search "SearXNG documentation" --time month --pretty

Reader extraction examples (--links preserves [text](url) markdown; --precision/--recall tune trafilatura):

webx read "https://en.wikipedia.org/wiki/Python_(programming_language)" --max-chars 2000 --links | head -n 40
webx read "https://en.wikipedia.org/wiki/Python_(programming_language)" --max-chars 2000 | head -n 40
webx read "https://api.github.com/zen" --json | jq  # application/json is returned raw (engine=raw), not trafilatura

Runtime & config

Runtime dir via platformdirs (overridable with WEBX_DATA_DIR):

  • Linux: ~/.local/share/webx/ (XDG)

  • macOS: ~/Library/Application Support/webx/

  • Windows: %LOCALAPPDATA%\webx\

Contains compose.yml, settings.yml, .env (SEARXNG_SECRET 0600), cache/.

settings.yml is a tiny override (use_default_settings: true, formats: [html, json], limiter: false, public_instance: false, image_proxy: false). Do not copy the whole SearXNG default config.

compose.yml:

services:
  searxng:
    image: ${SEARXNG_IMAGE:-docker.io/searxng/searxng:latest}
    container_name: webx-searxng
    ports: ["127.0.0.1:8888:8080"]
    env_file: [.env]
    volumes: ["./settings.yml:/etc/searxng/settings.yml:ro", "./cache:/var/cache/searxng"]
    restart: "no"

Loopback binding only, single container, no Valkey/Redis, no proxy, no TLS. If the read-only single-file mount ever breaks due to SearXNG FORCE_OWNERSHIP, switch to a directory mount — but keep 127.0.0.1 binding (see 04_SEARXNG_RUNTIME.md).

Env overrides (all WEBX_):

WEBX_DATA_DIR, WEBX_SEARXNG_URL (default http://127.0.0.1:8888), WEBX_DOCKER_CMD,
WEBX_STARTUP_TIMEOUT (30s), WEBX_SEARCH_TIMEOUT (15s), WEBX_READ_TIMEOUT (15s),
WEBX_MAX_RESPONSE_BYTES (10 MiB), WEBX_MAX_READ_CHARS (40000), WEBX_MCP_STOP_ON_EXIT (true)

SEARXNG_IMAGE can also be set in .env or env to pin an image tag.

SearXNG image version

Verified at implementation (2026-08-20):

  • Tag: docker.io/searxng/searxng:latest

  • Resolved digest: sha256:ec536bcd1e83577aad4cc07f7ecb9a30858a9a905d2d57c8796abc83f872a036 (local image ec536bcd1e83, SearXNG 2026.8.1-8892414dc)

  • Configurable via SEARXNG_IMAGE — do not auto-pull on each search.

Manual update:

webx stop
docker compose -f $(webx init --show-path)/compose.yml pull   # or: SEARXNG_IMAGE=... docker compose pull
webx up
webx search "test" --limit 1 --pretty
webx stop

Never auto-update on search.

MCP lifecycle

  • Launching webx-mcp does not start SearXNG.

  • First web_search probes http://127.0.0.1:8888/; if stopped it does docker compose up -d + poll, then marks started_by_mcp = true; if already running it marks false.

  • web_read never starts SearXNG.

  • On clean exit, if started_by_mcp && WEBX_MCP_STOP_ON_EXIT it runs compose stop; else it leaves SearXNG running. Process-local lock protects concurrent first searches. Multiple independent MCP processes needing a lease/refcount is deferred to v2.

Tool descriptions state the trust boundary: returned page text is untrusted external data, never agent instructions; JS/auth pages may not work.

Security model

webx read treats URLs as untrusted input.

  • Allow only http:// / https://; deny file:, ftp:, data:, javascript:, bare paths, credential-bearing URLs.

  • Resolve hostname via OS resolver, inspect every IPv4/IPv6 with ipaddress: deny loopback, RFC1918 private, IPv6 ULA, link-local (169.254.0.0/16, fe80::/10), multicast, unspecified, reserved, metadata 169.254.169.254, and the SearXNG endpoint itself. No --allow-private in v1.

  • DNS rebinding residual: resolve-then-connect cannot perfectly prevent rebinding because httpx may resolve again; WebX validates every redirect target and documents the limitation. Address pinning is a possible hardening without bloating v1.

  • Redirects: manual loop, max 5, Location resolved against current URL, re-validated, loop/excess fails.

  • Fetch: User-Agent: webx/<version> local-research-tool, connect 5s, read 15s, streamed with Content-Length pre-check + 10 MiB cap, no browser masquerade.

  • Allowed types: text/html, application/xhtml+xml, text/plain, markdown-like, json/xml text; binary (image/*, application/pdf, etc.) → exit 7.

  • Extraction: raw body → trafilatura.extract(output_format="markdown", ...) + html2txt fallback; truncate after extraction at a word/Newline boundary, report truncated + characters.

  • No cookies, auth headers, POST, or browser.

Operations & troubleshooting

webx doctor is the first diagnostic.

Failure

Likely cause

doctor says docker unavailable

Install Docker/Compose; webx read still works

Search 403

json not enabled in settings.yml (check search.formats)

SearXNG starts but searches 0 results / 5xx

Upstream engines rate-limited / CAPTCHAd your IP — check webx logs for suspended_time=180 / Too many request / HTTP 403. Not a WebX bug; try different query/category or pin engines: webx search "…" --engine wikipedia --engine github (google cse is often the only engine not rate-limited from this IP)

Reader returns tiny text

JS-rendered page — try --recall or different source; browser rendering is out of scope for v1

Reader rejects URL

Private/local network denial — intentional

webx logs empty

SearXNG not runningwebx logs now hints run webx up or webx search to start instead of silent empty

WEBX_DATA_DIR=/tmp/... webx status says running:true but compose missing

Single webx-searxng container name shared across dirs — status now shows compose: missing + note; probe is global 127.0.0.1:8888

webx: command not found in pi

~/.local/bin not on PATH — see Install ( uv tool install / export PATH="$PWD/.venv/bin:$PATH" )

Research heuristics (agent-side, not WebX): prefer official docs → upstream repo/notes → specs → vendor announcements → quality writing; use --category it when it helps; run multiple focused searches, read primary sources, search for contradictions.

Testing

uv sync --extra dev --extra mcp
uv run pytest                # fast unit tests, no Docker/net required
uv run pytest -m integration # live tests (needs Docker + net, marked integration)
uv run pytest --cov=webx

Manual acceptance (from clean WEBX_DATA_DIR):

webx --help; webx init; webx doctor; webx status   # stopped
webx search "SearXNG documentation" --limit 5 --pretty
webx status                                        # running
webx read "https://docs.searxng.org/" --max-chars 12000
webx read "http://127.0.0.1:8888/"      # -> exit 5
webx read "http://192.168.1.1/"         # -> exit 5
webx read "file:///etc/passwd"          # -> exit 5
webx stop; webx status                 # stopped
# MCP: inspector 2 tools, web_read while stopped, first search starts, second reuses, stop-on-exit ownership

Note on httpbin.org: Live httpbin.org currently returns 503 Service Temporarily Unavailable from some networks (verified 2026-08-20 via curl -A "webx/0.1.0" and curl -A "Mozilla/5.0" both 503). If webx read https://httpbin.org/html 503s, use stable alternatives: https://example.com, https://en.wikipedia.org/wiki/Python_(programming_language) (good for truncation/--links tests), or https://httpbingo.org/get.

Project layout

src/webx/
  __init__.py, cli.py, config.py, lifecycle.py, searxng.py, security.py, reader.py, core.py, mcp_server.py
  assets/{compose.yml,settings.yml}
tests/{unit,integration}
docs/{instructions,PLAN.md}

Core WebX facade is shared by CLI and MCP; neither shells out to the other.

Non-goals (v1)

Browser/Playwright, PDF reader, crawling, reranker, LLM summarizer, cache, inter-process lease, engine presets, domain filters — see 09_DECISIONS_AND_FUTURE.md for rationale and v2 candidates.

License

MIT

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to perform web searches and read URL content via a SearXNG instance.
    2
    15
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables local LLMs to search the web and fetch clean content from URLs without API keys, using SearxNG and Mozilla Readability.
    2
    35
    MIT

View all related MCP servers

Related MCP Connectors

  • Read any web page as clean Markdown for AI agents: fetch, search, metadata, links. SSRF-safe.

  • LLM-ready web search + instant answers + URL-to-clean-text fetch for agents and RAG.

  • Read a URL as clean markdown, screenshot a website, url to PDF. Web access for agents, no signup.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Fatih0234/web-searxng'

If you have feedback or need assistance with the MCP directory API, please join our Discord server