ddg-search
Provides web search through DuckDuckGo with multiple backends, automatic failover, and compact result formatting.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ddg-searchsearch for latest Python async web scraping tutorials"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ddg-search
A DuckDuckGo search MCP server that refuses to have a single point of failure. One process, many backends, automatic failover, honest error messages.
The idea
Web search is load-bearing infrastructure for coding agents, and it fails in boring ways: rate limits, bot detection, your VPS provider having a moment. Most servers give you one HTTP client and hope. This one routes each query across several backends — a local searcher on this machine plus any number of remote duckduckgo-mcp-server instances you happen to run — and keeps trying until something answers or the budget runs out.
Backends that fail get put in timeout. Backends that behave get more traffic. You get the results, one compact block, with a one-line note of who served it.
Related MCP server: DuckDuckGo Search MCP Server
Install
Requires Python 3.10+ and uv.
git clone <this repo> ~/.local/share/mcp/ddg-search # or anywhere you like
cd ~/.local/share/mcp/ddg-search
uv syncThat is the whole ceremony. uv sync creates .venv, locks dependencies, and
installs the package editable, so edits to src/ apply on restart.
Wire it into your agent
Any MCP client that speaks stdio works. For an mcp.json-style config:
{
"mcpServers": {
"ddg-search": {
"type": "stdio",
"command": "/path/to/ddg-search/.venv/bin/python",
"args": ["-m", "ddg_search.server"],
"env": {
"DDG_SAFE_SEARCH": "OFF",
"DDG_SEARCH_BACKEND": "auto"
},
"timeout": 60000
}
}
}DDG_SAFE_SEARCH is content filtering only — it does nothing against bot
detection, and it is off by default because agents doing research want recall,
not a chaperone.
Tools
search
Argument | Type | Default | Notes |
| string | required | Exact nouns beat vague one-word vibes |
| int | 10 | Upstream caps around 10–11 regardless |
| string |
| DuckDuckGo region code |
|
|
| Manual skips health sorting |
| string |
| One backend name/alias/IP (manual mode) |
| list |
| Ordered fallback chain (manual mode) |
Results come back compact on purpose:
via relay-b
3 results:
1. Some Page Title
https://example.com/page
The snippet text, labels stripped, no blank lines eating your tokens.
2. ...Every response states which backend served it. Failed attempts are listed
under Attempts: with a tag telling you where it broke:
Tag | Meaning |
| DuckDuckGo returned zero matches — genuine no-results or bot-empty, indistinguishable from here |
| This machine's client failed. Do not blame the remote hosts |
| A remote answered badly |
| The 25s budget ran out while waiting |
When things break, you get a log path
The router distinguishes "the internet is being the internet" from "this tool
is actually broken". Timeouts and empty result sets just get their [tag].
But when an attempt fails in a way that means our side broke — local
transport errors, remote backends answering badly — the response ends with:
log: /path/to/ddg-search/logs/20260822T090206-remote-tool-error.jsonThat file holds everything needed to replay and diagnose: the exact query and
arguments, every attempt with its failure detail, and a snapshot of per-backend
state at the time. Point DDG_SEARCH_LOGS_DIR somewhere else if you want;
logs are never written for timeouts or empty results.
status
Backend table: online flag, observed attempts this minute, last status,
cooldown expiry. Pass probe: true to actually ping remote backends instead
of trusting cached state.
Configuration
Environment variables, all optional:
Variable | Default | Purpose |
|
|
|
|
| Local transport: |
|
| Total budget across all backends per query |
|
| Timeout penalty per backend |
|
| Error penalty per backend |
|
| Per-backend probe wait for |
|
| Router state directory |
Backends live in src/ddg_search/config.py. The
default fleet is local (this machine) plus two remote relays; edit the tuple
to match your own infrastructure.
Behavior worth knowing
Failover prefers healthy backends with the fewest recent attempts, so traffic spreads instead of hammering one poor box.
Cooldowns are per-backend and time-boxed: a timeout sits a backend out for 90s, a soft failure for 30s. One success clears the slate instantly.
State survives restarts in
state/router-state.json. Delete it if you want amnesia; the server recreates it on next boot.
One quirk deserves its own paragraph. DuckDuckGo serves empty pages to clients it does not trust, so "no results" can mean either genuinely no matches or quiet bot-flagging — the router cannot tell those apart, and it does not pretend to. It treats empty as failure and tries the next backend; if every backend comes back empty you get a banner saying exactly how ambiguous that is.
Last thing: the 30 requests/minute ceiling is enforced by each duckduckgo-mcp-server instance, not here. The router spreads load across backends, but it will not lie about capacity the fleet does not have.
Running your own relays
Any machine that can run the stock server works as a backend:
pip install 'duckduckgo-mcp-server[browser]'
python -m duckduckgo_mcp_server.main --transport streamable-http --host 0.0.0.0 --port 18082Point a BackendConfig(url="http://that-host/ddg-mcp") at it. The
realip/ directory contains a launcher used by a systemd unit to
run one such exit behind mullvad-exclude on residential IP — useful if your
datacenter egress gets worse captcha treatment than your home connection.
Development
uv sync # install everything including dev tools
uv run pytest # 26 tests, no network needed except one optional live check
uv run ruff check src tests # lint
uv run ruff format src tests # format
uv run pyrefly check # static typesA quick manual smoke test through the full router:
uv run python -c "import asyncio; from ddg_search.router import SearchRouter; \
print(asyncio.run(SearchRouter().search('crawl4ai', 3, '', 'auto', None, None, None)))"See also
fast-webfetch-mcp — the other half: opens the URLs these searches find, through a local Crawl4AI browser
Model Context Protocol — what "MCP" means
duckduckgo-mcp-server — the search library doing the actual scraping underneath
License
MIT.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides web search functionality via DuckDuckGo for Claude Code and MCP-compatible clients, featuring advanced content exploration, navigation across search results, and detailed webpage analysis.316MIT
- FlicenseNot gradedqualityCmaintenanceBrings DuckDuckGo search (web, news, images, videos) to any MCP-compatible AI client without requiring an API key.
- FlicenseNot gradedqualityDmaintenanceMCP server that provides web search scraping from DuckDuckGo (with Mojeek fallback) and URL content fetching as markdown/text or raw HTML.1
- AlicenseAqualityBmaintenanceMCP server for DuckDuckGo web search, enabling AI agents to perform real-time text, news, and image searches without an API key.3MIT
Related MCP Connectors
Serper MCP — wraps the Serper Google Search API (serper.dev)
Stealth web browser for agents: search, fetch, click and type through persistent sessions over MCP.
Agentic search over your Dewey document collections from any MCP-compatible client.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/NikkeTryHard/ddg-search'
If you have feedback or need assistance with the MCP directory API, please join our Discord server