Duibonduil's picture
Upload 17 files
3e11f9b verified
"""
Search MCP Server
This module provides MCP server functionality for performing web searches using various search engines.
It supports structured queries and returns formatted search results.
Key features:
- Perform web searches using Exa, Google, and DuckDuckGo
- Filter and format search results
- Validate and process search queries
Main functions:
- mcpsearchexa: Searches the web using Exa
- mcpsearchgoogle: Searches the web using Google
- mcpsearchduckduckgo: Searches the web using DuckDuckGo
"""
import os
import sys
import traceback
from typing import List, Optional
import requests
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
from aworld.logs.util import logger
# Initialize MCP server
mcp = FastMCP("search-server")
# Base search result model that all providers will use
class SearchResult(BaseModel):
"""Base search result model with common fields"""
id: str
title: str
url: str
snippet: str
source: str # Which search engine provided this result
class GoogleSearchResult(SearchResult):
"""Google-specific search result model"""
displayLink: str = ""
formattedUrl: str = ""
htmlSnippet: str = ""
htmlTitle: str = ""
kind: str = ""
link: str = ""
class SearchResponse(BaseModel):
"""Unified search response model"""
query: str
results: List[SearchResult]
count: int
source: str
error: Optional[str] = None
@mcp.tool(description="Search the web using Google Custom Search API.")
def mcpsearchgoogle(
query: str = Field(..., description="The search query string."),
num_results: int = Field(
10, description="Number of search results to return (default 10)."
),
safe_search: bool = Field(
True, description="Whether to enable safe search filtering."
),
language: str = Field("en", description="Language code for search results."),
country: str = Field("us", description="Country code for search results."),
) -> str:
"""
Search the web using Google Custom Search API.
Requires GOOGLE_API_KEY and GOOGLE_CSE_ID environment variables to be set.
"""
try:
api_key = os.environ.get("GOOGLE_API_KEY")
cse_id = os.environ.get("GOOGLE_CSE_ID")
if not api_key:
raise ValueError("GOOGLE_API_KEY environment variable not set")
if not cse_id:
raise ValueError("GOOGLE_CSE_ID environment variable not set")
# Ensure num_results is within valid range
num_results = max(1, num_results)
# Build the Google Custom Search API URL
url = "https://www.googleapis.com/customsearch/v1"
params = {
"key": api_key,
"cx": cse_id,
"q": query,
"num": num_results,
"safe": "active" if safe_search else "off",
"hl": language,
"gl": country,
}
logger.info(f"Google search starts for query: {query}")
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
search_results = []
if "items" in data:
for i, item in enumerate(data["items"]):
result = GoogleSearchResult(
id=f"google-{i}",
title=item.get("title", ""),
url=item.get("link", ""),
snippet=item.get("snippet", ""),
source="google",
displayLink=item.get("displayLink", ""),
formattedUrl=item.get("formattedUrl", ""),
htmlSnippet=item.get("htmlSnippet", ""),
htmlTitle=item.get("htmlTitle", ""),
kind=item.get("kind", ""),
link=item.get("link", ""),
)
search_results.append(result)
return SearchResponse(
query=query,
results=search_results,
count=len(search_results),
source="google",
).model_dump_json()
except Exception as e:
logger.error(f"Google search error: {traceback.format_exc()}")
return SearchResponse(
query=query, results=[], count=0, source="google", error=str(e)
).model_dump_json()
def main():
load_dotenv()
print("Starting Search MCP Server...", file=sys.stderr)
mcp.run(transport="stdio")
# Make the module callable
def __call__():
"""
Make the module callable for uvx.
This function is called when the module is executed directly.
"""
main()
sys.modules[__name__].__call__ = __call__
# Run the server when the script is executed directly
if __name__ == "__main__":
main()