# DuckDuckGo MCP bench: 8 servers, 12 searches, 3 passes, Sept 2026

> Source: <https://gist.github.com/sergey-ermakovich/27e6e36dd2dcf5885890fa08b0ec6228>
> Published: 2026-09-23 07:18:51+00:00

Raw results from running the same twelve searches against every DuckDuckGo MCP server we could find in the MCP registry, npm, PyPI, Smithery, the Docker MCP catalog and search, on 2026-09-15. Three full passes plus one from a fresh clone of the public harness. Twelve distinct queries per pass, one per call, no delay between calls, the official MCP TypeScript SDK as the client. No ranking here, only what came back. The harness and the fixture are public at [https://github.com/HasData/mcp-bench](https://github.com/HasData/mcp-bench).

Disclosure. HasData, which maintains the harness, also runs one of the servers below.

| Server | Where it runs | Auth | Source | 
|---|---|---|---|
| HasData DuckDuckGo MCP | hosted, streamable HTTP | `x-api-key` header, free key | [https://hasdata.com/mcp/duckduckgo](https://hasdata.com/mcp/duckduckgo) | 
| nickclyde/duckduckgo-mcp-server | local, `uvx duckduckgo-mcp-server` | none | [https://github.com/nickclyde/duckduckgo-mcp-server](https://github.com/nickclyde/duckduckgo-mcp-server) | 
| Aas-ee/open-webSearch | local, `npx open-websearch` | none | [https://github.com/Aas-ee/open-webSearch](https://github.com/Aas-ee/open-webSearch) | 
| sweetcornna/free-search-mcp | local, `uvx free-search-mcp` | none | [https://github.com/sweetcornna/free-search-mcp](https://github.com/sweetcornna/free-search-mcp) | 
| yokingma/one-search-mcp | local, `npx one-search-mcp` | none | [https://github.com/yokingma/one-search-mcp](https://github.com/yokingma/one-search-mcp) | 
| pinkpixel-dev/web-scout-mcp | local, `npx @pinkpixel/web-scout-mcp` | none | [https://github.com/pinkpixel-dev/web-scout-mcp](https://github.com/pinkpixel-dev/web-scout-mcp) | 
| OEvortex/ddg_search | local, `npx @oevortex/ddg_search` | none | [https://github.com/OEvortex/ddg_search](https://github.com/OEvortex/ddg_search) | 
| zhsama/duckduckgo-mcp-server | local, `npx duckduckgo-mcp-server` | none | [https://github.com/zhsama/duckduckgo-mcp-server](https://github.com/zhsama/duckduckgo-mcp-server) | 

Not run, with the reason. `DDDMUC/dsh-free-search` (140 stars, ranks for the head term) is a DeepSeek Harness search plugin rather than a standalone MCP server, and its npm package ships no binary. Bright Data and Apify have no DuckDuckGo product.

Three of the eight are multi-engine. free-search-mcp and open-webSearch take an engine argument and were pinned to DuckDuckGo. one-search-mcp has no engine field in its tool schema and is covered separately below.

A call counted as failed when the server returned `isError`, when the body was an error envelope such as `{"error": ...}` regardless of transport status, when the body carried no results, or when the call timed out. Two rules were added during this run because servers kept failing inside a success: a result set whose every item has an empty or missing link, and a payload with no item array at all whose text opens with a failure word. Both are in the public harness.

Latency is the median of successful calls only, computed as a true median. The harness `report` command prints `sorted[floor(n/2)]`, the upper median, so its figures sit slightly above these.

| Server | Result | Median | Fastest to slowest | Median bytes | Connect | 
|---|---|---|---|---|---|
| open-webSearch | 10/12 | 1.12 s | 0.98 to 2.45 s | 5.4 KB | 37.1 s | 
| free-search-mcp | 12/12 | 1.39 s | 1.06 to 1.59 s | 4.2 KB | 8.3 s | 
| HasData | 12/12 | 1.96 s | 1.49 to 2.77 s | 10.0 KB | 2.3 s | 
| nickclyde | 12/12 | 2.37 s | 2.21 to 4.91 s | 3.6 KB | 8.8 s | 
| one-search-mcp, default provider | 12/12 | 7.27 s | 6.66 to 10.74 s | 4.9 KB | 8.7 s | 
| OEvortex | 3/12 | 1.53 s | 1.41 to 1.84 s | 1.2 KB | 48.5 s | 
| web-scout | 1/12 | 1.51 s | single call | 4.0 KB | 6.6 s | 
| zhsama | 0/12 | n/a | n/a | n/a | 5.8 s | 

Connect times in this pass include `npx -y` and `uvx` fetching packages on first use. Warm connects in the two later passes ran 1.8 to 10.9 s for the local servers against 1.6 to 3.6 s hosted.

| Server | Pass 1 | Pass 2 | Pass 3 | 
|---|---|---|---|
| free-search-mcp | 12/12 | 12/12 | 12/12 | 
| HasData | 12/12 | 12/12 | 12/12 | 
| nickclyde | 12/12 | 12/12 | 12/12 | 
| open-webSearch | 10/12 | 12/12 | 12/12 | 
| OEvortex | 3/12 | 6/12 | 2/12 | 
| web-scout | 1/12 | 0/12 | 1/12 | 
| zhsama | 0/12 | 0/12 | 0/12 | 

Local servers ran together at 09:42, 10:43 and 11:25 UTC. The hosted server was measured at 09:42, 10:48, 11:25 and 11:54, so its passes sit on a different clock and the columns are per server rather than a synchronised schedule.

Medians by pass, finishers only: pass 1 open-webSearch 1.12, free-search 1.39, HasData 1.96, nickclyde 2.37, one-search 7.27. Pass 2 open-webSearch 1.08, free-search 1.48, nickclyde 1.52, HasData 2.15, one-search 6.25. Pass 3 open-webSearch 1.04, free-search 1.42, nickclyde 1.44, HasData 2.14. The order of the middle three changes between passes.

Its tool schema exposes `query`, `limit`, `language`, `categories` and `timeRange`, with no engine field, so the first pass ran it as shipped. It returned twelve well-formed responses at a 7.27 s median, and every URL in them was a `bing.com/ck/a` redirect with no duckduckgo.com string anywhere in the captured payloads.

The server reads `SEARCH_PROVIDER` from the environment. The default is `local`, which drives a real Chrome profile (` userDataDir` pointing at the user's own Chrome) against Bing. Pinned to `SEARCH_PROVIDER=duckduckgo` it returned 0 of 12, each call failing after 10.8 to 16.4 s with `DDG detected an anomaly in the request, you are likely making requests too quickly` and `Failed to get the VQD` in its log.

Both measurements are in the results files. The fixture now carries the env var.

| Server | Format | Links | Extras | 
|---|---|---|---|
| HasData | JSON, 10 results with position, title, link, displayedLink, source, snippet, snippetHighlightedWords | direct | DuckDuckGo's assist answer with sources, pagination token | 
| open-webSearch | JSON, 10 results with title, url, description, source, engine | direct | reports which engines ran and any partial failures | 
| free-search-mcp | markdown by default, JSON with `format: "json"` | direct | names the engines used | 
| nickclyde | text, `Found N search results:` then numbered title, URL, summary | direct | none | 
| one-search-mcp | text, title, URL, description | Bing redirects on the default provider | none | 

- **zhsama** , 2 of 12 reached DuckDuckGo and came back with`DDG detected an anomaly in the request, you are likely making requests too quickly` after 2.4 s and 0.6 s. The other 10 returned in 2 to 5 ms with a 26-byte`Rate limit exceeded` from its own`checkRateLimit` . Its published build sets`rateLimit: { perSecond: 1, perMonth: 15000 }` , so the stderr line`{ second: 1, month: 2 }` is a running count and the one-per-second ceiling is what a zero-delay burst trips. It throws rather than queueing.
- **web-scout** , one real answer then 11 repetitions of`No results were found for your search query. Please try rephrasing your search or try again in a few minutes` , each under 310 ms against 1.5 s for the one that worked.`isError` false throughout.
- **OEvortex** , 9 of 12`Error: Search failed for "<query>": HTTP 202: Failed to fetch search results` . Successes landed on calls 8, 9 and 12 in pass 1.
- **open-webSearch** , clean for ten queries then`{"query": "...", "engines": ["duckduckgo"], "totalResults": 0, "results": [], "partialFailures": []}` on calls 11 and 12. No error flag. Its stderr for those two calls says it could not find the preloaded`d.js` URL it scrapes out of DuckDuckGo's HTML, so the diagnostic exists but never reaches the caller.
- **nickclyde** , clean in all three passes, then in the fresh-clone pass it answered seven queries and returned`No results were found for your search query. This could be due to DuckDuckGo's bot detection or the query returned no matches ... installing the optional browser backend (pip install 'duckduckgo-mcp-server[browser]') enables Chrome TLS impersonation` for the remaining five.

- `duckduckgo-mcp-server` is two unrelated projects. On PyPI it is nickclyde's, which finished every pass. On npm it is zhsama's, which finished none. One command apart.
- Three of the eight fail inside a 200 rather than erroring. Two return an empty list, one returns a prose sentence. A client that branches on errors treats all three as an empty search.
- The test IP was not the limiting factor. free-search-mcp started a tenth of a second after web-scout's last empty answer, from the same address, and completed twelve of twelve. open-webSearch started a tenth of a second after zhsama finished failing and completed ten.
- Zero delay between calls is not a realistic agent workload, and it is exactly what tripped zhsama. Read these as burst results.
- open-webSearch binds an HTTP server on port 3000 even over stdio. A stale instance surfaces to the client as `MCP error -32000: Connection closed` .`MODE=stdio` avoids it.
- Cold start is dominated by package fetch on first use, 5.8 to 48.5 s here, and drops to 1.8 to 10.9 s warm.

```
git clone https://github.com/HasData/mcp-bench.git && cd mcp-bench && npm install
cp .env.example .env            # only the hosted server needs a key
node mcp-bench.mjs probe duckduckgo
node mcp-bench.mjs run duckduckgo --runs 12 --timeout 180000
node mcp-bench.mjs report duckduckgo
```

Node 20 or newer, and `uv` on PATH for the two servers that start through `uvx`. The twelve queries live in `fixtures/duckduckgo.json` under `queryVariants`, one per run. Note that one-search-mcp on its default provider launches your real Chrome profile, and that open-webSearch wants port 3000 free.

All measurements are from tool calls made on 2026-09-15 between 09:42 and 11:56 UTC from one machine in Europe on one IP. Star counts, push dates and the Docker pull count were read the same week.
