Skip to main content
Glama
remon-rakibul

web-search-mcp

README.md
# web-search-mcp

A [FastMCP](https://gofastmcp.com/) server that exposes **free web search** to any
MCP client (Claude Desktop, Claude Code, etc.), backed by a self-hosted
[SearXNG](https://docs.searxng.org/) metasearch instance.

It provides a single tool, `web_search`, which returns a list of
`{title, url, content}` results.

## Why self-host SearXNG?

SearXNG's JSON API (`/search?format=json`) is disabled by default, and virtually
all public instances keep it disabled (they return `403`). This repo runs your
own instance — natively (no Docker) or via Docker — with the JSON format enabled
in `searxng/settings.yml`.

## Prerequisites

- Python 3.10+ (to run the MCP server)
- `git` (to fetch SearXNG for the local run)
- Optionally [`uv`](https://docs.astral.sh/uv/) — handy but not required; you can
  use plain `pip` + `python` instead (see below)

## Quickstart (local, no Docker)

### 1. Install SearXNG (one-time)

```bash
cd /home/remon/Documents/web-search-mcp

git clone --depth 1 https://github.com/searxng/searxng.git .searxng-src
python3 -m venv .searxng-src/venv
.searxng-src/venv/bin/pip install -r .searxng-src/requirements.txt
.searxng-src/venv/bin/pip install -e .searxng-src --no-build-isolation
```

> The two-step install matters: SearXNG's build imports the package, so the
> runtime requirements must be present **before** the editable install, hence
> `--no-build-isolation`.

### 2. Start SearXNG

```bash
./run-searxng.sh
```

This serves SearXNG on `http://127.0.0.1:8080` using `searxng/settings.yml`
(which has the JSON API enabled). Leave it running in its own terminal.

Verify the JSON API in another terminal:

```bash
curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8080/search?q=fastmcp&format=json'
# expect: 200
```

### 3. Install the MCP server's dependencies

Pick **one** approach:

```bash
# A) plain pip + python (e.g. into your active conda/venv)
pip install -r requirements.txt

# B) uv (creates a managed .venv automatically)
uv sync
```

### 4. Run the MCP server

```bash
# matches whichever you chose above:
python server.py        # if you used pip
uv run server.py        # if you used uv
# or:  fastmcp run server.py:mcp
```

It starts on **stdio** transport and waits for an MCP client — it prints little;
that's normal. By default it talks to `http://localhost:8080`; override with the
`SEARXNG_URL` env var.

> The deps are just `fastmcp` and `httpx` (see `requirements.txt`). With them
> installed in whatever Python is on your `PATH`, plain `python server.py` works
> — `uv` is optional.

### Quick test without an MCP client

With SearXNG running:

```bash
python -c "import asyncio, server; print(asyncio.run(server.web_search('claude code', num_results=3)))"
```

## Alternative: Docker

If you prefer Docker, skip the local install and use the bundled compose file:

```bash
docker compose up -d
```

Then run the MCP server (step 3 above). Same `searxng/settings.yml` is mounted
into the container.

## Connecting an MCP client

Example client config (e.g. Claude Desktop `mcpServers`) — using plain `python`
(deps installed via `pip install -r requirements.txt`):

```json
{
  "mcpServers": {
    "web-search": {
      "command": "python",
      "args": ["/home/remon/Documents/web-search-mcp/server.py"],
      "env": { "SEARXNG_URL": "http://localhost:8080" }
    }
  }
}
```

> Use an absolute path to the `python` that has the deps (e.g.
> `/home/remon/miniconda3/bin/python`) if `python` on the client's `PATH` differs.
> Prefer `uv`? Swap to `"command": "uv"`, `"args": ["run", "--directory",
> "/home/remon/Documents/web-search-mcp", "server.py"]`.

For Claude Code (pip/python):

```bash
claude mcp add web-search -e SEARXNG_URL=http://localhost:8080 \
  -- python /home/remon/Documents/web-search-mcp/server.py
```

## Connecting coding agents over HTTP (Cursor, Windsurf, …)

Run the server in HTTP mode and point editors at its URL. **Two processes must
be running** (two terminals):

```bash
# Terminal 1 — search backend
./run-searxng.sh                 # SearXNG on http://127.0.0.1:8080

# Terminal 2 — MCP server over HTTP
./run-mcp.sh                     # MCP endpoint at http://127.0.0.1:8000/mcp
```

`run-mcp.sh` just sets `MCP_TRANSPORT=http` (port `8000`, path `/mcp`, talking to
`SEARXNG_URL`). Override with `MCP_PORT=… ./run-mcp.sh` if 8000 is taken.

### Cursor — `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project)

```json
{
  "mcpServers": {
    "web-search": {
      "url": "http://127.0.0.1:8000/mcp"
    }
  }
}
```

### Windsurf — `~/.codeium/windsurf/mcp_config.json`

```json
{
  "mcpServers": {
    "web-search": {
      "serverUrl": "http://127.0.0.1:8000/mcp"
    }
  }
}
```

> Other clients (Claude Code, Cline, Zed, …) use the same idea — give them the
> URL `http://127.0.0.1:8000/mcp`. For Claude Code:
> `claude mcp add --transport http web-search http://127.0.0.1:8000/mcp`.
> After editing a config, restart / refresh the editor's MCP servers, then ask
> it to search the web — it should call the `web_search` tool.

No auth is needed for a localhost endpoint, so no `headers` block. If you expose
it beyond localhost, put it behind a reverse proxy with a bearer token (see
[DEPLOYMENT.md](DEPLOYMENT.md)) and add a matching `"headers": {"Authorization":
"Bearer …"}` to the client config.

## Using it in a LangGraph / LangChain agent

Bridge the MCP server into LangChain tools with
[`langchain-mcp-adapters`](https://github.com/langchain-ai/langchain-mcp-adapters),
then hand them to a LangGraph agent. A complete, runnable example is in
[`examples/langgraph_agent.py`](examples/langgraph_agent.py).

```bash
pip install langgraph "langchain[anthropic]" langchain-mcp-adapters
export ANTHROPIC_API_KEY=sk-ant-...     # for the Claude model
./run-searxng.sh                        # SearXNG must be running (separate terminal)

python examples/langgraph_agent.py "what is the model context protocol?"
```

The essence:

```python
from langchain.chat_models import init_chat_model
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent

client = MultiServerMCPClient({
    "web-search": {
        "command": "python",
        "args": ["/home/remon/Documents/web-search-mcp/server.py"],
        "transport": "stdio",                       # adapter launches server.py for you
        "env": {"SEARXNG_URL": "http://localhost:8080"},
    }
})
tools = await client.get_tools()                    # -> [web_search] as a LangChain tool
agent = create_react_agent(init_chat_model("anthropic:claude-opus-4-8"), tools)
result = await agent.ainvoke({"messages": [{"role": "user", "content": "..."}]})
```

> With `stdio` the adapter spawns `server.py` itself — you only need SearXNG
> running. To point at an already-running HTTP server instead (see
> [DEPLOYMENT.md](DEPLOYMENT.md)), use
> `{"url": "http://localhost:8000/mcp", "transport": "streamable_http"}`.

## Deploying on a server

To run this always-on for remote clients (HTTP transport, TLS, systemd), see
[DEPLOYMENT.md](DEPLOYMENT.md). The server reads `MCP_TRANSPORT=http` plus
`MCP_HOST`/`MCP_PORT`/`MCP_PATH` to switch from local stdio to a remote HTTP
endpoint — no code changes required.

## The `web_search` tool

| Parameter     | Type  | Default | Notes                                            |
|---------------|-------|---------|--------------------------------------------------|
| `query`       | str   | —       | Search terms (required).                         |
| `num_results` | int   | 5       | Max results to return.                           |
| `categories`  | str   | none    | e.g. `general`, `news`, `science`, `it`.         |
| `language`    | str   | none    | e.g. `en`, `de`, `all`.                          |
| `time_range`  | str   | none    | `day`, `month`, or `year`.                       |
| `pageno`      | int   | 1       | Result page number.                              |

## Point at a remote instance instead

Any JSON-enabled SearXNG works — no local install needed:

```bash
SEARXNG_URL=https://my-searxng.example.com uv run server.py
```

## Troubleshooting

- **403 from the tool / curl** — JSON format isn't enabled. Confirm
  `search.formats` includes `json` in `searxng/settings.yml`, then restart
  SearXNG (`./run-searxng.sh` or `docker compose restart`).
- **Connection refused** — SearXNG isn't running, or `SEARXNG_URL` points to the
  wrong host/port.
- **`ModuleNotFoundError` during install** — you skipped the requirements step;
  run `pip install -r .searxng-src/requirements.txt` first, then the editable
  install with `--no-build-isolation`.