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
JavaScript
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