google_search
Perform Google searches using the official API with automatic key rotation, intelligent quota management, and advanced filtering options. Supports multi-language, geolocation, and SafeSearch for tailored results.
Instructions
Performs Google searches using the official API with automatic API key rotation.
Features:
Official Google Web Search
Automatic API key rotation
Intelligent quota management
Multi-language and geolocation support
Parameters:
query: Search query (required)
num: Number of results (1-10, default: 5)
start: Starting index (default: 1)
safe: SafeSearch (off/active, default: off)
lr: Language (ex: lang_fr, lang_en)
gl: Country (ex: fr, us, uk)
Returns a JSON list of results with title, link, description and domain.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| cr | No | Country restriction (ex: countryFR, countryUS) | |
| dateRestrict | No | Time filter (ex: d1=24h, w1=week, m1=month, y1=year) | |
| exactTerms | No | Exact phrase required | |
| excludeTerms | No | Words to exclude from search | |
| fileType | No | File type (ex: pdf, doc, ppt) | |
| gl | No | Geolocation (country code: fr, us, uk, etc.) | |
| lr | No | Results language (ex: lang_fr, lang_en) | |
| num | No | Number of results to return (1-10) | |
| orTerms | No | Alternative terms (OR) | |
| query | Yes | Google search query | |
| rights | No | License filters (ex: cc_publicdomain) | |
| safe | No | SafeSearch level | |
| searchType | No | Search type (value: image) | |
| siteSearch | No | Search specific site (ex: reddit.com) | |
| siteSearchFilter | No | Include (i) or exclude (e) the site | |
| sort | No | Sort by date (value: date) | |
| start | No | Starting index of results |
Implementation Reference
- src/tools/search.ts:82-174 (handler)The core handler function 'execute' that implements the google_search tool logic: selects an available API key, constructs search parameters, calls Google Custom Search API, processes results into structured format, manages quotas, handles errors, and returns JSON response with results and metadata.export const execute = async (params: SearchParams) => { const response = { content: [] as TextContent[], isError: false }; try { const apiKey = config.getAvailableKey(); if (!apiKey) { throw new Error('All Google API keys have reached their daily quota. Try again tomorrow.'); } // Log which API key is being used console.error(`[INFO] Using API Key: ${apiKey.id} -> ${apiKey.apiKey.substring(0, 12)}...${apiKey.apiKey.substring(-4)}`); console.error(`[INFO] Search Engine ID: ${apiKey.searchEngineId}`); const customsearch = google.customsearch('v1'); const searchParams = { auth: apiKey.apiKey, cx: apiKey.searchEngineId, q: params.query, num: Math.min(params.num || 5, 10), start: params.start || 1, safe: params.safe || 'off', ...(params.lr && { lr: params.lr }), ...(params.gl && { gl: params.gl, hl: params.gl }), ...(params.dateRestrict && { dateRestrict: params.dateRestrict }), ...(params.fileType && { fileType: params.fileType }), ...(params.siteSearch && { siteSearch: params.siteSearch }), ...(params.siteSearchFilter && { siteSearchFilter: params.siteSearchFilter }), ...(params.cr && { cr: params.cr }), ...(params.exactTerms && { exactTerms: params.exactTerms }), ...(params.excludeTerms && { excludeTerms: params.excludeTerms }), ...(params.orTerms && { orTerms: params.orTerms }), ...(params.rights && { rights: params.rights }), ...(params.sort && { sort: params.sort }), ...(params.searchType && { searchType: params.searchType }), }; const searchResponse = await customsearch.cse.list(searchParams); config.incrementUsage(apiKey.id); const items = searchResponse.data.items || []; const results: SearchResult[] = items.map(item => ({ title: item.title || '', link: item.link || '', snippet: item.snippet || '', displayLink: item.displayLink || '', })); const quotaStatus = config.getQuotaStatus(); response.content.push({ type: 'text' as const, text: JSON.stringify({ results, metadata: { query: params.query, totalResults: searchResponse.data.searchInformation?.totalResults || '0', searchTime: searchResponse.data.searchInformation?.searchTime || '0', resultsCount: results.length, usedApiKey: apiKey.id, quotaStatus } }, null, 2), }); } catch (error: any) { console.error('[ERROR] Google Search Error:', error); if (error.code === 403) { const apiKey = config.getAvailableKey(); if (apiKey) { config.disableKey(apiKey.id, 'Quota exceeded or 403 error'); } } response.content.push({ type: 'text' as const, text: JSON.stringify({ error: 'Google Search error', message: error.message, details: error.code ? `Code: ${error.code}` : 'Unknown error', quotaStatus: config.getQuotaStatus() }, null, 2), }); response.isError = true; } return response; };
- src/tools/search.ts:60-78 (schema)Zod input schema defining parameters for the google_search tool, including query (required), num results, pagination, safety, language, geolocation, and advanced search filters.export const inputSchema = z.object({ query: z.string().describe('Google search query'), num: z.number().int().min(1).max(10).optional().describe('Number of results to return (1-10)'), start: z.number().int().min(1).optional().describe('Starting index of results'), safe: z.enum(['off', 'active']).optional().describe('SafeSearch level'), lr: z.string().optional().describe('Results language (ex: lang_fr, lang_en)'), gl: z.string().optional().describe('Geolocation (country code: fr, us, uk, etc.)'), dateRestrict: z.string().optional().describe('Time filter (ex: d1=24h, w1=week, m1=month, y1=year)'), fileType: z.string().optional().describe('File type (ex: pdf, doc, ppt)'), siteSearch: z.string().optional().describe('Search specific site (ex: reddit.com)'), siteSearchFilter: z.enum(['i', 'e']).optional().describe('Include (i) or exclude (e) the site'), cr: z.string().optional().describe('Country restriction (ex: countryFR, countryUS)'), exactTerms: z.string().optional().describe('Exact phrase required'), excludeTerms: z.string().optional().describe('Words to exclude from search'), orTerms: z.string().optional().describe('Alternative terms (OR)'), rights: z.string().optional().describe('License filters (ex: cc_publicdomain)'), sort: z.string().optional().describe('Sort by date (value: date)'), searchType: z.string().optional().describe('Search type (value: image)'), });
- src/tools/search.ts:33-33 (registration)Tool name definition: 'google_search', used for identification and registration in the MCP server.export const name = 'google_search';
- src/server.ts:20-22 (registration)Registration loop that adds the google_search tool (imported via tools/index.js) to the MCP server using server.tool() with its name, description, schema, annotations, and execute handler.for (const tool of Object.values(tools)) { server.tool(tool.name, tool.description, tool.inputSchema.shape, tool.annotations, tool.execute); }
- src/config.ts:13-70 (helper)GoogleSearchConfig class providing API key management, quota tracking, key selection, and status reporting, used by the handler for key rotation and error handling.export class GoogleSearchConfig { private globalConfig: GlobalConfigManager; constructor() { this.globalConfig = new GlobalConfigManager(); this.checkConfiguration(); } private checkConfiguration(): void { if (!this.globalConfig.hasValidConfig()) { console.error('[WARN] No Google API keys configured.'); console.error('[INFO] First time setup:'); console.error('[INFO] 1. Get API keys: https://console.cloud.google.com/'); console.error('[INFO] 2. Get Search Engine ID: https://programmablesearchengine.google.com/'); console.error('[INFO] 3. Run setup command: npx @kyaniiii/google-search-mcp setup'); console.error('[INFO] Full documentation: https://github.com/Fabien-desablens/google-search-mcp#readme'); return; } const status = this.globalConfig.getQuotaStatus(); console.error(`[INFO] ${status.keysStatus.length} Google API keys loaded globally`); } public getAvailableKey(): GoogleApiKey | null { const key = this.globalConfig.getAvailableKey(); if (!key) return null; return { id: key.id, apiKey: key.apiKey, searchEngineId: key.searchEngineId, dailyUsage: key.dailyUsage, dailyLimit: key.dailyLimit, lastReset: key.lastReset, isActive: key.isActive }; } public incrementUsage(keyId: string): void { this.globalConfig.incrementUsage(keyId); } public getQuotaStatus(): { totalUsed: number; totalLimit: number; keysStatus: any[] } { return this.globalConfig.getQuotaStatus(); } public disableKey(keyId: string, reason: string): void { this.globalConfig.disableKey(keyId, reason); } public hasValidConfig(): boolean { return this.globalConfig.hasValidConfig(); } public getConfigPath(): string { return this.globalConfig.getConfigPath(); } }