# DuckDuckGo MCP Servers in Claude Code: 8 Tested, 3 Actually Work

> Source: <https://dev.to/hasdata_com/duckduckgo-mcp-servers-in-claude-code-8-tested-3-actually-work-3gnp>
> Published: 2026-09-23 07:09:55+00:00

Free web search for an agent sounds like a solved problem. There are dozens of DuckDuckGo MCP servers, none of them wants an API key, and every catalog lists them with the same three-line description lifted from the README.

So I installed eight of them and gave each one the same job: twelve research questions, one after another, no pause between them. That is roughly what a coding agent does when it starts digging into an unfamiliar library. Three servers answered all twelve. One of them was not talking to DuckDuckGo at all.

I work at HasData and we run one of the eight. It came third on speed in the first pass and fourth in the two after, behind free servers. Every config below is copy-paste ready, and the harness is public if you want to rerun any of it.

Twelve queries, from `rust async runtime comparison` to `solar inverter efficiency`, sent back to back through the official MCP TypeScript SDK. Then the whole thing twice more, about an hour apart, to see what was stable. One machine, one IP, 2026-09-15.

A call counted as a failure if the server errored, if the body was an error dressed as a result, or if it came back with nothing in it. That third rule did most of the work.

Here is the whole test on one screen, ordered by how often each server finished and then by speed.

| Server | Install | Finished, 3 passes | Median | Payload | Returns | 
|---|---|---|---|---|---|
| free-search-mcp | `uvx free-search-mcp` | 12, 12, 12 | 1.39 s | 4.2 KB | markdown, JSON on request | 
| HasData (ours) | hosted, no install | 12, 12, 12 | 1.96 s | 10.0 KB | JSON | 
| nickclyde | `uvx duckduckgo-mcp-server` | 12, 12, 12 | 2.37 s | 3.6 KB | text | 
| open-webSearch | `npx open-websearch` | 10, 12, 12 | 1.12 s | 5.4 KB | JSON | 
| one-search-mcp | `npx one-search-mcp` | 12, 12, 12 | 7.27 s | 4.9 KB | text, Bing results | 
| OEvortex | `npx @oevortex/ddg_search` | 3, 6, 2 | 1.53 s | 1.2 KB | text | 
| web-scout | `npx @pinkpixel/web-scout-mcp` | 1, 0, 1 | 1.51 s | 4.0 KB | text | 
| zhsama | `npx duckduckgo-mcp-server` | 0, 0, 0 | n/a | n/a | n/a | 

The medians only count calls that returned something, so the bottom three look faster than they are. OEvortex failing in 1.53 s is still a failure.

```
{
  "mcpServers": {
    "duckduckgo": {
      "command": "uvx",
      "args": ["free-search-mcp"]
    }
  }
}
```

`free-search-mcp` answered twelve of twelve in every pass at a 1.39 s median, the fastest of the servers that finished. It is a 69-star project, MIT, last pushed in August 2026, so it is young. It returns markdown by default. If your agent would rather have JSON, ask for it per call:

```
{ "query": "rust async runtime comparison", "engines": ["duckduckgo"], "format": "json", "use_cache": false }
```

Three arguments is more config than the others need, and `use_cache` defaults to true, which will quietly hand you an old answer if you leave it alone. That is the trade for the best numbers in the test.

```
{
  "mcpServers": {
    "duckduckgo": {
      "command": "uvx",
      "args": ["duckduckgo-mcp-server"]
    }
  }
}
```

nickclyde's server is the default choice for good reason: 1,460 stars, pushed this month, MIT, and it is what the `mcp/duckduckgo` Docker image is built from, with over half a million pulls. It finished twelve of twelve in all three passes at 2.37 s, then came apart on a fourth run from a fresh clone, answering seven and refusing five with a note about DuckDuckGo's bot detection and a suggestion to install its optional browser backend for Chrome TLS impersonation.

It answers in prose, `Found N search results:` followed by a numbered list, so your model does the parsing. Its search tool takes a query, a result count and a region, and there is no pagination.

**Watch the command.** This is `duckduckgo-mcp-server` on PyPI, hence `uvx`. There is an unrelated `duckduckgo-mcp-server` on npm, and this config gets you a completely different server:

```
{
  "mcpServers": {
    "duckduckgo": {
      "command": "npx",
      "args": ["-y", "duckduckgo-mcp-server"]
    }
  }
}
```

That one is zhsama's, and it scored zero of twelve in all three passes. Google's AI Overview for the package name currently recommends the npm version.

```
{
  "mcpServers": {
    "duckduckgo": {
      "type": "http",
      "url": "https://mcp.hasdata.com/api/mcp?apis=duckduckgo",
      "headers": { "x-api-key": "YOUR-KEY" }
    }
  }
}
```

Twelve of twelve in all three passes at a 1.96 s median, which put it third on speed in the first pass and fourth in the two after, behind free servers. That is the honest placement and it is the least interesting number here.

What it does differently is the payload. Ten results carrying position, title, link, displayedLink, source, snippet and snippetHighlightedWords, plus DuckDuckGo's own assist answer with its sources and a pagination token. Nothing else in the test paginates, and only open-webSearch and free-search return JSON at all. The cost of that is weight: 10.0 KB per call against nickclyde's 3.6, which your agent pays for in context.

The other difference is the one I did not expect to matter. Time from cold config to first answer was 2.3 s, against 8.3 s for free-search, 8.8 s for nickclyde and 37 to 48 s for open-webSearch and OEvortex, which spend it resolving and building packages. There is nothing on your machine to install or keep current, and nothing that breaks when a transitive dependency moves.

It is the only server in the test that costs money past a free tier of 100 searches a month, with paid plans running from $2.95 down to $0.83 per thousand. Details and the free key are on the [server page](https://hasdata.com/mcp/duckduckgo).

Ten of its twelve calls came back in two to five milliseconds with a 26-byte error. Nothing reaches the network that fast. Its own stderr names the culprit:

```
[DEBUG] Rate limit check - Current counts: { second: 1, month: 2, lastReset: ... }
[ERROR] Rate limit exceeded
    at checkRateLimit (.../duckduckgo-mcp-server/build/index.js:80:23)
```

The shipped build sets `rateLimit: { perSecond: 1, perMonth: 15000 }`. So `{ second: 1, month: 2 }` is a counter, not a budget, and what stopped the run is one request per second. The server throws instead of queueing, which is the part that matters for an agent: a burst of twelve gets ten hard errors rather than twelve slow answers.

Pace your calls a second apart and you clear its limiter. The two that did get through reached DuckDuckGo and were turned away there anyway.

```
{ "mcpServers": { "duckduckgo": { "command": "npx", "args": ["-y", "@pinkpixel/web-scout-mcp"] } } }
```

It answered the first query in 1.5 s and then returned this eleven times, each in under 310 ms:

```
No results were found for your search query. Please try rephrasing your search or try again in a few minutes.
```

No error flag, no status code, just a sentence. Your agent sees a successful tool call with a polite message and concludes the web has nothing on Postgres index bloat.

Nine of twelve failed with `HTTP 202: Failed to fetch search results`, and the successes landed on calls eight, nine and twelve. Rerun an hour later it scored six, then two. That spread is not a rate limit being hit. A limit cuts in and stays cut in. This is a wrapper that works sometimes.

```
{
  "mcpServers": {
    "duckduckgo": {
      "command": "npx",
      "args": ["-y", "open-websearch"],
      "env": { "MODE": "stdio", "DEFAULT_SEARCH_ENGINE": "duckduckgo" }
    }
  }
}
```

1.12 s median, JSON with direct links, five fields per result including which engine produced it. It lost two of twelve in the first pass and none in the two after.

Those two look like this:

```
{
  "query": "bird migration patterns europe",
  "engines": ["duckduckgo"],
  "totalResults": 0,
  "results": [],
  "partialFailures": []
}
```

Note the last field. The server has somewhere to report that an engine failed and left it empty while returning nothing. Its stderr for those exact two calls says it could not find the preloaded `d.js` URL it scrapes out of DuckDuckGo's HTML, so the diagnosis exists, it just never reaches the caller.

The `MODE: stdio` in that config is not optional if you run it more than once. By default it also binds an HTTP server on port 3000, and a leftover instance from an earlier session makes the next one die with `MCP error -32000: Connection closed`, which tells you nothing about ports.

This is the one worth ten minutes of your attention. Its tool schema has no engine parameter, so I ran it as shipped, and it looked fine: twelve of twelve, 7.27 s each, well-formed results. Then I looked at the URLs. Every one was a `bing.com/ck/a` redirect. Not a single duckduckgo.com address in any response.

The server reads `SEARCH_PROVIDER` from the environment. The default is `local`, which launches your real Chrome profile and scrapes Bing. Set it properly:

```
{
  "mcpServers": {
    "search": {
      "command": "npx",
      "args": ["-y", "one-search-mcp"],
      "env": { "SEARCH_PROVIDER": "duckduckgo" }
    }
  }
}
```

and it returns zero of twelve, each call failing after ten to sixteen seconds with DuckDuckGo's anomaly message and `Failed to get the VQD` in its log. So its DuckDuckGo path does not work from here, and its working path is not DuckDuckGo.

The Bing redirect is not automatically a problem, since DuckDuckGo's organic index is largely Bing-sourced anyway. The problem is a URL that hides its destination in a base64 parameter, which quietly breaks deduplication and domain filters in whatever you build on top.

Three of the eight fail inside a successful tool call. Two return an empty array, one returns a prose sentence. If your agent checks for errors and then reads results, all three look like a search that found nothing.

``` js
const failed =
  res.isError ||
  !Array.isArray(items) || items.length === 0 ||
  /no results|bot detection|rate limit|anomaly/i.test(text.slice(0, 400));
```

Two details from the run. The array check alone misses web-scout, which returns no array at all. And an immediate retry does not help, because open-webSearch returned empty twice in a row, so back off with a cap and fail over to a second server instead.

For hand-driven searching, `free-search-mcp` with the engine pinned and the cache off. It never missed a call in three passes and it is the fastest of the ones that finished.

For an agent that runs unattended, the requirement is JSON with real links plus the check above, and that is a format question rather than a reliability one. nickclyde and free-search were just as reliable as anything else and both answer in prose. open-webSearch gives you JSON out of the box and is the one that silently empties. [Ours](https://hasdata.com/mcp/duckduckgo) gives you JSON with pagination, did not miss a call in forty-eight, starts answering in a couple of seconds with nothing installed, returns the heaviest payload here at 10 KB against nickclyde's 3.6, and costs money past a hundred searches a month.

On these numbers I would start with the free one and add the check.

The harness, the fixture and the twelve queries are at [github.com/HasData/mcp-bench](https://github.com/HasData/mcp-bench). Results move by IP and by day, so if yours disagree with mine I would rather know.
