MCP-WebSearch-SearXNG
Self-hosted SearXNG metasearch + MCP server. Privacy-respecting web search and image retrieval for LLMs, agents, and tool-using applications. HTTP Streamable transport, multi-query parallel search, base64 image rendering with smart resizing.
Documentation
WebSearch SearXNG + MCP Server
A self-hosted SearXNG metasearch engine paired with the `mcp-searxng` MCP server, built for LLMs, agents, and tool-using applications. Lets your model (or autonomous agent) browse the web, run searches across many engines at once, and surface images directly in your front end. Runs entirely on your own machine via Docker, accessed from your browser, and speaks MCP over HTTP Streamable transport so it plugs into any MCP-compatible client (Claude Desktop, Cline, Open WebUI, LM Studio, custom agent frameworks, etc.). Enhanced with image/video/category search, offset pagination, and smart base64 image resizing for vision-capable models. Both services stay on your local network, so nothing leaves your box unless you ask it to. No ads, no telemetry, no third-party trackers, no accounts — the SearXNG frontend is privacy-by-design and the MCP patches add zero phone-home of their own.
SearXNG frontend — the privacy-respecting metasearch UI at localhost:8888. Same backend the MCP server queries internally.
Autonomous tool chain — model fires multiple searxng_web_search calls in parallel, then chains into web_url_read to pull full article text.
Clean text extraction — long-form article content pulled by web_url_read, boilerplate stripped, ready for the model to summarize.
Inline image in news briefing — image fetched and resized through web_url_read's sharp pipeline. No external CDN, served as a base64 MCP image block.
Multi-query parallel search — queries separated by | fan out several searxng_web_search calls at once, landing on the "It's Gonna Be May" meme.
Native image blocks — multiple images returned inline. The detail parameter (low/medium/high) controls resize quality vs. token cost.
Installation
> Quick note on privacy before you start. This stack runs entirely on your machine and is privacy-respecting by design (no telemetry, no accounts, no third-party trackers), but the defaults are tuned for "just works," not maximum privacy. Before exposing it beyond your own computer, to your LAN, the internet, or anyone else please read **ADVANCED.md**
Prerequisites
- Docker and Docker Compose must be installed
- Node.js >= 20 must be installed
Step 1: Clone the Repository
git clone https://github.com/hypersniper05/MCP-WebSearch-SearXNG.git
cd MCP-WebSearch-SearXNGThis gives you the `docker-compose.yml`, the SearXNG `settings.yml`, the MCP patches, and the custom `Dockerfile` already laid out — so the next step is straight to configuration.
> Prefer to set everything up by hand instead of cloning? See **Building from scratch (without cloning)** in ADVANCED.md.
Step 2: Configure SearXNG Settings
The shipped `searxng/config/settings.yml` is already preconfigured for this stack — JSON output enabled, rate limiter off, fast engine retries, a curated set of engines, and engine suspension times tuned so a blocked engine is not retried on every single query. The only thing you need to touch here is the `secret_key`.
> Want to change which engines are enabled, the request timeout, or any other SearXNG default? See **Modifying the default settings** in ADVANCED.md.
Set the `secret_key` (or use the env var)
The shipped `settings.yml` has a placeholder `secret_key: "CHANGE_ME_BEFORE_RUNNING"`. SearXNG will start with the placeholder, so for a localhost-only personal instance you can leave it and come back to this later. But if you ever bind to `0.0.0.0`, expose this to your LAN, or share it with anyone, generate a real one — otherwise an attacker can forge image-proxy and CSRF tokens against your instance.
Two ways to set it (pick one):
Option A — env var (recommended):
Create a `.env` file next to `docker-compose.yml`:
SEARXNG_SECRET=The env var overrides whatever is in `settings.yml`, so the placeholder can stay in the tracked file forever.
Option B — edit `settings.yml` directly:
Replace the placeholder with your generated key. If you do this, make sure not to leak your `settings.yml`.
Generate a key with whichever tool you have installed:
# PowerShell (Windows — built in)
[System.BitConverter]::ToString([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)).Replace('-','').ToLower()# Node.js (already installed for the MCP server)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Docker (works anywhere this stack does)
docker run --rm alpine sh -c "apk add --no-cache openssl > /dev/null && openssl rand -hex 32"
# Git Bash / Linux / macOS
openssl rand -hex 32
# Python
python -c "import secrets; print(secrets.token_hex(32))"For the rest of the privacy/hardening trade-offs (LAN exposure, MCP auth, Tor routing), see **ADVANCED.md**.
Step 3: Build and Start Everything
docker compose build
docker compose up -dStep 4: Verify Services
Health check:
curl -s http://localhost:3001/healthExpected: `{"status":"healthy","server":"ihor-sokoliuk/mcp-searxng","version":"0.9.2-enhanced","transport":"http"}`
Test search:
curl -s "http://localhost:8888/search?q=test&format=json" | head -c 200Step 5 (only if accessing from another machine): Open the bindings to your LAN / Tailscale
By default the shipped `docker-compose.yml` binds the services to `127.0.0.1` — reachable only from the machine running Docker. If you want another device on your LAN, your Tailscale tailnet, or anywhere else off-host to reach the SearXNG UI or the MCP endpoint, you have to change the bindings to `0.0.0.0`.
Quick diagnostic to spot this:
docker compose psLook at the PORTS column. If you see:
| What you see | What it means |
|---|---|
| `0.0.0.0:8888->8080/tcp` | Reachable from anywhere on the network ✓ |
| `127.0.0.1:8888->8080/tcp` | Localhost only — the *exact* symptom that makes external access "time out" |
If both rows show `127.0.0.1:` (or one does and one doesn't, like SearXNG locked down while MCP is open), edit `docker-compose.yml` and change the binding(s):
services:
searxng:
ports:
- "0.0.0.0:8888:8080" # was 127.0.0.1:8888:8080
mcp-searxng:
ports:
- "0.0.0.0:3001:3001" # was 127.0.0.1:3001:3001Then a full down + up is required (port changes don't apply on a plain `restart`):
docker compose down
docker compose up -d
docker compose psBoth rows should now show `0.0.0.0:...`.
If `docker compose ps` already shows `0.0.0.0` and external access *still* fails, it's the OS firewall. On Windows, allow the ports from an admin PowerShell:
New-NetFirewallRule -DisplayName "MCP SearXNG (8888)" -Direction Inbound -Protocol TCP -LocalPort 8888 -Action Allow
New-NetFirewallRule -DisplayName "MCP Server (3001)" -Direction Inbound -Protocol TCP -LocalPort 3001 -Action Allow> Security note: switching to `0.0.0.0` exposes the services to anyone who can reach your machine on those ports. The MCP server has no authentication. If you're going beyond a trusted home/Tailscale network, read ADVANCED.md → Tier 2 for adding a reverse proxy with auth.
Endpoints Summary
| Service | URL | Purpose |
|---|---|---|
| SearXNG UI | http://localhost:8888 | Web search interface |
| SearXNG API | http://localhost:8888/search | JSON search API |
| MCP Server | http://localhost:3001/mcp | MCP Streamable HTTP |
| MCP Health | http://localhost:3001/health | Health check endpoint |
> Need TLS for an HTTPS-only client? The cleanest path is **Tailscale Serve** if both ends are on a tailnet (real Let's Encrypt-trusted cert, zero per-client setup), or a reverse proxy like Caddy / nginx with your own cert otherwise. Either is added in front of the existing HTTP MCP endpoint without any changes to this stack.
MCP Tools
1. searxng_web_search
Web search with category, pagination, and filtering support.
| Parameter | Type | Description |
|---|---|---|
| `query` | string | Search query (required) |
| `categories` | string | `general`, `images`, `videos`, `news`, `music`, `files`, `it`, `science`, `social media`, `map` |
| `max_results` | number | Results per batch (default: 20 general, 10 images/videos) |
| `offset` | number | Skip N results for pagination (e.g., 10 for results 11-20) |
| `pageno` | number | SearXNG page number |
| `time_range` | string | `day`, `month`, `year` |
| `language` | string | Language code (e.g., `en`, `fr`) |
| `safesearch` | number | `0` (none), `1` (moderate), `2` (strict) |
2. web_url_read
Fetches and reads URLs. Auto-detects image URLs and returns resized base64 image blocks.
| Parameter | Type | Description |
|---|---|---|
| `url` | string | URL to read (required) |
| `detail` | string | Image resize level: `low` (448px, ~256 tokens), `medium` (768px, ~756 tokens, default), `high` (1280px, ~2048 tokens) |
| `startChar` | number | Character offset for text pagination |
| `maxLength` | number | Max characters to return |
| `section` | string | Extract content under a heading |
| `paragraphRange` | string | Paragraph range (e.g., `1-5`) |
| `readHeadings` | boolean | Return headings only |
Image Search Flow
1. `searxng_web_search({query: "sunset", categories: "images"})` → returns image URLs + metadata
2. Pick an image → `web_url_read({url: "https://example.com/sunset.jpg", detail: "medium"})` → returns resized base64 image
3. To see more results → `searxng_web_search({query: "sunset", categories: "images", offset: 10})` → results 11-20
Custom Patch Files
All patches are in `mcp-searxng-patches/` and mounted read-only into the container:
| File | Purpose |
|---|---|
| `Dockerfile` | Extends base image with `sharp` for image resizing |
| `types.js` | Tool schemas with `categories`, `offset`, `max_results`, `detail` parameters |
| `search.js` | Category-aware search formatting, offset pagination, image/video result fields |
| `url-reader.js` | `fetchImage()` with sharp resizing (3 detail presets), base64 MCP image blocks |
| `index.js` | Tool handler wiring, image URL detection, detail passthrough |
Updating SearXNG
SearXNG is pinned to `:latest` in `docker-compose.yml`, so updates are pull-and-restart. Config in `searxng/config/settings.yml` is volume-mounted and survives the update.
Manual update (recommended)
cd MCP-WebSearch-SearXNG
docker compose pull searxng # grab newest :latest image
docker compose up -d searxng # recreate container with new image
docker compose logs searxng --tail 100 # check for config-schema warnings
docker image prune -f # remove the old imageMajor releases occasionally rename keys in `settings.yml` or add required fields. Check the SearXNG releases page for breaking changes before updating.
Pin to a specific version (safer / reproducible)
Replace `:latest` in `docker-compose.yml` with a date-tagged release, e.g.:
image: docker.io/searxng/searxng:2026.4.15-abc1234Updates then become an explicit edit — review release notes, bump tag, `docker compose up -d`.
Automatic updates
| Option | Pros | Cons |
|---|---|---|
| Watchtower container | Set-and-forget | Silent break on config-schema changes |
| Scheduled `docker compose pull && up -d` (Task Scheduler or `/schedule` agent) | You control cadence | Same schema-break risk |
| GitHub release notifications + manual update | Safest | Slowest |
A reasonable middle ground: schedule a weekly job that runs `docker compose pull` and reports whether a new image is available, but does not auto-apply it.
Updating the MCP server
`mcp-searxng` is built locally from `mcp-searxng-patches/Dockerfile` with custom JS patches
mounted on top. The base image is pinned to `isokoliuk/mcp-searxng:1.16.0` — see the
comment in that Dockerfile before changing it.
After pulling this repo on another machine, or to pick up upstream MCP changes:
docker compose build --pull mcp-searxng
docker compose up -d --force-recreate mcp-searxng`--force-recreate` is not optional. A plain `docker compose up -d` rebuilds the image but
leaves the existing container running on the old image, so the update silently does
nothing. Symptom: the container crash-loops with
`ERR_MODULE_NOT_FOUND: Cannot find package '@modelcontextprotocol/sdk'` while SearXNG
itself keeps working fine.
Confirm the container is actually on a freshly built image — these two ids must match:
docker inspect mcp-searxng --format '{{.Image}}' && docker images --no-trunc --format '{{.ID}}' mcp-searxng-enhanced:latestAnd confirm which base it was built on. `sdk` is correct; `core node server` means the
image is stale and still on the 2.x base:
docker run --rm --entrypoint sh mcp-searxng-enhanced:latest -c "ls /app/node_modules/@modelcontextprotocol/"If upstream renames internal modules or changes function signatures, the volume-mounted JS patches may need to be rebased manually.
Tested With
- Qwen3.6 35B A3B — runs both `searxng_web_search` (including multi-query and category filters) and `web_url_read` (text + image modes) reliably. Tool selection, parameter inference, and result interpretation all work well with this model.
Notes
- MCP server uses HTTP Streamable transport (not stdio or SSE-only)
- SearXNG connects internally via Docker hostname `searxng:8080`
- All data stays local — no external API keys needed
- Custom patches are MIT-licensed, audited, no filesystem/shell access, no telemetry
- Search engines: Bing, Yahoo, mwmbl, Seznam and Wiby are the general-web
engines enabled by default, plus Wikipedia and WolframAlpha for direct
answers. Brave, DuckDuckGo, Startpage and Mojeek stay configured but are
frequently blocked — see below.
- If results look thin, it is usually your exit IP, not this stack. Brave
(HTTP 429), DuckDuckGo and Startpage (CAPTCHA), Mojeek (empty result page)
and Ecosia (HTTP 403) all reject self-hosted instances on shared or flagged
addresses — VPN exits, CGNAT and datacenter ranges are the common causes.
Every one of them fails on the very first query from a cold container, so it
is not a rate-limiting problem you can tune your way out of. Check what
SearXNG reports:
curl -s "http://localhost:8888/search?q=test&format=json" | jq .unresponsive_enginesOptions, roughly in order of effort: search from a residential IP without a
VPN, put the outgoing requests behind proxies (`outgoing.proxies` in
`searxng/config/settings.yml`), or supply an API key for an engine that sells
one (`braveapi`). Adding more blocked engines does not help.
Frequently asked questions
What is MCP-WebSearch-SearXNG?
MCP-WebSearch-SearXNG is Self-hosted SearXNG metasearch + MCP server. Privacy-respecting web search and image retrieval for LLMs, agents, and tool-using applications. HTTP Streamable transport, multi-query parallel search, base64 image rendering with smart resizing.
How do I install MCP-WebSearch-SearXNG?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is MCP-WebSearch-SearXNG open source?
Yes — it is hosted on GitHub at https://github.com/hypersniper05/MCP-WebSearch-SearXNG.
Related MCP tools
🔥 Official Firecrawl MCP Server - Adds powerful web scraping and search to Cursor, Claude and any other LLM clients.
Use any LLMs (Large Language Models) for Deep Research. Support SSE API and MCP server.
Enhanced MCP server for interactive user feedback and command execution in AI-assisted development, featuring dual interface support (Web UI and Desktop Application) with intelligent environment detection and cross-platform compatibility.
A powerful Zotero AI and MCP plugin with ChatGPT, Gemini 3.7, Claude Fable 5, Claude Opus 5, DeepSeek V4, Grok, OpenRouter, Kimi k3, GLM 5.3, SiliconFlow, GPT-oss, Gemma 4, Qwen 3.8
Connect your browser to AI models. Just use Dia on Chrome, Arc or Firefox.
文颜 MCP Server 可以让 AI 自动将 Markdown 文章排版后发布至微信公众号。
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP