Skip to main content
Glama
DocHatty

Community Research MCP

by DocHatty
brave.py6.93 kB
""" Brave Search API. Privacy-focused web search with news, filtering, freshness controls, and extra snippet extraction. API: https://brave.com/search/api/ Rate Limits: 2,000/month (free tier) """ import logging import os from typing import Any, Optional import httpx __all__ = ["search", "search_news"] # ══════════════════════════════════════════════════════════════════════════════ # Configuration # ══════════════════════════════════════════════════════════════════════════════ API_BASE = "https://api.search.brave.com/res/v1" API_TIMEOUT = 30.0 API_KEY = os.getenv("BRAVE_SEARCH_API_KEY") logger = logging.getLogger(__name__) # ══════════════════════════════════════════════════════════════════════════════ # Search Functions # ══════════════════════════════════════════════════════════════════════════════ async def search( query: str, language: Optional[str] = None, *, max_results: int = 20, freshness: Optional[str] = None, country: str = "us", ) -> list[dict[str, Any]]: """ Search the web via Brave Search API. Args: query: Search query string (max 400 chars) language: Programming language context (prepended to query) max_results: Maximum results (1-20) freshness: Filter by age - 'pd' (24h), 'pw' (week), 'pm' (month), 'py' (year) country: Country code for results Returns: List of results with title, url, snippet Example: >>> results = await search("websocket tutorial", language="python") """ if not API_KEY: logger.debug("Skipped: BRAVE_SEARCH_API_KEY not set") return [] full_query = f"{language} {query}".strip() if language else query full_query = full_query[:400] # API limit params: dict[str, Any] = { "q": full_query, "count": min(max(max_results, 1), 20), "country": country, "extra_snippets": "true", } if freshness: params["freshness"] = freshness try: async with httpx.AsyncClient(timeout=API_TIMEOUT) as client: response = await client.get( f"{API_BASE}/web/search", headers={ "Accept": "application/json", "X-Subscription-Token": API_KEY, }, params=params, ) response.raise_for_status() data = response.json() results = [] # Web results for item in data.get("web", {}).get("results", []): if url := item.get("url"): # Combine main snippet with extras snippets = [item.get("description", "")] snippets.extend(item.get("extra_snippets", [])) results.append( { "title": item.get("title", ""), "url": url, "snippet": " ".join(filter(None, snippets)), "age": item.get("age", ""), "source": "brave", } ) # Include news if present for item in data.get("news", {}).get("results", []): if url := item.get("url"): results.append( { "title": item.get("title", ""), "url": url, "snippet": item.get("description", ""), "age": item.get("age", ""), "type": "news", "source": "brave", } ) 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 = 20, freshness: Optional[str] = None, ) -> list[dict[str, Any]]: """ Search Brave News. Args: query: Search query string language: Programming language context max_results: Maximum results (1-20) freshness: Filter by age - 'pd', 'pw', 'pm' Returns: List of news articles """ if not API_KEY: return [] full_query = f"{language} {query}".strip() if language else query params: dict[str, Any] = { "q": full_query[:400], "count": min(max(max_results, 1), 20), } if freshness: params["freshness"] = freshness try: async with httpx.AsyncClient(timeout=API_TIMEOUT) as client: response = await client.get( f"{API_BASE}/news/search", headers={ "Accept": "application/json", "X-Subscription-Token": API_KEY, }, params=params, ) response.raise_for_status() data = response.json() return [ { "title": item.get("title", ""), "url": item.get("url", ""), "snippet": item.get("description", ""), "age": item.get("age", ""), "publisher": item.get("meta_url", {}).get("hostname", ""), "source": "brave:news", } for item in data.get("results", []) if item.get("url") ] except Exception as e: logger.warning(f"News search failed: {e}") return [] # ══════════════════════════════════════════════════════════════════════════════ # Backward Compatibility # ══════════════════════════════════════════════════════════════════════════════ search_brave = search search_brave_web = search search_brave_news = search_news

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