Skip to main content
Glama
DocHatty

Community Research MCP

by DocHatty
serper.py7.47 kB
""" Serper API (Google Search). Real-time Google Search results including web, news, images, knowledge graph, and "People Also Ask" questions. API: https://serper.dev/ Rate Limits: 2,500/month (free tier) """ import logging import os from typing import Any, Optional import httpx __all__ = ["search", "search_news", "get_related"] # ══════════════════════════════════════════════════════════════════════════════ # Configuration # ══════════════════════════════════════════════════════════════════════════════ API_BASE = "https://google.serper.dev" API_TIMEOUT = 30.0 API_KEY = os.getenv("SERPER_API_KEY") logger = logging.getLogger(__name__) # ══════════════════════════════════════════════════════════════════════════════ # Search Functions # ══════════════════════════════════════════════════════════════════════════════ async def search( query: str, language: Optional[str] = None, *, max_results: int = 30, country: str = "us", locale: str = "en", ) -> list[dict[str, Any]]: """ Search Google via Serper API. Args: query: Search query string language: Programming language context (prepended to query) max_results: Maximum results to return country: Country code (e.g., 'us', 'uk') locale: Language code (e.g., 'en', 'es') Returns: List of results with title, url, snippet, and metadata Example: >>> results = await search("async patterns", language="python") """ if not API_KEY: logger.debug("Skipped: SERPER_API_KEY not set") return [] full_query = f"{language} {query}".strip() if language else query try: async with httpx.AsyncClient(timeout=API_TIMEOUT) as client: response = await client.post( f"{API_BASE}/search", headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"}, json={ "q": full_query, "num": min(max_results, 100), "gl": country, "hl": locale, }, ) response.raise_for_status() data = response.json() results = [] # Knowledge graph if kg := data.get("knowledgeGraph"): results.append( { "title": kg.get("title", "Knowledge Graph"), "url": kg.get("website", ""), "snippet": kg.get("description", ""), "type": "knowledge_graph", "source": "serper", } ) # Organic results for item in data.get("organic", []): if url := item.get("link"): results.append( { "title": item.get("title", ""), "url": url, "snippet": item.get("snippet", ""), "position": item.get("position", 0), "source": "serper", } ) # People Also Ask for item in data.get("peopleAlsoAsk", []): if url := item.get("link"): results.append( { "title": item.get("question", ""), "url": url, "snippet": item.get("snippet", ""), "type": "question", "source": "serper", } ) return results except httpx.HTTPStatusError as e: if e.response.status_code == 401: logger.error("Invalid API key") elif e.response.status_code == 429: logger.warning("Rate limit exceeded") else: logger.warning(f"HTTP {e.response.status_code}") return [] except Exception as e: logger.error(f"Search failed: {e}") return [] async def search_news( query: str, language: Optional[str] = None, *, max_results: int = 30, ) -> list[dict[str, Any]]: """ Search Google News via Serper. Args: query: Search query string language: Programming language context max_results: Maximum results to return Returns: List of news articles with title, url, date, source """ if not API_KEY: return [] full_query = f"{language} {query}".strip() if language else query try: async with httpx.AsyncClient(timeout=API_TIMEOUT) as client: response = await client.post( f"{API_BASE}/news", headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"}, json={"q": full_query, "num": min(max_results, 100)}, ) response.raise_for_status() data = response.json() return [ { "title": item.get("title", ""), "url": item.get("link", ""), "snippet": item.get("snippet", ""), "date": item.get("date", ""), "publisher": item.get("source", ""), "source": "serper:news", } for item in data.get("news", []) if item.get("link") ] except Exception as e: logger.warning(f"News search failed: {e}") return [] async def get_related(query: str) -> list[str]: """ Get related search suggestions. Args: query: Original search query Returns: List of related query strings """ if not API_KEY: return [] try: async with httpx.AsyncClient(timeout=API_TIMEOUT) as client: response = await client.post( f"{API_BASE}/search", headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"}, json={"q": query}, ) response.raise_for_status() data = response.json() return [ item.get("query", "") for item in data.get("relatedSearches", []) if item.get("query") ] except Exception as e: logger.warning(f"Related searches failed: {e}") return [] # ══════════════════════════════════════════════════════════════════════════════ # Backward Compatibility # ══════════════════════════════════════════════════════════════════════════════ search_serper = search search_serper_news = search_news get_serper_related_searches = get_related

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DocHatty/community-research-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server