UNPKG

google-context-mcp

Version:

Secure Google Search MCP server providing real-time coding context to AI models and LLMs - find latest docs, solutions, and best practices while coding

243 lines (242 loc) 11 kB
import { google } from 'googleapis'; export class GoogleSearchMCP { apiKey; customSearchEngineId; customsearch; constructor(apiKey, customSearchEngineId) { this.apiKey = apiKey; this.customSearchEngineId = customSearchEngineId; this.customsearch = google.customsearch('v1'); } async initialize(server) { try { if (server.setRequestHandler) { server.setRequestHandler('tools/call', async (request) => { const { name, arguments: args } = request.params; switch (name) { case 'google_search': return await this.handleGoogleSearch(args); case 'google_search_advanced': return await this.handleAdvancedGoogleSearch(args); case 'google_search_suggestions': return await this.handleSearchSuggestions(args); default: throw new Error(`Unknown tool: ${name}`); } }); server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'google_search', description: 'Perform a basic Google search and return relevant results', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The search query to execute' }, numResults: { type: 'number', description: 'Number of results to return (1-10)', default: 5 } }, required: ['query'] } }, { name: 'google_search_advanced', description: 'Perform an advanced Google search with additional options', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The search query to execute' }, numResults: { type: 'number', description: 'Number of results to return (1-10)', default: 5 }, startIndex: { type: 'number', description: 'Starting index for results (1-based)', default: 1 }, language: { type: 'string', description: 'Language code (e.g., "en", "es", "fr")' }, country: { type: 'string', description: 'Country code (e.g., "US", "GB", "IN")' }, safeSearch: { type: 'string', enum: ['off', 'medium', 'high'], description: 'Safe search level', default: 'medium' }, dateRestrict: { type: 'string', description: 'Date restriction (e.g., "d1", "w1", "m1", "y1")' }, siteSearch: { type: 'string', description: 'Restrict search to specific site' } }, required: ['query'] } }, { name: 'google_search_suggestions', description: 'Get search suggestions for a query', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The query to get suggestions for' } }, required: ['query'] } } ] }; }); console.error('✅ MCP tools registered successfully'); } else { console.error('⚠️ Server does not support setRequestHandler, tools may not work'); } } catch (error) { console.error('❌ Error registering MCP tools:', error); throw error; } } async handleGoogleSearch(args) { const { query, numResults = 5 } = args; if (!query || typeof query !== 'string') { throw new Error('Query parameter is required and must be a string'); } const results = await this.performSearch({ query, numResults: Math.min(Math.max(numResults, 1), 10) }); return { content: [ { type: 'text', text: `Search results for "${query}":\n\n${this.formatSearchResults(results)}` } ] }; } async handleAdvancedGoogleSearch(args) { const { query, numResults = 5, startIndex = 1, language, country, safeSearch = 'medium', dateRestrict, siteSearch } = args; if (!query || typeof query !== 'string') { throw new Error('Query parameter is required and must be a string'); } const results = await this.performSearch({ query, numResults: Math.min(Math.max(numResults, 1), 10), startIndex: Math.max(startIndex, 1), language, country, safeSearch, dateRestrict, siteSearch }); return { content: [ { type: 'text', text: `Advanced search results for "${query}":\n\n${this.formatSearchResults(results)}` } ] }; } async handleSearchSuggestions(args) { const { query } = args; if (!query || typeof query !== 'string') { throw new Error('Query parameter is required and must be a string'); } return { content: [ { type: 'text', text: `Search suggestions for "${query}" are not available in this version. Please use google_search or google_search_advanced tools instead.` } ] }; } async performSearch(options) { try { if (!this.customSearchEngineId) { throw new Error('Custom Search Engine ID is required. Please set GOOGLE_CUSTOM_SEARCH_ENGINE_ID environment variable or create a custom search engine at https://programmablesearchengine.google.com/'); } const params = { key: this.apiKey, cx: this.customSearchEngineId, q: options.query, num: Math.min(Math.max(options.numResults || 5, 1), 10), start: options.startIndex || 1 }; if (options.language) params.lr = `lang_${options.language}`; if (options.country) params.cr = `country${options.country}`; if (options.safeSearch) params.safe = options.safeSearch; if (options.dateRestrict) params.dateRestrict = options.dateRestrict; if (options.siteSearch) params.siteSearch = options.siteSearch; const response = await this.customsearch.cse.list(params); if (!response.data.items) { return []; } return response.data.items.map((item) => ({ title: item.title || 'No title', link: item.link || '#', snippet: item.snippet || 'No description available', displayLink: item.displayLink || '', formattedUrl: item.formattedUrl || '' })); } catch (error) { console.error('Google search error:', error); if (error.code === 400 && error.message?.includes('invalid argument')) { throw new Error('Search failed: Invalid search parameters. Please check your custom search engine configuration.'); } else if (error.code === 403) { throw new Error('Search failed: API key is invalid or quota exceeded. Please check your Google API key and usage limits.'); } else if (error.code === 429) { throw new Error('Search failed: Rate limit exceeded. Please try again later.'); } else { throw new Error(`Search failed: ${error.message || 'Unknown error occurred'}`); } } } formatSearchResults(results) { if (results.length === 0) { return 'No results found.'; } return results.map((result, index) => { return `${index + 1}. **${result.title}** URL: ${result.link} ${result.snippet} ${result.displayLink ? `Source: ${result.displayLink}` : ''} `; }).join(''); } } //# sourceMappingURL=google-search-mcp.js.map