# SearXNG MCP Server A Model Context Protocol (MCP) server that provides web search capabilities by integrating with a SearXNG instance. ## Features - **Web Search**: Perform powerful aggregated searches across multiple engines. - **Discovery**: Programmatically retrieve available categories and engines. - **Stateless HTTP**: Compatible with any standard JSON-RPC client. - **Flexible Configuration**: Supports environment variables and command-line arguments. ## Example of compose.yml to run SearXNG with MCP server ```yaml services: searxng: image: searxng/searxng:latest ports: - "${SEARXNG_PORT:-8080}:8080" volumes: - "${SEARXNG_VOL_CONFIG:-searxng-config}:/etc/searxng/" - "${SEARXNG_VOL_DATA:-searxng-data}:/var/cache/searxng/" restart: always searxng-mcp: image: ghcr.io/aicrafted/searxng-mcp:latest restart: unless-stopped depends_on: # Ensure SearXNG starts before the MCP server - searxng environment: SEARXNG_URL: "${SEARXNG_URL:-http://searxng:8080}" MCP_HOST: "${MCP_HOST:-127.0.0.1}" MCP_PORT: "${MCP_PORT:-32123}" MCP_TRANSPORT: "${MCP_TRANSPORT:-http}" MCP_ALLOWED_HOSTS: "${MCP_ALLOWED_HOSTS:-localhost:*,127.0.0.1:*}" MCP_ALLOWED_ORIGINS: "${MCP_ALLOWED_ORIGINS:-http://localhost:*,http://127.0.0.1:*}" MCP_DISABLE_DNS_REBINDING_PROTECTION: "${MCP_DISABLE_DNS_REBINDING_PROTECTION:-false}" ports: - "${MCP_PORT:-32123}:${MCP_PORT:-32123}" volumes: searxng-config: searxng-data: ``` > **Important:** Enable JSON responses in your SearXNG `settings.yml`, otherwise the MCP server cannot read search results: > > ```yaml > search: > formats: > - html > - json > ``` ### Example `.env` ```env # SearXNG url should be visible by the MCP server inside docker, so use internal service port here SEARXNG_URL=http://searxng:8080 # Public SearXNG port SEARXNG_PORT=8080 # Searxng config and data volumes, start with "./" if You want to bind dir instead using volume SEARXNG_VOL_CONFIG=searxng-config SEARXNG_VOL_DATA=searxng-data # MCP server host, port and transport ("stdio", "sse", "http") MCP_HOST=127.0.0.1 MCP_PORT=32123 MCP_TRANSPORT=http # MCP DNS rebinding protection (see https://github.com/modelcontextprotocol/python-sdk/issues/1798 for details) MCP_ALLOWED_HOSTS=localhost:*,127.0.0.1:* MCP_ALLOWED_ORIGINS=http://localhost:*,http://127.0.0.1:* # MCP_DISABLE_DNS_REBINDING_PROTECTION=true ``` ## MCP client config ### HTTP transport (recommended) ```JSON { "mcpServers": { "searxng": { "type": "http", "url": "http://localhost:32123/mcp" } } } ``` ### SSE transport ```JSON { "mcpServers": { "searxng": { "type": "sse", "url": "http://localhost:32123/sse" } } } ``` > **Note:** SSE transport uses the `/sse` endpoint, not `/mcp`. HTTP transport uses `/mcp`. ## Prerequisites for run from sources - Python 3.10+ - A running [SearXNG](https://github.com/searxng/searxng) instance. ## Installation 1. Clone the repository and navigate to the directory. 2. Install dependencies: ```bash pip install -r requirements.txt ``` 3. Set up your `.env` file (optional). ## Configuration The server reads configuration from command-line arguments and environment variables. Command-line arguments override the corresponding defaults used at startup. | Variable | Default | Description | | :--- | :--- | :--- | | `SEARXNG_URL` | `http://localhost:8080` | URL of the SearXNG instance. | | `SEARXNG_PORT` | `8080` | Public host port for the SearXNG container in the compose example. | | `SEARXNG_VOL_CONFIG` | `searxng-config` | Docker volume or host path mounted to `/etc/searxng/` in the compose example. | | `SEARXNG_VOL_DATA` | `searxng-data` | Docker volume or host path mounted to `/var/cache/searxng/` in the compose example. | | `MCP_HOST` | `127.0.0.1` | Host to bind for HTTP/SSE transports. Use `0.0.0.0` in Docker when publishing the port. | | `MCP_PORT` | `8000` | Port to bind for HTTP/SSE transports. | | `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio`, `http`, or `sse`. | | `MCP_ALLOWED_HOSTS` | SDK defaults for localhost | Comma-separated allowed `Host` headers for DNS rebinding protection. | | `MCP_ALLOWED_ORIGINS` | SDK defaults for localhost | Comma-separated allowed `Origin` headers for DNS rebinding protection. | | `MCP_DISABLE_DNS_REBINDING_PROTECTION` | `false` | Set to `true` to disable the SDK DNS rebinding protection. | ## Usage Run the server using `uv` or standard python: ```bash python searxng_mcp.py --transport http --port 32123 --searxng http://searx.lan ``` ### Run with Docker 1. **Build the image**: ```bash docker build -t searxng-mcp . ``` 2. **Run the container**: ```bash docker run -d \ -p 32123:32123 \ --env-file .env \ --name searxng-mcp \ searxng-mcp ``` ### Transport Options - `stdio`: Standard input/output (default for some MCP clients). - `http`: Stateless HTTP (streamable-http). - `sse`: Server-Sent Events. ### DNS Rebinding Protection Recent versions of the MCP Python SDK validate `Host` and `Origin` headers for HTTP/SSE transports to protect local servers from DNS rebinding attacks. If you expose the server through Docker, a reverse proxy, or a custom domain and receive `421 Invalid Host Header`, configure the allowlist explicitly: ```env MCP_ALLOWED_HOSTS=localhost:*,127.0.0.1:*,mcp.example.com:* MCP_ALLOWED_ORIGINS=http://localhost:*,http://127.0.0.1:*,https://mcp.example.com ``` For trusted local development or when this validation is handled by another infrastructure layer, you can disable the SDK protection: ```env MCP_DISABLE_DNS_REBINDING_PROTECTION=true ``` Use disabling sparingly; setting `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` is the recommended option. --- # Search Abilities Guide SearXNG aggregates results from various sources. This guide outlines the capabilities available through the `web_search` tool. ## Search Categories Categories help refine your search by content type. Use these in the `categories` parameter (comma-separated). | Category | Description | | :--- | :--- | | `general` | Default web search (Google, Brave, DuckDuckGo, etc.) | | `images` | Image search results | | `videos` | Video content from YouTube, Vimeo, etc. | | `news` | Recent news articles | | `map` | Geographical and map information | | `it` | IT-related searches (StackOverflow, GitHub, etc.) | | `science` | Scientific papers and articles (ArXiv, Google Scholar) | | `files` | Torrent and file searches | | `social_media` | Posts and profiles from social platforms | ## Supported Engines SearXNG can query over 130 engines. Configured engines typically include: - **Web**: Google, Brave, DuckDuckGo, Qwant, Startpage - **Knowledge**: Wikipedia, Wikidata - **Development**: GitHub, StackOverflow, PyPI - **Social**: Reddit, Twitter/X ## Advanced Search Parameters - **`categories`**: Filter by specific types (e.g., `news,it`). - **`engines`**: Force specific engines (e.g., `google,wikipedia`). - **`language`**: Specify search language (e.g., `en`, `es`, `fr`). - **`pageno`**: Navigate through multiple pages of results. - **`time_range`**: Filter by date (`day`, `month`, `year`). - **`safesearch`**: Control content filtering (0=None, 1=Moderate, 2=Strict). ## Programmatic Discovery Use the `web_search_info` tool to dynamically retrieve the list of enabled categories and engines from your instance. # Windows Troubleshooting ## localhost not reachable while Docker container is running **Symptom:** `http://localhost:/` returns connection refused or hits the wrong service, but `curl` from inside the container works fine. **Root cause: WSL2 port relay ghost** WSL2 automatically forwards ports from the Linux VM to the Windows host using `wslrelay.exe`. When a process inside WSL listens on a port, WSL creates a relay bound to `[::1]:` (IPv6 loopback) on the Windows side. When that WSL process stops, `wslrelay.exe` often **does not release the port**. The relay entry stays alive as a zombie listener on `[::1]:`. Later, when Docker maps a container to the same host port, it binds correctly to `0.0.0.0:` — but `[::1]:` is already taken by the stale relay. On Windows, `localhost` resolves to `::1` (IPv6) first. So browser and curl requests to `localhost:` hit the dead `wslrelay.exe` entry instead of the Docker container, resulting in a connection error or unexpected response. Connecting via the explicit IPv4 address `127.0.0.1:` bypasses the relay and reaches Docker correctly. **How to diagnose:** ```powershell # Check what is listening on the port netstat -ano | findstr : # Identify the processes Get-Process -Id , | Select-Object Id,Name ``` If you see two entries for the same port — one owned by `com.docker.backend` and another by `wslrelay` — this is the problem. **Workarounds:** | Option | Command | Notes | |--------|---------|-------| | Use IPv4 directly | `http://127.0.0.1:/` | Immediate, no restart needed | | Restart WSL | `wsl --shutdown` | Kills all stale relays; WSL restarts on next use | | Remap Docker port | Change host port in `docker run -p` or `docker-compose.yml` | Avoids the conflict entirely | **Permanent fix:** After `wsl --shutdown`, restart the Docker container. The relay will no longer exist and `localhost:` will work normally until the same port is reused inside WSL again. **Prevention:** If you regularly run services on the same port both in WSL and in Docker, prefer one of: - Always use Docker for that service, never WSL directly - Use different ports for WSL dev and Docker prod instances - Add `127.0.0.1::` explicit binding in `docker-compose.yml` to force IPv4 --- ## Related - [WSL2 networking documentation](https://learn.microsoft.com/en-us/windows/wsl/networking) - WSL GitHub issue tracker: search `wslrelay port leak`