Skip to main content
Glama
DanielNMC
by DanielNMC
README.md
# uvxwebsearchmcp

A zero-config web search and fetch MCP server for LLM agents. Wraps [`ddgs`](https://pypi.org/project/ddgs/) (multi-backend metasearch) with a persistent rolling cache and structured, retry-friendly error envelopes.

## Why

LLM agents need a web search tool that survives bursty query patterns. Single-engine scrapers die on HTTP 429 within minutes. `uvxwebsearchmcp` solves this by:

- **Multi-backend fan-out** via `ddgs`'s `backend="auto"` — every search tries up to 10 engines.
- **Persistent 15-minute rolling cache** — repeated agent queries don't re-hit upstream.
- **Structured error envelopes** — agents see `retryable: bool`, `retry_after_seconds`, `hints`, and `attempts` instead of opaque stack traces.
- **Zero API keys, zero config** — install and run.

## Install

This package lives in a **private Git repository**, so you cannot use the plain `uvx uvxwebsearchmcp` form (that requires PyPI). Use one of these forms instead, depending on your auth:

```bash
# SSH key auth (recommended — uses your ssh-agent, no secrets in URL)
uvx --from git+ssh://git@github.com/DanielNMC/web_tools_mcp.git uvxwebsearchmcp

# GitHub PAT auth (works in CI; replace ghp_xxx with your token)
uvx --from "git+https://ghp_xxx@github.com/DanielNMC/web_tools_mcp.git" uvxwebsearchmcp

# Pin to a tag for reproducibility
uvx --from "git+ssh://git@github.com/DanielNMC/web_tools_mcp.git@v0.1.0" uvxwebsearchmcp
```

`uvx` creates an ephemeral venv, installs the package + its dependencies (`ddgs`, `diskcache`, `mcp`), runs the server on stdio, and tears down on exit. The cache persists across invocations in `~/.cache/uvxwebsearchmcp/cache.db`.

### Local development

If you have the repo cloned:

```bash
git clone https://github.com/DanielNMC/web_tools_mcp.git
cd web_tools_mcp
uvx .          # runs the server from the current checkout
```

## Tools

### `web_search(query, max_results=10, backend="auto")`

Searches the web via ddgs and returns either `{"results": [...]}` or `{"error": {...}}`.

- `query` (str, required) — non-empty search query.
- `max_results` (int, default `10`) — must be 1..50.
- `backend` (str, default `"auto"`) — one of:
  - `auto` — fans out across all configured engines (recommended)
  - `bing`, `duckduckgo`, `google`, `grokipedia`, `mojeek`, `startpage`, `yandex`, `yahoo`, `wikipedia`
  - **`brave` is intentionally NOT supported** (would require an API key).

The MCP tool description sent to the LLM lists the allowed backends.

### `web_fetch(url, max_chars=10000)`

Fetches a URL and extracts its content as markdown via ddgs. Returns `{"url", "content"}` on success or `{"error": {...}}` on failure.

## Errors

Both tools return structured errors instead of raising. The shape is:

```json
{
  "error": {
    "type": "rate_limit" | "backend_failure" | "empty_results" | "invalid_input",
    "message": "human-readable explanation",
    "retryable": true | false,
    "retry_after_seconds": 30,
    "hints": ["Wait and retry", "Or decompose the query"],
    "attempts": [{"backend": "duckduckgo", "outcome": "rate_limit"}]
  }
}
```

Read `retryable` before deciding to retry. `retry_after_seconds` is a concrete wait suggestion, not a vague "later". `hints` is natural-language guidance for the LLM.

## Configuration (all optional)

| Env var | Default | Purpose |
|---|---|---|
| `WEBSEARCH_CACHE_DIR` | `$XDG_CACHE_HOME/uvxwebsearchmcp` (or `~/.cache/uvxwebsearchmcp`) | Override the cache location. |
| `XDG_CACHE_HOME` | `~/.cache` | Standard XDG override; honored if `WEBSEARCH_CACHE_DIR` is unset. |
| `HOME` | (system) | Used as last-resort fallback for the cache dir. |

If the cache directory cannot be created or written (read-only filesystem, locked-down CI), the server falls back to an in-memory cache and continues to serve. No tool calls will fail because of a cache problem.

## Claude Desktop configuration

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "web-search": {
      "command": "uvx",
      "args": [
        "--from", "git+ssh://git@github.com/DanielNMC/web_tools_mcp.git",
        "uvxwebsearchmcp"
      ]
    }
  }
}
```

Other MCP clients (Cursor, Kilo, etc.) take the same shape — point them at `uvx` with the `--from` URL and the package name as the executable.

## How it works

```
   LLM agent
       │
       ▼
   MCP server (stdio)
       │
       ▼
   ┌──────────────┐
   │  Cache       │  diskcache, SQLite-backed
   │  TTL: 15min  │  persists across uvx runs
   │  negative:60s│
   └──────┬───────┘
          │ miss
          ▼
   ┌──────────────┐
   │  ddgs        │  multi-backend metasearch
   │  backend=auto│  (10 engines)
   └──────────────┘
```

The `auto` backend tries each engine in random order; if one 429s, ddgs moves to the next. The cache absorbs the rest of the load.

## Limitations

- `web_fetch` does not cache. Single-URL fetches are typically non-repeating and caching them would just bloat disk.
- The cache is process-local to the user — no cross-user or cross-host sharing.
- `ddgs` scrapes engines without API keys. If every engine de-platforms your IP simultaneously, the tool degrades to error envelopes, not crashes.

## Development

```bash
git clone https://github.com/DanielNMC/web_tools_mcp.git
cd web_tools_mcp
uv sync
uv run pytest              # 88 tests
uv run ruff check src tests
uv build                   # produces dist/*.whl and dist/*.tar.gz
```

## Releasing a new version

```bash
# Bump version in pyproject.toml
git tag v0.2.0
git push origin master --tags
```

Other users pin to your tag with `@v0.2.0` in the `--from` URL.

## License

MIT