/work · Published on npm
searxng-mcp-server
A privacy-first MCP server that turns a self-hosted SearXNG instance into the search backend for AI coding clients: seven tools, no API keys, SSRF guards on every redirect hop.
TypeScript · MCP SDK · SearXNG · undici · Zod · Readability
Problem
Search for an AI coding client usually means renting it: an MCP server with a paid search API behind it. Every query leaves the machine, every request costs money, and the provider sees what the agent is working on. A tool that runs inside an editor all day should not have a meter attached.
The second gap is fetching. Agents read web pages straight into their context, so whoever controls a page controls part of the prompt. And a fetch helper that follows redirects without re-checking the target is an SSRF hole that waits for one redirect to localhost.
Architecture
searxng-mcp-server is a TypeScript server on the official MCP SDK. It speaks to exactly one target: your own SearXNG instance, passed as SEARXNG_URL. stdio is the default transport; an opt-in Streamable HTTP endpoint covers remote setups. Seven tools come out of that: web search, image, news, video, music, fetch_content for pages, and list_engines, which reports the engines and categories the connected instance has enabled.
fetch_content turns HTML into clean Markdown. Readability extracts the article, linkedom parses without a browser, turndown converts. The model gets readable text with its structure intact, not tag soup.
The package carries its own icon metadata as base64 data URIs, so registry clients render it without a second request. VS Code and Cursor get one-click install buttons, Claude Code a one-liner, and a bundled docker-compose brings up the SearXNG side.
Security: every redirect hop re-checked
fetch_content validates every target against private address ranges and repeats the check at every redirect hop. A chain that starts on a public host and ends at 127.0.0.1 is rejected mid-flight, and so are DNS-rebind attempts that resolve to internal addresses.
Everything the tools return is untrusted by contract. Page content arrives wrapped and flagged with an UNTRUSTED_WEB_CONTENT marker, so the client treats it as data, not instructions. Prompt injection hidden in a fetched page loses its delivery channel.
Secrets never reach the logs, and stdout stays reserved for JSON-RPC. A debug channel that prints into the protocol stream would corrupt every client.
The opt-in HTTP transport carries its own gate: it serves the current spec revision only, stays stateless per request, authenticates with a bearer token compared timing-safe, validates Host and Origin, and refuses to start unauthenticated on a non-localhost bind.
Quality & distribution
The package ships on npm as searxng-mcp-server and runs via npx without a global install. Tags drive releases: GitHub Actions publishes with OIDC trusted publishing and provenance attestations, with no long-lived npm token anywhere.
CI holds a coverage gate at roughly 95 percent lines on a baseline that only ratchets upward, and a transport test drives a full tools/call round-trip over an in-memory transport, asserting the untrusted-content marker on the way out. prepublishOnly runs lint, typecheck, tests and build before anything leaves the machine.
Renovate moves dependencies under supply-chain presets: GitHub Actions pinned by digest, npm packages held back until they reach a minimum release age. The changelog travels inside the npm tarball, next to the code it describes.