README
¶
ddg-mcp
A Model Context Protocol (MCP) server that provides web search and web fetch capabilities via DuckDuckGo.
Exposes web_search and web_fetch tools that MCP-compatible clients (Claude Desktop, Cursor, Windsurf, opencode, etc.) can use to search the web and retrieve page content.
Features
- Web search via DuckDuckGo Lite endpoint (no API key required)
- Titles, URLs, snippets, and domain info per result
- Zero-click / instant answer extraction
- Pagination via offset/vqd tokens
- Region bias (
kl), safe search (kp), and time range (df) filters - Sponsored result detection
- Parser breakage detection with warning when HTML structure changes
- Automatic retry with exponential backoff + jitter on rate limiting (HTTP 429)
- Web fetch — retrieve and extract readable content from URLs
- Strips scripts, styles, nav, header, footer elements
- Content truncation with configurable max length
- SSRF protection (blocks redirects to private/reserved IPs)
- Query validation with length limits
- Stdio transport for MCP compatibility
- Cross-platform binaries via GoReleaser
Installation
Pre-built binaries
Download the latest release from the releases page.
Go install
go install github.com/bcambl/ddg-mcp@latest
Build from source
git clone https://github.com/bcambl/ddg-mcp.git
cd ddg-mcp
make build
Usage
Running the server
ddg-mcp
The server communicates over stdio using the MCP protocol. It is designed to be launched by MCP-compatible clients.
Environment Variables
The following environment variables can be used to customize server behavior without modifying the binary or client configuration:
| Variable | Type | Default | Description |
|---|---|---|---|
DDG_SEARCH_URL |
string | https://lite.duckduckgo.com/lite/ |
DuckDuckGo search endpoint URL |
DDG_TIMEOUT |
duration | 15s |
HTTP timeout for search requests |
DDG_FETCH_TIMEOUT |
duration | 15s |
HTTP timeout for fetch requests |
DDG_MAX_RETRIES |
int | 2 |
Max retries on HTTP 429 (rate limited) for search |
DDG_MAX_BODY_SIZE |
int (bytes) | 2097152 (2 MiB) |
Maximum response body size for search and fetch |
DDG_USER_AGENT |
string | Chrome 120 UA | User-Agent for search requests |
DDG_FETCH_USER_AGENT |
string | Chrome 120 UA | User-Agent for fetch requests |
DDG_DEFAULT_REGION |
string | (none) | Default region bias (e.g., us-en, uk-en, de-de) when not specified per-request |
DDG_SSRF_PROTECTION |
bool | true |
Enable SSRF protection for web_fetch (redirects to private IPs blocked) |
DDG_LOG_LEVEL |
string | info |
Log level: debug, info, warn, error |
Duration values accept Go-style strings: 15s, 1m, 500ms.
Boolean values accept: true, false, 1, 0.
Example with Go install:
DDG_DEFAULT_REGION=de-de DDG_TIMEOUT=30s ddg-mcp
MCP client configuration
Add the server to your MCP client configuration.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"ddg-search": {
"command": "ddg-mcp"
}
}
}
Cursor
Add to .cursor/mcp.json in your project or global config:
{
"mcpServers": {
"ddg-search": {
"command": "ddg-mcp"
}
}
}
Windsurf
Add to your Windsurf MCP settings:
{
"mcpServers": {
"ddg-search": {
"command": "ddg-mcp"
}
}
}
opencode
Add to your opencode.json:
{
"mcp": {
"ddg-search": {
"command": "ddg-mcp"
}
}
}
Using Go install path
If installed via go install, use the full path:
{
"mcpServers": {
"ddg-search": {
"command": "go",
"args": ["run", "github.com/bcambl/ddg-mcp@latest"]
}
}
}
Tool: web_search
Search the web using DuckDuckGo. Returns search results with titles, URLs, snippets, and domain info. Supports pagination, region bias, safe search, and time range filters. Zero-click / instant answer results are included when available.
Input:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | The search query (max 500 characters) |
region |
string | No | Region code for search bias (e.g., us-en, uk-en, de-de, wt-wt for no region) |
safe_search |
int | No | Safe search level: 1=strict, -1=moderate, -2=off. Default: 0 (DuckDuckGo default) |
time_range |
string | No | Time range filter: d=day, w=week, m=month, y=year. Empty=any time |
offset |
int | No | Result offset for pagination (0, 10, 20, etc.) |
vqd |
string | No | Pagination token from previous search response, required when offset > 0 |
Output:
{
"query": "golang mcp",
"result_count": 3,
"results": [
{
"title": "Example Result",
"url": "https://example.com/page",
"snippet": "A brief description of the search result.",
"domain": "example.com",
"sponsored": false
}
],
"zero_click": {
"title": "Instant Answer Title",
"url": "https://example.com/answer",
"description": "Brief answer from DuckDuckGo's instant answers."
},
"has_next_page": true,
"next_offset": 10,
"vqd": "3-123-456",
"parser_warning": ""
}
Pagination:
To get the next page of results, pass offset and vqd from the previous response:
{
"query": "golang mcp",
"offset": 10,
"vqd": "3-123-456"
}
Error cases:
- Empty query:
"query must not be empty" - Query too long:
"query exceeds maximum length of 500 characters" - Invalid region:
"region must match format xx-xx (e.g., us-en, uk-en, de-de)" - Invalid safe_search:
"safe_search must be one of: 0 (default), 1 (strict), -1 (moderate), -2 (off)" - Invalid time_range:
"time_range must be one of: d (day), w (week), m (month), y (year), or empty" - Paginated request without vqd:
"vqd token is required for paginated requests (offset > 0)" - Rate limited:
"rate limited by DuckDuckGo (HTTP 429), please retry later" - Server error:
"DuckDuckGo returned HTTP 500: ..." - Parser breakage:
"parser may be broken: HTML contains result markers but no results were extracted"
Tool: web_fetch
Fetch and extract readable content from a URL. Returns the page title and text content with scripts, styles, navigation, header, and footer elements removed. Only text/html and text/plain content types are supported.
Input:
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | The URL to fetch (must be HTTP or HTTPS) |
max_length |
int | No | Maximum content length in characters (default 10000, max 50000) |
Output:
{
"url": "https://example.com/page",
"title": "Example Page Title",
"content": "Extracted readable text content from the page...",
"content_length": 8234,
"content_type": "text/html; charset=utf-8",
"truncated": false
}
Error cases:
- Empty URL:
"url must not be empty" - Invalid URL:
"url must be a valid HTTP or HTTPS URL" - Private IP (SSRF protection enabled):
"url must not point to a private or reserved IP address" - Unsupported content type:
"unsupported content type: application/pdf (only text/html and text/plain are supported)" - Server error:
"fetch returned HTTP 404"
Development
Prerequisites
- Go 1.26+
Build
make build
Test
make test
Run all checks
make check
Integration tests
Integration tests hit the real DuckDuckGo endpoint and require a build tag:
go test -tags=integration -v ./...
How it works
The server scrapes DuckDuckGo's Lite search endpoint (lite.duckduckgo.com/lite/) rather than using a formal API. This approach:
- Requires no API key
- Returns results similar to a browser search
- Is subject to DuckDuckGo's rate limiting
Search
DuckDuckGo wraps result URLs in redirect links (e.g., //duckduckgo.com/l/?uddg=<encoded-url>). The server extracts the real URL from the uddg query parameter and falls back to the raw href for direct links.
Pagination works by POSTing form data (including a vqd token and offset) back to the Lite endpoint when offset > 0. The vqd token identifies the search session and is extracted from a hidden input in the results page.
The parser detects potential breakage — if the response HTML contains result markers (CSS classes, redirect link patterns) but no results were extracted, a parser_warning is included in the response.
Fetch
The fetch tool makes a GET request to the provided URL, strips unwanted HTML elements (scripts, styles, nav, header, footer), and returns the readable text content. It enforces a configurable max content length and blocks requests to private/reserved IPs when SSRF protection is enabled (default).
Limitations
- Search results are limited to the first page per request (~10 results); pagination requires explicit offset/vqd tokens
- Subject to DuckDuckGo rate limiting; avoid rapid successive queries
- HTML scraping may break if DuckDuckGo changes their page structure (a
parser_warningis included when detected) - No support for image search, news search, or other DuckDuckGo features
- Fetch only supports
text/htmlandtext/plaincontent types
License
Documentation
¶
There is no documentation for this package.