Skip to main content
Glama
README.md
# mcp-web

An MCP server that gives a locally-run LLM access to the internet: web search,
page fetching, raw HTTP, and optional headless rendering. Built for LM Studio,
but it is a plain stdio MCP server and works with any MCP client.

## Tools

| Tool | What it does |
|---|---|
| `web_search` | DuckDuckGo search. Returns title, url, snippet. |
| `fetch_url` | Fetches a page and returns its main content as markdown. |
| `http_request` | Arbitrary HTTP method/headers/body. For JSON APIs. |
| `render_page` | Loads a page in headless Chromium, runs its JS, returns text. Registered only when the `browser` extra is installed. |

## Install

```bash
uv venv
uv pip install -e .
```

With headless rendering:

```bash
uv pip install -e ".[browser]"
uv run playwright install chromium
```

## Wiring into LM Studio

LM Studio reads `~/.lmstudio/mcp.json` (also reachable from the Program tab in
the right sidebar → Install → Edit `mcp.json`). Add:

```json
{
  "mcpServers": {
    "mcp-web": {
      "command": "/Users/YOU/mcp-web/.venv/bin/mcp-web"
    }
  }
}
```

Then load a tool-capable model and enable the server for the chat. LM Studio
asks for confirmation before each tool call by default.

## Security

Local models are talked into things. Every outbound request in this server —
including redirects and browser navigations — goes through `net/guard.py`,
which resolves the hostname and refuses:

- loopback, RFC1918, link-local, reserved, and multicast addresses
- cloud metadata endpoints (169.254.169.254)
- anything but `http` and `https`
- IPv4 addresses disguised as IPv6 (`::ffff:127.0.0.1`) or as integers
  (`http://2130706433/`)

Without this, `http_request` would hand the model your router admin page and
every service you have bound to localhost.

**Known gap:** the guard resolves DNS, then httpx resolves it again to
connect. A hostile authoritative nameserver can answer differently the second
time (DNS rebinding) and reach a private address. Closing this needs a custom
transport that connects to the already-validated IP. Acceptable for a local
tool on a trusted network; not acceptable if you ever expose this server.

## Configuration

All optional, all environment variables:

| Variable | Default | Meaning |
|---|---|---|
| `MCPWEB_ALLOW_PRIVATE` | `0` | `1` lets the model reach localhost and your LAN |
| `MCPWEB_ALLOWLIST` | empty | Comma-separated hosts; when set, nothing else is reachable |
| `MCPWEB_TIMEOUT` | `20` | Per-request timeout, seconds |
| `MCPWEB_MAX_BYTES` | `2000000` | Response body cap |
| `MCPWEB_MAX_REDIRECTS` | `5` | Redirect hop limit |
| `MCPWEB_USER_AGENT` | Chrome-ish | Sent on every request |

## Tests

```bash
uv run pytest
```

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct role: web_search finds pages, fetch_url converts pages to markdown, and http_request handles raw API calls. The descriptions also explicitly steer agents toward the right tool, so confusion is unlikely.

Naming Consistency4/5

web_search and fetch_url follow a clean verb_noun pattern, while http_request is a noun phrase and breaks the pattern. Overall the names are still simple, lowercase, and readable, with only one minor deviation.

Tool Count5/5

Three tools is a reasonable, well-scoped size for a focused web access server. Each tool earns its place and there is no obvious redundancy.

Completeness3/5

The basic search/fetch/request workflow is covered, but fetch_url explicitly tells agents to retry with render_page for JavaScript-heavy pages while render_page does not exist in the tool set. This creates a notable dead end for those pages and makes the surface feel incomplete.

Maintenance

ActivityMaintained
ResponsivenessNo issues