Skip to main content
Glama

web_search

Perform web searches using a privacy-respecting metasearch engine with customizable parameters like language, time range, categories, and safe search levels to find relevant online content.

Instructions

Perform web searches using SearXNG, a privacy-respecting metasearch engine. Returns relevant web content with customizable parameters. Returns a Dictionary response with status, message, data (search results), and error if any.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
categoriesNoCategories to search in (e.g., 'general', 'images', 'news'). Default: null (all categories).
enginesNoSpecific search engines to use. Default: null (all available engines).
languageNoLanguage code for search results (e.g., 'en', 'de', 'fr'). Default: 'en'en
max_resultsNoMaximum number of search results to return. Range: 1-50. Default: 10.
pagenoNoPage number for results. Must be minimum 1. Default: 1.
queryNoSearch query
safesearchNoSafe search level: 0 (off), 1 (moderate), 2 (strict). Default: 1 (moderate).
time_rangeNoTime range for search results. Options: 'day', 'week', 'month', 'year'. Default: null (no time restriction).

Implementation Reference

  • Implementation of the 'web_search' tool handler. This async function is decorated with @mcp.tool(), defining the input schema via Pydantic Fields and executing the search logic by querying a SearXNG instance, handling authentication, progress reporting, error handling, and returning formatted results.
    @mcp.tool( annotations={ "title": "SearXNG Search", "readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, "openWorldHint": False, }, tags={"search"}, ) async def web_search( query: str = Field(description="Search query", default=None), language: str = Field( description="Language code for search results (e.g., 'en', 'de', 'fr'). Default: 'en'", default="en", ), time_range: Optional[str] = Field( description="Time range for search results. Options: 'day', 'week', 'month', 'year'. Default: null (no time restriction).", default=None, ), categories: Optional[List[str]] = Field( description="Categories to search in (e.g., 'general', 'images', 'news'). Default: null (all categories).", default=None, ), engines: Optional[List[str]] = Field( description="Specific search engines to use. Default: null (all available engines).", default=None, ), safesearch: int = Field( description="Safe search level: 0 (off), 1 (moderate), 2 (strict). Default: 1 (moderate).", default=1, ), pageno: int = Field( description="Page number for results. Must be minimum 1. Default: 1.", default=1, ge=1, ), max_results: int = Field( description="Maximum number of search results to return. Range: 1-50. Default: 10.", default=10, ge=1, le=50, ), ctx: Context = Field( description="MCP context for progress reporting.", default=None ), ) -> Dict[str, Any]: """ Perform web searches using SearXNG, a privacy-respecting metasearch engine. Returns relevant web content with customizable parameters. Returns a Dictionary response with status, message, data (search results), and error if any. """ logger = logging.getLogger("SearXNG") logger.debug(f"[SearXNG] Searching for: {query}") try: if not query: return { "status": 400, "message": "Invalid input: query must not be empty", "data": None, "error": "query must not be empty", } # Prepare search parameters search_params = { "q": query, "format": "json", "language": language, "safesearch": safesearch, "pageno": pageno, } if time_range: search_params["time_range"] = time_range if categories: search_params["categories"] = ",".join(categories) if engines: search_params["engines"] = ",".join(engines) # Report initial progress if ctx is available if ctx: await ctx.report_progress(progress=0, total=100) logger.debug("Reported initial progress: 0/100") # Make request to SearXNG auth = (SEARXNG_USERNAME, SEARXNG_PASSWORD) if HAS_BASIC_AUTH else None response = requests.get( f"{SEARXNG_INSTANCE_URL}/search", params=search_params, auth=auth ) response.raise_for_status() search_response: Dict[str, Any] = response.json() # Limit results limited_results = search_response.get("results", [])[:max_results] # Construct final response final_response = { **search_response, "results": limited_results, "number_of_results": len(limited_results), } # Report completion if ctx: await ctx.report_progress(progress=100, total=100) logger.debug("Reported final progress: 100/100") logger.debug(f"[SearXNG] Search completed for query: {query}") return { "status": 200, "message": "Search completed successfully", "data": final_response, "error": None, } except requests.exceptions.HTTPError as e: status_code = e.response.status_code if e.response else None if status_code == 401: error_msg = "Authentication failed. Please check your SearXNG username and password." else: error_msg = f"SearXNG API error: {e.response.json().get('message', str(e)) if e.response else str(e)}" logger.error(f"[SearXNG Error] {error_msg}") return { "status": status_code or 500, "message": "Failed to perform search", "data": None, "error": error_msg, } except Exception as e: logger.error(f"[SearXNG Error] {str(e)}") return { "status": 500, "message": "Failed to perform search", "data": None, "error": str(e), }

Other Tools

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/Knuckles-Team/searxng-mcp'

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