#
tokens: 1164/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .dockerignore
├── .env
├── .gitignore
├── .python-version
├── ddgs.py
├── docker-compose.yml
├── Dockerfile
├── pyproject.toml
├── README.md
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
1 | .venv
```

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.11
2 | 
```

--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------

```
1 | PORT=8000
2 | NETWORK_MODE=mcp-ddgs_default #or you can use "host"
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
1 | Simple Model Context Protocol server for searching Duck Duck Go, based on [Open-WebUI's built-in Web Search feature](https://github.com/open-webui/open-webui/blob/3f3a5bb0ab8ce3425f317f1e57b084523aa2b2a5/backend/open_webui/retrieval/web/duckduckgo.py) and using SSE transport
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM python:3.12-slim-bookworm
 2 | 
 3 | COPY --from=ghcr.io/astral-sh/uv:0.6.1 /uv /uvx /bin/
 4 | 
 5 | RUN apt update && apt install curl -y
 6 | 
 7 | ADD . /app
 8 | 
 9 | WORKDIR /app
10 | 
11 | RUN uv venv --python 3.12 venv
12 | 
13 | RUN uv sync --frozen
14 | 
15 | CMD ["uv", "run", "python", "ddgs.py" ]
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-ddgs"
 3 | version = "0.1.0"
 4 | description = "Simple Model Context Protocol server for searching Duck Duck Go"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | dependencies = [
 8 |     "duckduckgo-search>=7.4.2",
 9 |     "httpx>=0.28.1",
10 |     "mcp[cli]>=1.2.1",
11 | ]
12 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | services:
 2 |   mcp-ddgs:
 3 |     container_name: mcp-ddgs
 4 |     restart: unless-stopped
 5 |     pull_policy: build
 6 |     image: lowlyocean/mcp-ddgs:latest
 7 |     build:
 8 |       context: .
 9 |       network: host
10 |     ports:
11 |       - ${PORT}:${PORT}
12 |     environment:
13 |       FASTMCP_PORT: ${PORT}
14 |     extra_hosts:
15 |       - "host.docker.internal:host-gateway"
16 |     network_mode: ${NETWORK_MODE}
17 |   
```

--------------------------------------------------------------------------------
/ddgs.py:
--------------------------------------------------------------------------------

```python
 1 | from mcp.server.fastmcp import FastMCP
 2 | from duckduckgo_search import DDGS
 3 | from typing import Annotated
 4 | from pydantic import Field
 5 | 
 6 | # Initialize FastMCP server
 7 | mcp = FastMCP("ddgs")
 8 | 
 9 | @mcp.tool()
10 | async def search(query: Annotated[str, Field(description="The query to search for")]) -> list[dict]:
11 |     """
12 |     Search using DuckDuckGo's Search API and return the results as a list of dictionaries
13 |     """
14 |     # Use the DDGS context manager to create a DDGS object
15 |     with DDGS() as ddgs:
16 |         # Use the ddgs.text() method to perform the search
17 |         ddgs_gen = ddgs.text(
18 |             query, safesearch="moderate", max_results=3, backend="auto"
19 |         )
20 |         # Check if there are search results
21 |         if ddgs_gen:
22 |             # Convert the search results into a list
23 |             search_results = [r for r in ddgs_gen]
24 | 
25 |     # Return the list of search results
26 |     return [
27 |         {
28 |             "link": result["href"],
29 |             "title": result.get("title"),
30 |             "snippet": result.get("body"),
31 |         }
32 |         for result in search_results
33 |     ]
34 | 
35 | if __name__ == "__main__":
36 |     # Initialize and run the server
37 |     mcp.run(transport='sse')
```