Skip to main content
Glama

Google Ads MCP Server

by seovimalraj
search.ts6.93 kB
import { URLSearchParams } from 'node:url'; export class UpstreamError extends Error { constructor( public readonly service: string, public readonly status: number, message?: string, public readonly details?: unknown, ) { super(message ?? `${service} request failed with status ${status}`); this.name = 'UpstreamError'; } } function stripJsonPrefix(payload: string): string { return payload.replace(/^\)\]\}'?,?/, '').trim(); } export async function fetchAutocompleteSuggestions( query: string, ): Promise<{ query: string; suggestions: string[] }> { const searchParams = new URLSearchParams({ client: 'firefox', q: query }); const response = await fetch( `https://suggestqueries.google.com/complete/search?${searchParams.toString()}`, { headers: { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0 Safari/537.36', 'accept-language': 'en-US,en;q=0.9', }, }, ); if (!response.ok) { throw new UpstreamError( 'google-autocomplete', response.status, 'Failed to fetch autocomplete suggestions.', ); } let data: unknown; try { data = await response.json(); } catch { throw new Error('Autocomplete response was not valid JSON.'); } if (!Array.isArray(data) || data.length < 2 || !Array.isArray(data[1])) { throw new Error('Unexpected autocomplete payload format.'); } const suggestions = data[1].filter((value): value is string => typeof value === 'string'); return { query, suggestions }; } interface TrendsExploreWidget { id: string; token: string; request: Record<string, unknown>; } interface TimelineEntry { time: string; formattedTime?: string; formattedValue?: string[]; value?: number[]; } export interface TrendIndexOptions { keyword: string; geo?: string; timeRange?: string; category?: number; property?: string; } export interface TrendIndexPoint { time: string; formattedTime: string; values: number[]; } export interface TrendIndexResult { keyword: string; geo: string; timeRange: string; seriesLabels: string[]; averages: number[]; points: TrendIndexPoint[]; } export async function fetchTrendIndex(options: TrendIndexOptions): Promise<TrendIndexResult> { const comparisonItem = [ { keyword: options.keyword, geo: options.geo ?? '', time: options.timeRange ?? 'today 12-m', }, ]; const exploreParams = new URLSearchParams({ hl: 'en-US', tz: '0', req: JSON.stringify({ comparisonItem, category: options.category ?? 0, property: options.property ?? '', }), }); const exploreResponse = await fetch( `https://trends.google.com/trends/api/explore?${exploreParams.toString()}`, { headers: { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0 Safari/537.36', 'accept-language': 'en-US,en;q=0.9', }, }, ); if (!exploreResponse.ok) { throw new UpstreamError( 'google-trends-explore', exploreResponse.status, 'Failed to initialise trends request.', ); } const explorePayload = stripJsonPrefix(await exploreResponse.text()); let exploreData: any; try { exploreData = JSON.parse(explorePayload); } catch { throw new Error('Unable to parse explore response from Google Trends.'); } const widget: TrendsExploreWidget | undefined = (exploreData?.widgets ?? []).find( (candidate: TrendsExploreWidget) => candidate?.id === 'TIMESERIES' && candidate?.token, ); if (!widget) { throw new Error('Could not locate a timeseries widget in the explore response.'); } const multilineParams = new URLSearchParams({ hl: 'en-US', tz: '0', req: JSON.stringify(widget.request ?? {}), token: widget.token, }); const timelineResponse = await fetch( `https://trends.google.com/trends/api/widgetdata/multiline?${multilineParams.toString()}`, { headers: { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0 Safari/537.36', 'accept-language': 'en-US,en;q=0.9', }, }, ); if (!timelineResponse.ok) { throw new UpstreamError( 'google-trends-data', timelineResponse.status, 'Failed to fetch trends timeline data.', ); } const timelinePayload = stripJsonPrefix(await timelineResponse.text()); let timelineData: any; try { timelineData = JSON.parse(timelinePayload); } catch { throw new Error('Unable to parse timeline response from Google Trends.'); } const defaultTimeline = timelineData?.default ?? timelineData; const entries: TimelineEntry[] = Array.isArray(defaultTimeline?.timelineData) ? defaultTimeline.timelineData : []; const points: TrendIndexPoint[] = []; for (const entry of entries) { const time = typeof entry.time === 'string' ? entry.time : ''; if (!time) { continue; } const formattedTime = typeof entry.formattedTime === 'string' ? entry.formattedTime : new Date(Number(time) * 1000).toISOString(); const values = Array.isArray(entry.value) ? entry.value.filter((value): value is number => typeof value === 'number') : []; if (values.length === 0) { continue; } points.push({ time, formattedTime, values }); } const seriesLabels: string[] = Array.isArray(defaultTimeline?.legend) ? defaultTimeline.legend.filter((label: unknown): label is string => typeof label === 'string') : Array.isArray(defaultTimeline?.seriesLabels) ? defaultTimeline.seriesLabels.filter( (label: unknown): label is string => typeof label === 'string', ) : []; const averages: number[] = Array.isArray(defaultTimeline?.averages) ? defaultTimeline.averages.filter( (value: unknown): value is number => typeof value === 'number', ) : []; let requestGeo: string | undefined; let requestTime: string | undefined; const comparisonItems = (widget.request as { comparisonItem?: unknown })?.comparisonItem; if (Array.isArray(comparisonItems) && comparisonItems.length > 0) { const first = comparisonItems[0]; if (first && typeof first === 'object') { const item = first as Record<string, unknown>; if (typeof item.geo === 'string') { requestGeo = item.geo; } if (typeof item.time === 'string') { requestTime = item.time; } } } const resolvedGeo = requestGeo ?? options.geo ?? ''; const resolvedTime = requestTime ?? options.timeRange ?? 'today 12-m'; return { keyword: options.keyword, geo: resolvedGeo, timeRange: resolvedTime, seriesLabels, averages, points, }; } export const __testUtils = { stripJsonPrefix, };

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/seovimalraj/google-ads-mcp'

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