{"id":51174146,"url":"https://github.com/bcambl/ddg-mcp","last_synced_at":"2026-06-27T02:31:19.596Z","repository":{"id":360112139,"uuid":"1248681978","full_name":"bcambl/ddg-mcp","owner":"bcambl","description":"basic duck duck go mcp server","archived":false,"fork":false,"pushed_at":"2026-05-25T02:56:52.000Z","size":40,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T04:28:10.570Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bcambl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":"DCO","cla":null}},"created_at":"2026-05-25T00:21:14.000Z","updated_at":"2026-05-25T02:55:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bcambl/ddg-mcp","commit_stats":null,"previous_names":["bcambl/ddg-mcp"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bcambl/ddg-mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcambl%2Fddg-mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcambl%2Fddg-mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcambl%2Fddg-mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcambl%2Fddg-mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bcambl","download_url":"https://codeload.github.com/bcambl/ddg-mcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcambl%2Fddg-mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34839888,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-27T02:31:18.878Z","updated_at":"2026-06-27T02:31:19.588Z","avatar_url":"https://github.com/bcambl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ddg-mcp\n\nA [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that provides web search and web fetch capabilities via [DuckDuckGo](https://duckduckgo.com).\n\nExposes `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.\n\n## Features\n\n- **Web search** via DuckDuckGo Lite endpoint (no API key required)\n  - Titles, URLs, snippets, and domain info per result\n  - Zero-click / instant answer extraction\n  - Pagination via offset/vqd tokens\n  - Region bias (`kl`), safe search (`kp`), and time range (`df`) filters\n  - Sponsored result detection\n  - Parser breakage detection with warning when HTML structure changes\n  - Automatic retry with exponential backoff + jitter on rate limiting (HTTP 429)\n- **Web fetch** — retrieve and extract readable content from URLs\n  - Strips scripts, styles, nav, header, footer elements\n  - Content truncation with configurable max length\n  - SSRF protection (blocks redirects to private/reserved IPs)\n- Query validation with length limits\n- Stdio transport for MCP compatibility\n- Cross-platform binaries via GoReleaser\n\n## Installation\n\n### Pre-built binaries\n\nDownload the latest release from the [releases page](https://github.com/bcambl/ddg-mcp/releases).\n\n### Go install\n\n```bash\ngo install github.com/bcambl/ddg-mcp/cmd/ddg-mcp@latest\n```\n\n### Build from source\n\n```bash\ngit clone https://github.com/bcambl/ddg-mcp.git\ncd ddg-mcp\nmake build\n```\n\n## Usage\n\n### Running the server\n\n```bash\nddg-mcp\n```\n\nThe server communicates over stdio using the MCP protocol. It is designed to be launched by MCP-compatible clients.\n\n### Environment Variables\n\nThe following environment variables can be used to customize server behavior without modifying the binary or client configuration:\n\n| Variable | Type | Default | Description |\n|----------|------|---------|-------------|\n| `DDG_SEARCH_URL` | string | `https://lite.duckduckgo.com/lite/` | DuckDuckGo search endpoint URL |\n| `DDG_TIMEOUT` | duration | `15s` | HTTP timeout for search requests |\n| `DDG_FETCH_TIMEOUT` | duration | `15s` | HTTP timeout for fetch requests |\n| `DDG_MAX_RETRIES` | int | `2` | Max retries on HTTP 429 (rate limited) for search |\n| `DDG_MAX_BODY_SIZE` | int (bytes) | `2097152` (2 MiB) | Maximum response body size for search and fetch |\n| `DDG_USER_AGENT` | string | Chrome 120 UA | User-Agent for search requests |\n| `DDG_FETCH_USER_AGENT` | string | Chrome 120 UA | User-Agent for fetch requests |\n| `DDG_DEFAULT_REGION` | string | *(none)* | Default region bias (e.g., `us-en`, `uk-en`, `de-de`) when not specified per-request |\n| `DDG_SSRF_PROTECTION` | bool | `true` | Enable SSRF protection for `web_fetch` (redirects to private IPs blocked) |\n| `DDG_LOG_LEVEL` | string | `info` | Log level: `debug`, `info`, `warn`, `error` |\n\nDuration values accept Go-style strings: `15s`, `1m`, `500ms`.\n\nBoolean values accept: `true`, `false`, `1`, `0`.\n\n**Example with Go install:**\n\n```bash\nDDG_DEFAULT_REGION=de-de DDG_TIMEOUT=30s ddg-mcp\n```\n\n### MCP client configuration\n\nAdd the server to your MCP client configuration.\n\n#### Claude Desktop\n\nEdit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n  \"mcpServers\": {\n    \"ddg-search\": {\n      \"command\": \"ddg-mcp\"\n    }\n  }\n}\n```\n\n#### Cursor\n\nAdd to `.cursor/mcp.json` in your project or global config:\n\n```json\n{\n  \"mcpServers\": {\n    \"ddg-search\": {\n      \"command\": \"ddg-mcp\"\n    }\n  }\n}\n```\n\n#### Windsurf\n\nAdd to your Windsurf MCP settings:\n\n```json\n{\n  \"mcpServers\": {\n    \"ddg-search\": {\n      \"command\": \"ddg-mcp\"\n    }\n  }\n}\n```\n\n#### opencode\n\nAdd to your `opencode.json`:\n\n```json\n{\n  \"mcp\": {\n    \"ddg-search\": {\n      \"command\": \"ddg-mcp\"\n    }\n  }\n}\n```\n\n#### Using Go install path\n\nIf installed via `go install`, use the full path:\n\n```json\n{\n  \"mcpServers\": {\n    \"ddg-search\": {\n      \"command\": \"go\",\n      \"args\": [\"run\", \"github.com/bcambl/ddg-mcp/cmd/ddg-mcp@latest\"]\n    }\n  }\n}\n```\n\n### Tool: `web_search`\n\nSearch 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.\n\n**Input:**\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `query` | string | Yes | The search query (max 500 characters) |\n| `region` | string | No | Region code for search bias (e.g., `us-en`, `uk-en`, `de-de`, `wt-wt` for no region) |\n| `safe_search` | int | No | Safe search level: `1`=strict, `-1`=moderate, `-2`=off. Default: `0` (DuckDuckGo default) |\n| `time_range` | string | No | Time range filter: `d`=day, `w`=week, `m`=month, `y`=year. Empty=any time |\n| `offset` | int | No | Result offset for pagination (0, 10, 20, etc.) |\n| `vqd` | string | No | Pagination token from previous search response, required when `offset \u003e 0` |\n\n**Output:**\n\n```json\n{\n  \"query\": \"golang mcp\",\n  \"result_count\": 3,\n  \"results\": [\n    {\n      \"title\": \"Example Result\",\n      \"url\": \"https://example.com/page\",\n      \"snippet\": \"A brief description of the search result.\",\n      \"domain\": \"example.com\",\n      \"sponsored\": false\n    }\n  ],\n  \"zero_click\": {\n    \"title\": \"Instant Answer Title\",\n    \"url\": \"https://example.com/answer\",\n    \"description\": \"Brief answer from DuckDuckGo's instant answers.\"\n  },\n  \"has_next_page\": true,\n  \"next_offset\": 10,\n  \"vqd\": \"3-123-456\",\n  \"parser_warning\": \"\"\n}\n```\n\n**Pagination:**\n\nTo get the next page of results, pass `offset` and `vqd` from the previous response:\n\n```json\n{\n  \"query\": \"golang mcp\",\n  \"offset\": 10,\n  \"vqd\": \"3-123-456\"\n}\n```\n\n**Error cases:**\n\n- Empty query: `\"query must not be empty\"`\n- Query too long: `\"query exceeds maximum length of 500 characters\"`\n- Invalid region: `\"region must match format xx-xx (e.g., us-en, uk-en, de-de)\"`\n- Invalid safe_search: `\"safe_search must be one of: 0 (default), 1 (strict), -1 (moderate), -2 (off)\"`\n- Invalid time_range: `\"time_range must be one of: d (day), w (week), m (month), y (year), or empty\"`\n- Paginated request without vqd: `\"vqd token is required for paginated requests (offset \u003e 0)\"`\n- Rate limited: `\"rate limited by DuckDuckGo (HTTP 429), please retry later\"`\n- Server error: `\"DuckDuckGo returned HTTP 500: ...\"`\n- Parser breakage: `\"parser may be broken: HTML contains result markers but no results were extracted\"`\n\n### Tool: `web_fetch`\n\nFetch 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.\n\n**Input:**\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `url` | string | Yes | The URL to fetch (must be HTTP or HTTPS) |\n| `max_length` | int | No | Maximum content length in characters (default 10000, max 50000) |\n\n**Output:**\n\n```json\n{\n  \"url\": \"https://example.com/page\",\n  \"title\": \"Example Page Title\",\n  \"content\": \"Extracted readable text content from the page...\",\n  \"content_length\": 8234,\n  \"content_type\": \"text/html; charset=utf-8\",\n  \"truncated\": false\n}\n```\n\n**Error cases:**\n\n- Empty URL: `\"url must not be empty\"`\n- Invalid URL: `\"url must be a valid HTTP or HTTPS URL\"`\n- Private IP (SSRF protection enabled): `\"url must not point to a private or reserved IP address\"`\n- Unsupported content type: `\"unsupported content type: application/pdf (only text/html and text/plain are supported)\"`\n- Server error: `\"fetch returned HTTP 404\"`\n\n## Development\n\n### Prerequisites\n\n- Go 1.26+\n\n### Build\n\n```bash\nmake build\n```\n\n### Test\n\n```bash\nmake test\n```\n\n### Run all checks\n\n```bash\nmake check\n```\n\n### Integration tests\n\nIntegration tests hit the real DuckDuckGo endpoint and require a build tag:\n\n```bash\ngo test -tags=integration -v ./...\n```\n\n## How it works\n\nThe server scrapes DuckDuckGo's Lite search endpoint (`lite.duckduckgo.com/lite/`) rather than using a formal API. This approach:\n\n- Requires no API key\n- Returns results similar to a browser search\n- Is subject to DuckDuckGo's rate limiting\n\n### Search\n\nDuckDuckGo wraps result URLs in redirect links (e.g., `//duckduckgo.com/l/?uddg=\u003cencoded-url\u003e`). The server extracts the real URL from the `uddg` query parameter and falls back to the raw `href` for direct links.\n\nPagination works by POSTing form data (including a `vqd` token and offset) back to the Lite endpoint when `offset \u003e 0`. The `vqd` token identifies the search session and is extracted from a hidden input in the results page.\n\nThe 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.\n\n### Fetch\n\nThe 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).\n\n## Limitations\n\n- Search results are limited to the first page per request (~10 results); pagination requires explicit offset/vqd tokens\n- Subject to DuckDuckGo rate limiting; avoid rapid successive queries\n- HTML scraping may break if DuckDuckGo changes their page structure (a `parser_warning` is included when detected)\n- No support for image search, news search, or other DuckDuckGo features\n- Fetch only supports `text/html` and `text/plain` content types\n\n## License\n\n[MIT](LICENSE)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbcambl%2Fddg-mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbcambl%2Fddg-mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbcambl%2Fddg-mcp/lists"}