search
Perform anonymous, enhanced Google searches with advanced filtering, including language, region, time range, and safe search options. Optimize results with customizable limits and anti-detection features.
Instructions
Search the web using Google with enhanced anonymization and anti-detection features. Supports advanced filtering options.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| language | No | Language code for results (e.g., 'en', 'es', 'fr', 'de', 'ja') | |
| limit | No | Maximum number of results to return (1-10, default: 5) | |
| query | Yes | Search query to execute | |
| region | No | Region code for localized results (e.g., 'us', 'uk', 'ca', 'au') | |
| safeSearch | No | Safe search filter level | |
| timeRange | No | Time range filter for recent results |
Implementation Reference
- src/search.ts:447-735 (handler)Core implementation of the search tool: performs anonymized Google web search with retries, DNS rotation, request fingerprinting, and robust result parsing using multiple strategies.export async function performSearch( query: string, options: SearchOptions = {} ): Promise<SearchResult[]> { const limit = Math.min(options.limit || 5, 10); // Test and find working DNS before starting search console.log("Testing DNS resolution..."); await DNSResolver.findWorkingDNS("google.com"); // Create axios instance with enhanced configuration const axiosInstance = axios.create({ maxRedirects: 3, timeout: 12000, validateStatus: (status) => status < 500, // Accept 4xx errors but retry on 5xx // DNS optimization for better reliability family: 4, // Force IPv4 to avoid IPv6 DNS issues // Use HTTPS agent with DNS optimization httpsAgent: new Agent({ family: 4, keepAlive: true, keepAliveMsecs: 1000, maxSockets: 5, maxFreeSockets: 2, timeout: 10000, }), }); // Configure enhanced retry logic with adaptive strategies axiosRetry(axiosInstance, { onRetry: async (retryCount, error, requestConfig) => { console.log(`Retry attempt ${retryCount} for ${requestConfig.url}`); // Rotate DNS on network errors or DNS-related failures if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN' || retryCount > 2) { console.log(`DNS-related error detected, rotating DNS server...`); DNSResolver.rotateDNS(); await DNSResolver.testDNSResolution("google.com"); } // Rotate user agent on retry to avoid detection if (retryCount > 1) { let newUserAgent = randomUseragent.getRandom(); // Try to get a desktop user agent for (let i = 0; i < 3; i++) { if (newUserAgent && (newUserAgent.includes('Windows') || newUserAgent.includes('Macintosh') || newUserAgent.includes('Linux'))) { break; } newUserAgent = randomUseragent.getRandom(); } requestConfig.headers = { ...requestConfig.headers, ...RequestAnonymizer.generateHeaders(newUserAgent) }; console.log(`Rotated user agent for retry ${retryCount}: ${newUserAgent.substring(0, 50)}...`); } }, retries: 8, retryCondition: (error) => { // Network errors - always retry if (axiosRetry.isNetworkError(error)) { return true; } // Idempotent request errors - always retry if (axiosRetry.isIdempotentRequestError(error)) { return true; } // HTTP status based retries if (error.response?.status) { const status = error.response.status; // Server errors (5xx) - always retry if (status >= 500) { return true; } // Rate limiting (429) - retry with longer delays if (status === 429) { return true; } // Temporary redirects that might resolve if (status === 302 || status === 307 || status === 308) { return true; } // Request timeout - retry if (status === 408) { return true; } // Too many requests from this IP - retry with longer delay if (status === 503) { return true; } } // Timeout errors - retry if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') { return true; } // DNS resolution errors - retry if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') { return true; } // Connection errors - retry if (error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED') { return true; } return false; }, retryDelay: (retryCount, error) => { // Adaptive delay based on error type let baseDelay = Math.pow(2, retryCount) * 1000; // Exponential backoff // Longer delays for rate limiting if (error?.response?.status === 429) { baseDelay = Math.pow(3, retryCount) * 2000; // More aggressive backoff for rate limits } // Shorter delays for network errors if (axiosRetry.isNetworkError(error)) { baseDelay = Math.min(baseDelay, 3000); // Cap network error delays } // Add jitter to prevent thundering herd const jitter = Math.random() * Math.min(1000, baseDelay * 0.3); const totalDelay = baseDelay + jitter; console.log(`Retry ${retryCount}: waiting ${totalDelay}ms (error: ${error?.response?.status || error?.code || 'unknown'})`); return totalDelay; }, }); // Generate realistic request fingerprint using random-useragent library // Try to get a desktop user agent, fallback to any if none found let userAgent = randomUseragent.getRandom(); // If we got a mobile user agent, try a few more times to get a desktop one for (let i = 0; i < 5; i++) { if (userAgent && (userAgent.includes('Windows') || userAgent.includes('Macintosh') || userAgent.includes('Linux'))) { break; } userAgent = randomUseragent.getRandom(); } let headers = RequestAnonymizer.generateHeaders(userAgent); let params = SearchParameterBuilder.build(query, options); // Add random delay to avoid detection await RequestAnonymizer.addRandomDelay(); // Multiple attempt strategy with different approaches const maxAttempts = 5; let lastError: Error | null = null; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { console.log(`Search attempt ${attempt}/${maxAttempts} for query: "${query}"`); // Update params for this attempt params = SearchParameterBuilder.build(query, options, attempt); const config: AxiosRequestConfig = { decompress: true, headers, params, // Increase timeout for later attempts timeout: 12000 + (attempt - 1) * 3000, }; const response = await axiosInstance.get("https://www.google.com/search", config); // Validate response if (response.status === 429) { const retryAfter = response.headers['retry-after']; const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 5000; console.log(`Rate limited, waiting ${waitTime}ms before next attempt`); await new Promise(resolve => setTimeout(resolve, waitTime)); if (attempt === maxAttempts) { throw new Error("Rate limited by Google. Please try again later."); } continue; } if (response.status >= 400) { if (attempt === maxAttempts) { throw new Error(`Google search failed with status ${response.status}`); } console.log(`HTTP ${response.status} on attempt ${attempt}, retrying...`); await RequestAnonymizer.addRandomDelay(); continue; } // Check if we got a valid response if (!response.data || response.data.length < 1000) { if (attempt === maxAttempts) { throw new Error("Received invalid or empty response from Google"); } console.log(`Invalid response on attempt ${attempt}, retrying...`); await RequestAnonymizer.addRandomDelay(); continue; } const results = ResultParser.parse(response.data, limit); // If we got results, return them if (results.length > 0) { console.log(`Successfully found ${results.length} results on attempt ${attempt}`); return results; } // If no results and this is not the last attempt, try again if (attempt < maxAttempts) { console.log(`No results found on attempt ${attempt}, retrying with different strategy...`); // Rotate user agent for next attempt let newUserAgent = randomUseragent.getRandom(); for (let i = 0; i < 3; i++) { if (newUserAgent && (newUserAgent.includes('Windows') || newUserAgent.includes('Macintosh') || newUserAgent.includes('Linux'))) { break; } newUserAgent = randomUseragent.getRandom(); } headers = RequestAnonymizer.generateHeaders(newUserAgent); await RequestAnonymizer.addRandomDelay(); continue; } // Last attempt and no results return results; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); console.log(`Attempt ${attempt} failed:`, lastError.message); if (attempt === maxAttempts) { break; } // Wait before next attempt with shorter delays for more aggressive retries const waitTime = Math.min(1000 * attempt, 5000); console.log(`Waiting ${waitTime}ms before next attempt...`); await new Promise(resolve => setTimeout(resolve, waitTime)); // Rotate user agent for next attempt let newUserAgent = randomUseragent.getRandom(); for (let i = 0; i < 3; i++) { if (newUserAgent && (newUserAgent.includes('Windows') || newUserAgent.includes('Macintosh') || newUserAgent.includes('Linux'))) { break; } newUserAgent = randomUseragent.getRandom(); } headers = RequestAnonymizer.generateHeaders(newUserAgent); } } // All attempts failed, throw the last error with enhanced message if (lastError) { if (axios.isAxiosError(lastError)) { if (lastError.code === "ECONNABORTED" || lastError.code === "ETIMEDOUT") { throw new Error("Search request timed out after multiple attempts. Please check your internet connection and try again."); } if (lastError.response?.status === 403) { throw new Error("Access denied by Google after multiple attempts. The requests may have been blocked. Please try again later."); } if (lastError.response?.status === 429) { throw new Error("Rate limited by Google after multiple attempts. Please try again in a few minutes."); } if (lastError.response?.status === 503) { throw new Error("Google service temporarily unavailable. Please try again later."); } } throw new Error(`Search failed after ${maxAttempts} attempts: ${lastError.message}`); } throw new Error(`Search failed after ${maxAttempts} attempts: Unknown error`); }
- src/server.ts:56-120 (handler)MCP tool handler wrapper: processes arguments, calls performSearch, formats results, provides logging and user-friendly error messages.execute: async (args, { log }) => { try { log.info("Starting enhanced web search", { query: args.query, limit: args.limit, options: { language: args.language, region: args.region, safeSearch: args.safeSearch, timeRange: args.timeRange, }, }); const searchOptions: SearchOptions = { limit: args.limit, language: args.language, region: args.region, safeSearch: args.safeSearch, timeRange: args.timeRange, }; const results = await performSearch(args.query, searchOptions); log.info("Search completed successfully", { resultCount: results.length, query: args.query, }); if (results.length === 0) { return "No search results found. This could be due to:\n" + "- Very specific or uncommon search terms\n" + "- Temporary blocking by Google (try again later)\n" + "- Network connectivity issues\n" + "- Search filters being too restrictive"; } // Format results in a readable way const formattedResults = results.map((result, index) => { return `${index + 1}. **${result.title}**\n` + ` URL: ${result.url}\n` + ` Description: ${result.description || "No description available"}\n`; }).join("\n"); return `Found ${results.length} search results for "${args.query}":\n\n${formattedResults}`; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; log.error("Search failed", { query: args.query, error: errorMessage, }); // Provide helpful error messages to users if (errorMessage.includes("Rate limited")) { return `Search temporarily unavailable due to rate limiting. Please try again in a few minutes.\n\nQuery: "${args.query}"`; } else if (errorMessage.includes("Access denied") || errorMessage.includes("blocked")) { return `Search request was blocked by Google. This can happen with frequent requests. Please try again later.\n\nQuery: "${args.query}"`; } else if (errorMessage.includes("timeout")) { return `Search request timed out. Please check your internet connection and try again.\n\nQuery: "${args.query}"`; } else { return `Search failed: ${errorMessage}\n\nQuery: "${args.query}"\n\nTip: Try rephrasing your search query or try again later.`; } } },
- src/server.ts:25-50 (schema)Zod input schema defining parameters for the search tool: query (required), limit, language, region, safeSearch, timeRange.parameters: z.object({ query: z.string().describe("Search query to execute"), limit: z .number() .min(1) .max(10) .optional() .default(5) .describe("Maximum number of results to return (1-10, default: 5)"), language: z .string() .optional() .describe("Language code for results (e.g., 'en', 'es', 'fr', 'de', 'ja')"), region: z .string() .optional() .describe("Region code for localized results (e.g., 'us', 'uk', 'ca', 'au')"), safeSearch: z .enum(["off", "moderate", "strict"]) .optional() .describe("Safe search filter level"), timeRange: z .enum(["hour", "day", "week", "month", "year"]) .optional() .describe("Time range filter for recent results"), }),
- src/server.ts:22-121 (registration)Registration of the 'search' tool object with FastMCP server.addTool method.server.addTool({ name: "search", description: "Search the web using Google with enhanced anonymization and anti-detection features. Supports advanced filtering options.", parameters: z.object({ query: z.string().describe("Search query to execute"), limit: z .number() .min(1) .max(10) .optional() .default(5) .describe("Maximum number of results to return (1-10, default: 5)"), language: z .string() .optional() .describe("Language code for results (e.g., 'en', 'es', 'fr', 'de', 'ja')"), region: z .string() .optional() .describe("Region code for localized results (e.g., 'us', 'uk', 'ca', 'au')"), safeSearch: z .enum(["off", "moderate", "strict"]) .optional() .describe("Safe search filter level"), timeRange: z .enum(["hour", "day", "week", "month", "year"]) .optional() .describe("Time range filter for recent results"), }), annotations: { title: "Enhanced Web Search", readOnlyHint: true, openWorldHint: true, }, execute: async (args, { log }) => { try { log.info("Starting enhanced web search", { query: args.query, limit: args.limit, options: { language: args.language, region: args.region, safeSearch: args.safeSearch, timeRange: args.timeRange, }, }); const searchOptions: SearchOptions = { limit: args.limit, language: args.language, region: args.region, safeSearch: args.safeSearch, timeRange: args.timeRange, }; const results = await performSearch(args.query, searchOptions); log.info("Search completed successfully", { resultCount: results.length, query: args.query, }); if (results.length === 0) { return "No search results found. This could be due to:\n" + "- Very specific or uncommon search terms\n" + "- Temporary blocking by Google (try again later)\n" + "- Network connectivity issues\n" + "- Search filters being too restrictive"; } // Format results in a readable way const formattedResults = results.map((result, index) => { return `${index + 1}. **${result.title}**\n` + ` URL: ${result.url}\n` + ` Description: ${result.description || "No description available"}\n`; }).join("\n"); return `Found ${results.length} search results for "${args.query}":\n\n${formattedResults}`; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; log.error("Search failed", { query: args.query, error: errorMessage, }); // Provide helpful error messages to users if (errorMessage.includes("Rate limited")) { return `Search temporarily unavailable due to rate limiting. Please try again in a few minutes.\n\nQuery: "${args.query}"`; } else if (errorMessage.includes("Access denied") || errorMessage.includes("blocked")) { return `Search request was blocked by Google. This can happen with frequent requests. Please try again later.\n\nQuery: "${args.query}"`; } else if (errorMessage.includes("timeout")) { return `Search request timed out. Please check your internet connection and try again.\n\nQuery: "${args.query}"`; } else { return `Search failed: ${errorMessage}\n\nQuery: "${args.query}"\n\nTip: Try rephrasing your search query or try again later.`; } } }, });
- src/search.ts:10-22 (schema)TypeScript interfaces defining SearchResult output structure and SearchOptions input for the performSearch function.export interface SearchResult { description: string; title: string; url: string; } export interface SearchOptions { limit?: number; language?: string; region?: string; safeSearch?: "moderate" | "off" | "strict"; timeRange?: "day" | "hour" | "month" | "week" | "year"; }