Skip to main content
Glama
ahonn

Google Search Console MCP Server

by ahonn
search-console.ts7.14 kB
import { google, searchconsole_v1, webmasters_v3 } from 'googleapis'; import { GoogleAuth } from 'google-auth-library'; type SearchanalyticsQueryRequest = webmasters_v3.Params$Resource$Searchanalytics$Query['requestBody']; type ListSitemapsRequest = webmasters_v3.Params$Resource$Sitemaps$List; type GetSitemapRequest = webmasters_v3.Params$Resource$Sitemaps$Get; type SubmitSitemapRequest = webmasters_v3.Params$Resource$Sitemaps$Submit; type IndexInspectRequest = searchconsole_v1.Params$Resource$Urlinspection$Index$Inspect['requestBody']; export class SearchConsoleService { private auth: GoogleAuth; constructor(credentials: string) { this.auth = new google.auth.GoogleAuth({ keyFile: credentials, scopes: ['https://www.googleapis.com/auth/webmasters.readonly'], }); } private async getWebmasters() { const authClient = await this.auth.getClient(); return google.webmasters({ version: 'v3', auth: authClient, } as webmasters_v3.Options); } private async getSearchConsole() { const authClient = await this.auth.getClient(); return google.searchconsole({ version: 'v1', auth: authClient, } as searchconsole_v1.Options); } private normalizeUrl(url: string): string { const parsedUrl = new URL(url); if (parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:') { return `sc-domain:${parsedUrl.hostname}`; } return `https://${url}`; } private async handlePermissionError<T>( operation: () => Promise<T>, fallbackOperation: () => Promise<T>, ): Promise<T> { try { return await operation(); } catch (err) { if (err instanceof Error && err.message.toLowerCase().includes('permission')) { return await fallbackOperation(); } throw err; } } async searchAnalytics(siteUrl: string, requestBody: SearchanalyticsQueryRequest) { const webmasters = await this.getWebmasters(); return this.handlePermissionError( () => webmasters.searchanalytics.query({ siteUrl, requestBody }), () => webmasters.searchanalytics.query({ siteUrl: this.normalizeUrl(siteUrl), requestBody }), ); } /** * Enhanced Search Analytics with advanced features * - Supports up to 25,000 rows (vs 1,000 default) * - Regex filtering capabilities * - Quick wins detection */ async enhancedSearchAnalytics( siteUrl: string, requestBody: SearchanalyticsQueryRequest, options: { regexFilter?: string; enableQuickWins?: boolean; quickWinsThresholds?: { minImpressions?: number; maxCtr?: number; positionRangeMin?: number; positionRangeMax?: number; }; } = {} ) { // Ensure requestBody is defined if (!requestBody) { throw new Error('Request body is required'); } // Apply regex filter if provided if (options.regexFilter && requestBody.dimensions?.includes('query')) { requestBody.dimensionFilterGroups = [ ...(requestBody.dimensionFilterGroups || []), { groupType: 'and', filters: [{ dimension: 'query', operator: 'includingRegex', expression: options.regexFilter }] } ]; } // Execute enhanced search analytics const result = await this.searchAnalytics(siteUrl, requestBody); // Apply quick wins detection if enabled if (options.enableQuickWins && result.data.rows) { const quickWins = this.detectQuickWins(result.data.rows, options.quickWinsThresholds); return { ...result, data: { ...result.data, quickWins: quickWins, enhancedFeatures: { regexFilterApplied: !!options.regexFilter, quickWinsEnabled: true, rowLimit: requestBody.rowLimit || 1000 } } }; } return result; } /** * Automatic Quick Wins Detection * Identifies SEO optimization opportunities */ private detectQuickWins( rows: any[], thresholds: { minImpressions?: number; maxCtr?: number; positionRangeMin?: number; positionRangeMax?: number; } = {} ) { const { minImpressions = 50, maxCtr = 2.0, positionRangeMin = 4, positionRangeMax = 10 } = thresholds; return rows .filter(row => { const impressions = row.impressions || 0; const ctr = (row.ctr || 0) * 100; const position = row.position || 0; return impressions >= minImpressions && ctr <= maxCtr && position >= positionRangeMin && position <= positionRangeMax; }) .map(row => { const impressions = row.impressions || 0; const currentClicks = row.clicks || 0; const currentCtr = (row.ctr || 0) * 100; const position = row.position || 0; // Calculate potential with 5% target CTR const targetCtr = 5.0; const potentialClicks = Math.round((impressions * targetCtr) / 100); const additionalClicks = Math.max(0, potentialClicks - currentClicks); return { query: row.keys?.[0] || 'N/A', page: row.keys?.[1] || 'N/A', currentPosition: Number(position.toFixed(1)), impressions: impressions, currentClicks: currentClicks, currentCtr: Number(currentCtr.toFixed(2)), potentialClicks: potentialClicks, additionalClicks: additionalClicks, opportunity: additionalClicks > 0 ? 'High' : 'Low', optimizationNote: `Move from position ${position.toFixed(1)} to improve CTR` }; }) .sort((a, b) => b.additionalClicks - a.additionalClicks); } async listSites() { const webmasters = await this.getWebmasters(); return webmasters.sites.list(); } async listSitemaps(requestBody: ListSitemapsRequest) { const webmasters = await this.getWebmasters(); return this.handlePermissionError( () => webmasters.sitemaps.list(requestBody), () => webmasters.sitemaps.list({ ...requestBody, siteUrl: this.normalizeUrl(requestBody.siteUrl!), }), ); } async getSitemap(requestBody: GetSitemapRequest) { const webmasters = await this.getWebmasters(); return this.handlePermissionError( () => webmasters.sitemaps.get(requestBody), () => webmasters.sitemaps.get({ ...requestBody, siteUrl: this.normalizeUrl(requestBody.siteUrl!), }), ); } async submitSitemap(requestBody: SubmitSitemapRequest) { const webmasters = await this.getWebmasters(); return this.handlePermissionError( () => webmasters.sitemaps.submit(requestBody), () => webmasters.sitemaps.submit({ ...requestBody, siteUrl: this.normalizeUrl(requestBody.siteUrl!), }), ); } async indexInspect(requestBody: IndexInspectRequest) { const searchConsole = await this.getSearchConsole(); return searchConsole.urlInspection.index.inspect({ requestBody }); } }

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

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