Shrijeeth-Suresh commited on
Commit
3e53b8b
·
1 Parent(s): 7aa98fe

feat: create web search MCP with DuckDuckGo integration and Gradio UI

Browse files
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .venv
2
+ poetry.lock
3
+ .env
4
+ .ruff_cache
5
+ *.egg-info
6
+ build
7
+ __pycache__
Makefile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ install:
2
+ python -m pip install .
3
+
4
+ format:
5
+ python -m ruff format .
6
+
7
+ lint:
8
+ python -m ruff check --select I,RUF022 --fix .
9
+
10
+ check:
11
+ python -m ruff check .
app.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from search_engines.duckduckgo import duckduckgo_search
4
+
5
+ duckduckgo_interface = gr.Interface(
6
+ fn=duckduckgo_search,
7
+ inputs=[
8
+ gr.Textbox(label="Search Query"),
9
+ gr.Slider(minimum=1, maximum=10, step=1, value=5, label="Number of Results"),
10
+ ],
11
+ outputs=gr.Dataframe(label="Search Results", headers=["title", "body", "link"]),
12
+ title="DuckDuckGo Search",
13
+ description="Search the web using DuckDuckGo Search Engine.",
14
+ )
15
+
16
+ app = gr.TabbedInterface(
17
+ interface_list=[duckduckgo_interface],
18
+ tab_names=["DuckDuckGo Search"],
19
+ )
20
+
21
+
22
+ if __name__ == "__main__":
23
+ app.launch(
24
+ mcp_server=True,
25
+ )
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ python-dotenv==1.1.0
2
+ gradio==5.33.0
3
+ pydantic==2.11.5
4
+ pydantic_settings==2.9.1
5
+ langchain-community==0.3.24
6
+ duckduckgo-search==8.0.2
7
+ pandas==2.2.3
8
+ ruff==0.11.12
search_engines/__init__.py ADDED
File without changes
search_engines/duckduckgo.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from langchain_community.tools import DuckDuckGoSearchResults
3
+ from langchain_community.utilities.duckduckgo_search import DuckDuckGoSearchAPIWrapper
4
+
5
+ from utils.helpers import map_results
6
+
7
+
8
+ async def duckduckgo_search(query: str, max_results: int = 5) -> pd.DataFrame:
9
+ """
10
+ Given a search query, returns the search results from DuckDuckGo.
11
+
12
+ Args:
13
+ query (str): The search query.
14
+ max_results (int, optional): The number of maximum results to return. Defaults to 5.
15
+
16
+ Returns:
17
+ str: The search results from DuckDuckGo Search Engine.
18
+ """
19
+ ddg = DuckDuckGoSearchResults(
20
+ output_format="list",
21
+ )
22
+ ddg.max_results = max_results
23
+ results = await ddg.ainvoke(query)
24
+ mapping = {
25
+ "title": "title",
26
+ "link": "link",
27
+ "snippet": "body",
28
+ }
29
+ results = await map_results(results, mapping)
30
+ return results
utils/__init__.py ADDED
File without changes
utils/helpers.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+
3
+
4
+ async def map_results(results: list[dict], mapping: dict[str, str]) -> pd.DataFrame:
5
+ values = list(mapping.values())
6
+ if "title" not in values:
7
+ raise ValueError("title is required in the mapping")
8
+ if "link" not in values:
9
+ raise ValueError("link is required in the mapping")
10
+ if "body" not in values:
11
+ raise ValueError("body is required in the mapping")
12
+ mapped_data = [
13
+ {mapping.get(key, key): value for key, value in result.items()}
14
+ for result in results
15
+ ]
16
+ result = {"title": [], "link": [], "body": []}
17
+ for data in mapped_data:
18
+ result["title"].append(data["title"])
19
+ result["link"].append(data["link"])
20
+ result["body"].append(data["body"])
21
+ return pd.DataFrame(result)