Skip to main content
Glama
brendon92

Specialized AI Search Tools

by brendon92

websearch

Search the web across multiple engines with advanced filters for file type, language, region, date range, and domains to find specific information.

Instructions

Search the web using various search engines (DuckDuckGo, Brave, Google, Bing, SerpAPI) with advanced filtering options. Returns a list of search results with titles, URLs, and snippets.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query string
engineNoSearch engine to useduckduckgo
limitNoMaximum number of results
filtersNo

Implementation Reference

  • The protected execute method implements the core logic of the websearch tool, handling input parameters and dispatching to specific search engine implementations based on the 'engine' parameter.
    protected async execute(params: WebSearchParams): Promise<SearchResult[]> { logger.info(`Searching with ${params.engine}`, { query: params.query, limit: params.limit }); try { switch (params.engine) { case 'duckduckgo': return await this.searchDuckDuckGo(params); case 'brave': return await this.searchBrave(params); case 'google': return await this.searchGoogle(params); case 'bing': return await this.searchBing(params); case 'serpapi': return await this.searchSerpApi(params); default: throw new Error(`Unsupported search engine: ${params.engine}`); } } catch (error) { logger.error(`Search failed for ${params.engine}`, { error }); throw new Error( `Search failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } }
  • Defines the input schema (webSearchSchema) for the websearch tool using Zod, including query, engine selection, result limit, and optional filters.
    const webSearchSchema = z.object({ query: z.string().min(1).describe('Search query string'), engine: z .enum(['duckduckgo', 'brave', 'google', 'bing', 'serpapi']) .optional() .default('duckduckgo') .describe('Search engine to use'), limit: z.number().int().min(1).max(50).optional().default(10).describe('Maximum number of results'), filters: searchFiltersSchema, });
  • Registers the WebSearchTool instance (new WebSearchTool()) along with other tools to the MCP server via server.registerTools().
    export function registerAllTools(server: MCPServer): void { const tools: BaseTool[] = [ new WebSearchTool(), new WebFetchTool(), new TypeConversionTool(), ]; // Register all tools if (tools.length > 0) { server.registerTools(tools); logger.info(`Registered ${tools.length} tool(s): ${tools.map(t => t.name).join(', ')}`); } else { logger.warn('No tools registered - add tool implementations to src/tools/index.ts'); } }
  • Supporting schema for search filters used within the websearch tool's main schema.
    const searchFiltersSchema = z .object({ fileType: z.string().optional().describe('Filter by file type (e.g., pdf, doc, xls)'), language: z.string().optional().describe('Filter by language code (e.g., en, pl, de)'), region: z.string().optional().describe('Filter by region/country code (e.g., us, pl, uk)'), dateRange: z .enum(['day', 'week', 'month', 'year']) .optional() .describe('Filter by date range'), domain: z.string().optional().describe('Filter by specific domain (e.g., github.com)'), exactPhrase: z.boolean().optional().describe('Search for exact phrase match'), }) .optional();
  • The WebSearchTool class definition, including name, description, schema assignment, execute handler, and private helper methods for different search engines.
    export class WebSearchTool extends BaseTool<typeof webSearchSchema> { readonly name = 'websearch'; readonly description = 'Search the web using various search engines (DuckDuckGo, Brave, Google, Bing, SerpAPI) with advanced filtering options. Returns a list of search results with titles, URLs, and snippets.'; readonly schema = webSearchSchema; protected async execute(params: WebSearchParams): Promise<SearchResult[]> { logger.info(`Searching with ${params.engine}`, { query: params.query, limit: params.limit }); try { switch (params.engine) { case 'duckduckgo': return await this.searchDuckDuckGo(params); case 'brave': return await this.searchBrave(params); case 'google': return await this.searchGoogle(params); case 'bing': return await this.searchBing(params); case 'serpapi': return await this.searchSerpApi(params); default: throw new Error(`Unsupported search engine: ${params.engine}`); } } catch (error) { logger.error(`Search failed for ${params.engine}`, { error }); throw new Error( `Search failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Search using DuckDuckGo (free, no API key required) */ private async searchDuckDuckGo(params: WebSearchParams): Promise<SearchResult[]> { let query = params.query; // Apply filters to query if (params.filters?.exactPhrase) { query = `"${query}"`; } if (params.filters?.fileType) { query += ` filetype:${params.filters.fileType}`; } if (params.filters?.domain) { query += ` site:${params.filters.domain}`; } const url = 'https://html.duckduckgo.com/html/'; const response = await httpClient.post( url, new URLSearchParams({ q: query, kl: params.filters?.region || '', }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, } ); const $ = cheerio.load(response.data); const results: SearchResult[] = []; $('.result').each((_, element): false | void => { if (results.length >= params.limit) return false; const $result = $(element); const $title = $result.find('.result__title'); const $snippet = $result.find('.result__snippet'); const title = $title.text().trim(); const snippet = $snippet.text().trim(); const url = $title.find('a').attr('href') || ''; if (title && url) { results.push({ title, url: this.cleanDuckDuckGoUrl(url), snippet, source: 'duckduckgo', }); } }); logger.info(`DuckDuckGo search completed`, { resultsFound: results.length }); return results; } /** * Search using Brave Search API */ private async searchBrave(params: WebSearchParams): Promise<SearchResult[]> { const apiKey = process.env.BRAVE_API_KEY; if (!apiKey) { throw new Error('BRAVE_API_KEY environment variable is required for Brave Search'); } let query = params.query; if (params.filters?.exactPhrase) { query = `"${query}"`; } if (params.filters?.fileType) { query += ` filetype:${params.filters.fileType}`; } if (params.filters?.domain) { query += ` site:${params.filters.domain}`; } const url = 'https://api.search.brave.com/res/v1/web/search'; const response = await httpClient.get(url, { params: { q: query, count: params.limit, country: params.filters?.region?.toUpperCase(), }, headers: { 'X-Subscription-Token': apiKey, Accept: 'application/json', }, }); const results: SearchResult[] = (response.data.web?.results || []).map((item: any) => ({ title: item.title, url: item.url, snippet: item.description || '', source: 'brave', })); logger.info(`Brave search completed`, { resultsFound: results.length }); return results; } /** * Search using Google Custom Search API */ private async searchGoogle(params: WebSearchParams): Promise<SearchResult[]> { const apiKey = process.env.GOOGLE_API_KEY; const searchEngineId = process.env.GOOGLE_SEARCH_ENGINE_ID; if (!apiKey || !searchEngineId) { throw new Error( 'GOOGLE_API_KEY and GOOGLE_SEARCH_ENGINE_ID environment variables are required for Google Search' ); } let query = params.query; if (params.filters?.exactPhrase) { query = `"${query}"`; } if (params.filters?.fileType) { query += ` filetype:${params.filters.fileType}`; } if (params.filters?.domain) { query += ` site:${params.filters.domain}`; } const url = 'https://www.googleapis.com/customsearch/v1'; const response = await httpClient.get(url, { params: { key: apiKey, cx: searchEngineId, q: query, num: Math.min(params.limit, 10), // Google API max is 10 per request lr: params.filters?.language ? `lang_${params.filters.language}` : undefined, gl: params.filters?.region, dateRestrict: this.getGoogleDateRestrict(params.filters?.dateRange), }, }); const results: SearchResult[] = (response.data.items || []).map((item: any) => ({ title: item.title, url: item.link, snippet: item.snippet || '', source: 'google', })); logger.info(`Google search completed`, { resultsFound: results.length }); return results; } /** * Search using Bing Search API */ private async searchBing(params: WebSearchParams): Promise<SearchResult[]> { const apiKey = process.env.BING_API_KEY; if (!apiKey) { throw new Error('BING_API_KEY environment variable is required for Bing Search'); } let query = params.query; if (params.filters?.exactPhrase) { query = `"${query}"`; } if (params.filters?.fileType) { query += ` filetype:${params.filters.fileType}`; } if (params.filters?.domain) { query += ` site:${params.filters.domain}`; } const url = 'https://api.bing.microsoft.com/v7.0/search'; const response = await httpClient.get(url, { params: { q: query, count: params.limit, mkt: params.filters?.region || 'en-US', freshness: this.getBingFreshness(params.filters?.dateRange), }, headers: { 'Ocp-Apim-Subscription-Key': apiKey, }, }); const results: SearchResult[] = (response.data.webPages?.value || []).map((item: any) => ({ title: item.name, url: item.url, snippet: item.snippet || '', source: 'bing', })); logger.info(`Bing search completed`, { resultsFound: results.length }); return results; } /** * Clean DuckDuckGo redirect URLs */ private cleanDuckDuckGoUrl(url: string): string { try { const urlObj = new URL(url, 'https://duckduckgo.com'); const uddg = urlObj.searchParams.get('uddg'); return uddg || url; } catch { return url; } } /** * Convert date range to Google dateRestrict format */ private getGoogleDateRestrict(dateRange?: string): string | undefined { if (!dateRange) return undefined; const map: Record<string, string> = { day: 'd1', week: 'w1', month: 'm1', year: 'y1', }; return map[dateRange]; } /** * Convert date range to Bing freshness format */ private getBingFreshness(dateRange?: string): string | undefined { if (!dateRange) return undefined; const map: Record<string, string> = { day: 'Day', week: 'Week', month: 'Month', year: 'Year', }; return map[dateRange]; } /** * Search using SerpAPI (supports multiple search engines) */ private async searchSerpApi(params: WebSearchParams): Promise<SearchResult[]> { const apiKey = process.env.SERP_API_KEY; if (!apiKey) { throw new Error('SERP_API_KEY environment variable is required for SerpAPI'); } let query = params.query; if (params.filters?.exactPhrase) { query = `"${query}"`; } if (params.filters?.fileType) { query += ` filetype:${params.filters.fileType}`; } if (params.filters?.domain) { query += ` site:${params.filters.domain}`; } const url = 'https://serpapi.com/search'; const response = await httpClient.get(url, { params: { api_key: apiKey, q: query, num: params.limit, engine: 'google', // SerpAPI supports multiple engines, defaulting to google hl: params.filters?.language || 'en', gl: params.filters?.region || 'us', tbs: this.getSerpApiDateFilter(params.filters?.dateRange), }, }); const results: SearchResult[] = (response.data.organic_results || []).map((item: any) => ({ title: item.title, url: item.link, snippet: item.snippet || '', source: 'serpapi', })); logger.info(`SerpAPI search completed`, { resultsFound: results.length }); return results.slice(0, params.limit); } /** * Convert date range to SerpAPI tbs format */ private getSerpApiDateFilter(dateRange?: string): string | undefined { if (!dateRange) return undefined; const map: Record<string, string> = { day: 'qdr:d', week: 'qdr:w', month: 'qdr:m', year: 'qdr:y', }; return map[dateRange]; } }

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/brendon92/mcp-server'

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