uvxwebsearchmcp
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
MITThis server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing