Content
# Tool List
MCP server with two tools for LLM agents: web search and page reading.
- `web_search` — search via [SearXNG](https://docs.searxng.org/); returns a list
of results (title, link, snippet) and resource-links to found URLs.
- `web_fetch` — page loading and main text extraction in Markdown
(Readability), without menus, ads, and scripts.
Transport — [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports),
protocol MCP `2025-11-25`. Compatible with opencode, Roo Code, Cline, OpenClaw and any
MCP client over HTTP.
Implemented in Go, delivered as a single static binary in a distroless image (~13 MB).
## Purpose
The language model does not have direct access to the internet. MCP is a standard way to give
the agent tools and allow it to decide when to call them:
- request «find X» → agent calls `web_search` → gets a list of links;
- request «read page» → `web_fetch` → gets Markdown with main text.
An alternative — to teach each client to work with a search API directly — leads
to duplication of the same logic: parsing the issue, reading pages, protection against
requests to the internal network. The server collects this in one place for a single URL.
### SearXNG
SearXNG — self-hosted meta-search engine. It does not have its own index: for each
request, it queries Google, Brave, DuckDuckGo, Wikipedia and other engines,
aggregates their output and gives a single JSON. Advantages — no direct
transfer of requests to Google/Bing and management of the set of engines, locale and freshness.
This is an **external service**; it is not in the repository intentionally. SearXNG is deployed
separately (there is an [official docker image](https://github.com/searxng/searxng-docker))
and can be common for several consumers. `web_search` refers to it by
the address from `SEARXNG_URL`.
Division of responsibility: SearXNG is responsible for search, mcp-web — for making search
and page reading tools that the MCP agent understands.
## Running
Requires Go 1.26+.
```bash
export SEARXNG_URL=http://localhost:8080 # address of available SearXNG
make run # go run, port from .env or 3502
curl localhost:3502/health # {"status":"ok","proxy":false}
```
Building an image and deploying:
```bash
make image # docker build → mcp-web:latest
make deploy # docker save | ssh load on host + docker compose up
```
`docker-compose.yaml` is intended only for deployment (without `build:`). SearXNG
is not declared in it: the server connects to the external network `shared` and refers to
the neighboring container `searxng`. The network and address are configured for a specific
infrastructure.
Other targets — `make help` (`build`, `test`, `lint`, `check`, `smoke`).
## Connecting a Client
A client with a Streamable-HTTP transport refers to `/mcp`.
opencode (`~/.config/opencode/opencode.json`):
```json
{
"mcp": {
"web": { "type": "remote", "url": "http://your-host:3502/mcp" }
}
}
```
Roo Code and Cline — MCP server of type `streamable-http` with the same URL. Claude Desktop
only supports stdio, so it requires a bridge
[`mcp-remote`](https://github.com/geelen/mcp-remote).
## Tools
### web_search
| Parameter | Type | Default | |
|-------------|--------------------|---------|--|
| `query` | string | — | required |
| `count` | int 1–10 | 5 | number of results |
| `freshness` | `pd`/`pw`/`pm`/`py` | — | day / week / month / year |
| `language` | string | env | BCP-47, e.g. `ru-RU` |
| `country` | string | — | ISO code of country, if not set `language` |
The response contains three representations at once: a text list for the model,
`structuredContent` (array `{title, url, description}` by `outputSchema`) and
`resource_link` to each result — so that the agent calls `web_fetch` by a ready
link, and not reconstructs the URL from the text.
### web_fetch
| Parameter | Type | Default | |
|--------------|-----------------|---------|--|
| `url` | URL | — | required |
| `max_length` | int 500–100000 | 20000 | limit of Markdown length |
Behavior:
- SSRF-check of host and each redirect — private, loopback, link-local and CGNAT
addresses are rejected to exclude the model's access to internal services;
- timeouts, response limit 5 MB, no more than 5 redirects (redirect cycle
is returned with a separate understandable error);
- if the Readability result is empty (JS-page, stub) returns the title
and meta-description instead of an empty response;
- for statuses 401/403/404/429, an explanation is formed about what happened and how
to act — so that the agent does not switch to blindly trying links.
Both tools are marked with `readOnlyHint` and `openWorldHint`.
Results are cached in memory (LRU): search — 10 minutes / 500 keys, pages —
1 hour / 100 keys. The header `Cache-Control: no-store` is taken into account.
## Configuration
Parameters are read from the environment; template — `.env.example`.
| Variable | Default | |
|----------------------|-------------------------------------|--|
| `PORT` | `8080` | port inside container |
| `MCP_PORT` | `3502` | port on host (compose) |
| `SEARXNG_URL` | `http://searxng:8080` | SearXNG address |
| `SEARXNG_ENGINES` | `duckduckgo,brave,wikipedia,github` | list of engines |
| `SEARXNG_LANGUAGE` | `en-US` | default locale |
| `SEARXNG_TIMEOUT_MS` | `10000` | SearXNG request timeout |
| `ALLOWED_ORIGINS` | empty | CSV of allowed browser Origin |
| `HTTPS_PROXY` | empty | outgoing proxy for `web_fetch` |
| `NO_PROXY` | `searxng,localhost,127.0.0.1` | exceptions from proxy |
`NO_PROXY` is processed by standard Go tools. Listed domains go around
the proxy — this is necessary for internal services and any hosts that should
be available directly.
The set of engines is set in two places: `SEARXNG_ENGINES` and `engines:` in the SearXNG configuration.
The engine must be enabled on the SearXNG side, otherwise the results will be empty.
## Security
Authentication is not provided: the service is designed for a trusted local network. When
publishing outside, authorization is provided by an external reverse-proxy.
- SSRF-guard in `web_fetch` (including redirects) blocks requests to `192.168.x`,
`127.x`, link-local and other internal addresses;
- `ALLOWED_ORIGINS` cuts off extraneous browser Origin (protection against
DNS-rebinding); requests without the `Origin` header (curl, non-browser clients)
are passed;
- the container is launched under non-root with `read_only` rootfs, `cap_drop: ALL`,
`no-new-privileges` and limits on memory, CPU and number of processes.
## Structure
```
cmd/mcp-web/ entrypoint, flags, graceful shutdown
internal/
config/ environment parsing
httpapi/ net/http: /health and /mcp, origin-check
mcpserver/ MCP server assembly, instructions
tools/ web_search, web_fetch (schemes and handlers)
searxng/ SearXNG client
fetch/ http-client, SSRF-guard, Readability → Markdown
```
The server is built on the official
[go-sdk](https://github.com/modelcontextprotocol/go-sdk); the lifecycle of Streamable HTTP sessions
is managed by the SDK itself. Content extraction —
[readeck/go-readability](https://codeberg.org/readeck/go-readability), HTML→Markdown conversion —
[html-to-markdown](https://github.com/JohannesKaufmann/html-to-markdown).
## Limitations
JavaScript is not executed (headless browser is not used intentionally — for
the size of the image and simplicity). Search pagination and news/images categories are not
supported. For SPA pages, `web_fetch` returns only the title and description.
## License
MIT.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
Filesystem
Node.js MCP Server for filesystem operations with dynamic access control.
Fetch
Retrieve and process content from web pages by converting HTML into markdown format.
Agent-Reach
Give your AI agent eyes to see the entire internet. Read & search Twitter,...
Context 7
Context7 MCP provides up-to-date code documentation for any prompt.
context7-mcp
Context7 MCP Server provides natural language access to documentation for...
mempalace
The highest-scoring AI memory system ever benchmarked. And it's free.