Skip to main content
Glama
martechery

Google Ads MCP Server

by martechery

get_performance

Retrieve Google Ads performance metrics for accounts, campaigns, ad groups, or ads with customizable filters, date ranges, and output formats.

Instructions

Get performance (level: account|campaign|ad_group|ad). Optional: login_customer_id (aka MCC/manager account id) overrides env.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
customer_idNo10-digit customer ID (no dashes). Optional.
login_customer_idNoManager account (MCC) ID to use as login-customer for this request (10 digits, no dashes). Overrides env GOOGLE_ADS_MANAGER_ACCOUNT_ID.
levelYesAggregation level
daysNoDays back to query (1-365, default 30)
limitNoGAQL LIMIT (1-1000, default 50)
page_sizeNooptional page size (1-10000)
page_tokenNooptional page token
auto_paginateNofetch multiple pages automatically
max_pagesNolimit when auto_paginate=true (1-20)
output_formatNorender formattable
filtersNooptional performance filters

Implementation Reference

  • Main handler for get_performance tool: manages sessions, builds and executes GAQL query for performance data at specified level (account/campaign/etc), handles pagination and formatting.
    async (_input: any) => { const input = (_input || {}) as any; const startTs = Date.now(); let sessionKey: string | undefined; try { sessionKey = requireSessionKeyIfEnabled(input); } catch (e: any) { const msg = e?.message || String(e); logEvent('get_performance', startTs, { sessionKey, customerId: input?.customer_id, requestId: input?.request_id, error: { code: 'ERR_INPUT', message: String(msg) } }); return { content: [{ type: 'text', text: `Error: ${msg}` }] }; } if (sessionKey) { const rc = checkRateLimit(sessionKey); if (!rc.allowed) { logEvent('get_performance', startTs, { sessionKey, customerId: input?.customer_id, requestId: input?.request_id, error: { code: 'ERR_RATE_LIMITED', message: `Retry after ${rc.retryAfter}s` } }); return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_RATE_LIMITED', message: `Rate limit exceeded. Retry after ${rc.retryAfter} seconds`, retry_after: rc.retryAfter } }) }] }; } } if (!input.customer_id) { const envAccount = process.env.GOOGLE_ADS_ACCOUNT_ID; if (envAccount) { input.customer_id = envAccount; } else { const res = await listAccessibleCustomers(sessionKey); if (!res.ok) { const hint = mapAdsErrorMsg(res.status, res.errorText || ''); const lines = [ 'No customer_id provided. Please choose an account and re-run with customer_id.', `Error listing accounts (status ${res.status}): ${res.errorText || ''}`, ]; if (hint) lines.push(`Hint: ${hint}`); logEvent('get_performance', startTs, { sessionKey, requestId: input?.request_id, error: { code: `HTTP_${res.status}`, message: String(res.errorText || '') } }); return { content: [{ type: 'text', text: lines.join('\n') }] }; } const names = res.data?.resourceNames || []; if (!names.length) return { content: [{ type: 'text', text: 'No accessible accounts found.' }] }; const rows = names.map((rn: string) => ({ account_id: (rn.split('/').pop() || rn) })); const table = tabulate(rows, ['account_id']); const lines = [ 'No customer_id provided. Select one of the accounts below, then call again with customer_id.', table, ]; logEvent('get_performance', startTs, { sessionKey, requestId: input?.request_id }); return { content: [{ type: 'text', text: lines.join('\n') }] }; } } // Enforce allowlist if present if (sessionKey && input.customer_id && !isCustomerAllowedForSession(sessionKey, input.customer_id)) { return { content: [{ type: 'text', text: `Error: Customer ID ${input.customer_id} not in allowlist for this session` }] }; } const days = Math.max(1, Math.min(365, Number(input.days ?? 30))); const limit = Math.max(1, Math.min(1000, Number(input.limit ?? 50))); const query = buildPerformanceQuery(input.level, days, limit, input.filters || {}); const auto = !!input.auto_paginate; const maxPages = Math.max(1, Math.min(20, Number(input.max_pages ?? 5))); const pageSize = (typeof input.page_size === 'number') ? Math.max(1, Math.min(10_000, Number(input.page_size))) : undefined; let pageToken = input.page_token as string | undefined; let all: any[] = []; let lastToken: string | undefined; let pageCount = 0; // Normalize MCC/login-customer aliases for robustness const loginCustomerId = (input as any).login_customer_id ?? (input as any).loginCustomerId ?? (input as any).managerAccountId ?? (input as any).mcc; do { const res = await executeGaql({ customerId: input.customer_id, query, pageSize, pageToken, loginCustomerId, sessionKey }); if (!res.ok) { const hint = mapAdsErrorMsg(res.status, res.errorText || ''); const lines = [`Error executing performance query (status ${res.status}): ${res.errorText || ''}`]; if (hint) lines.push(`Hint: ${hint}`); logEvent('get_performance', startTs, { sessionKey, customerId: input.customer_id, requestId: input?.request_id, error: { code: `HTTP_${res.status}`, message: String(res.errorText || '') } }); return { content: [{ type: "text", text: lines.join('\n') }] }; } const data = res.data; const results = (data?.results && Array.isArray(data.results)) ? data.results : []; all = all.concat(results); lastToken = data?.nextPageToken; pageToken = auto ? lastToken : undefined; pageCount++; } while (auto && pageToken && pageCount < maxPages); if (!all.length) { return { content: [{ type: "text", text: "No results found for the selected period." }] }; } const rows = (all as any[]).map((r: any) => { const out = { ...r }; const metrics = { ...(r?.metrics || {}) } as any; const micros = (metrics.cost_micros ?? metrics.costMicros); if (typeof micros === 'number') metrics.cost_units = microsToUnits(micros); (out as any).metrics = metrics; return out; }); const first = rows[0]; const fields: string[] = []; for (const key of Object.keys(first)) { const val = (first as any)[key]; if (val && typeof val === "object" && !Array.isArray(val)) { for (const sub of Object.keys(val)) fields.push(`${key}.${sub}`); } else { fields.push(key); } } const fmt = (input.output_format || 'table').toLowerCase(); if (fmt === 'json') return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] }; if (fmt === 'csv') { const { toCsv } = await import('./utils/formatCsv.js'); const csv = toCsv(rows, fields); return { content: [{ type: 'text', text: csv }] }; } const table = tabulate(rows, fields); const lines: string[] = [ `Performance (${input.level}) for last ${input.days ?? 30} days:`, table, ]; if (!auto && lastToken) lines.push(`Next Page Token: ${lastToken}`); if (auto) lines.push(`Pages fetched: ${pageCount}`); const out = { content: [{ type: "text", text: lines.join("\n") }] }; logEvent('get_performance', startTs, { sessionKey, customerId: input.customer_id, requestId: input?.request_id }); return out; }
  • Registration of the get_performance tool using addTool, linking name, description, schema, and handler.
    addTool( server, "get_performance", "Get performance (level: account|campaign|ad_group|ad). Optional: login_customer_id (aka MCC/manager account id) overrides env.", GetPerformanceZ, async (_input: any) => { const input = (_input || {}) as any; const startTs = Date.now(); let sessionKey: string | undefined; try { sessionKey = requireSessionKeyIfEnabled(input); } catch (e: any) { const msg = e?.message || String(e); logEvent('get_performance', startTs, { sessionKey, customerId: input?.customer_id, requestId: input?.request_id, error: { code: 'ERR_INPUT', message: String(msg) } }); return { content: [{ type: 'text', text: `Error: ${msg}` }] }; } if (sessionKey) { const rc = checkRateLimit(sessionKey); if (!rc.allowed) { logEvent('get_performance', startTs, { sessionKey, customerId: input?.customer_id, requestId: input?.request_id, error: { code: 'ERR_RATE_LIMITED', message: `Retry after ${rc.retryAfter}s` } }); return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_RATE_LIMITED', message: `Rate limit exceeded. Retry after ${rc.retryAfter} seconds`, retry_after: rc.retryAfter } }) }] }; } } if (!input.customer_id) { const envAccount = process.env.GOOGLE_ADS_ACCOUNT_ID; if (envAccount) { input.customer_id = envAccount; } else { const res = await listAccessibleCustomers(sessionKey); if (!res.ok) { const hint = mapAdsErrorMsg(res.status, res.errorText || ''); const lines = [ 'No customer_id provided. Please choose an account and re-run with customer_id.', `Error listing accounts (status ${res.status}): ${res.errorText || ''}`, ]; if (hint) lines.push(`Hint: ${hint}`); logEvent('get_performance', startTs, { sessionKey, requestId: input?.request_id, error: { code: `HTTP_${res.status}`, message: String(res.errorText || '') } }); return { content: [{ type: 'text', text: lines.join('\n') }] }; } const names = res.data?.resourceNames || []; if (!names.length) return { content: [{ type: 'text', text: 'No accessible accounts found.' }] }; const rows = names.map((rn: string) => ({ account_id: (rn.split('/').pop() || rn) })); const table = tabulate(rows, ['account_id']); const lines = [ 'No customer_id provided. Select one of the accounts below, then call again with customer_id.', table, ]; logEvent('get_performance', startTs, { sessionKey, requestId: input?.request_id }); return { content: [{ type: 'text', text: lines.join('\n') }] }; } } // Enforce allowlist if present if (sessionKey && input.customer_id && !isCustomerAllowedForSession(sessionKey, input.customer_id)) { return { content: [{ type: 'text', text: `Error: Customer ID ${input.customer_id} not in allowlist for this session` }] }; } const days = Math.max(1, Math.min(365, Number(input.days ?? 30))); const limit = Math.max(1, Math.min(1000, Number(input.limit ?? 50))); const query = buildPerformanceQuery(input.level, days, limit, input.filters || {}); const auto = !!input.auto_paginate; const maxPages = Math.max(1, Math.min(20, Number(input.max_pages ?? 5))); const pageSize = (typeof input.page_size === 'number') ? Math.max(1, Math.min(10_000, Number(input.page_size))) : undefined; let pageToken = input.page_token as string | undefined; let all: any[] = []; let lastToken: string | undefined; let pageCount = 0; // Normalize MCC/login-customer aliases for robustness const loginCustomerId = (input as any).login_customer_id ?? (input as any).loginCustomerId ?? (input as any).managerAccountId ?? (input as any).mcc; do { const res = await executeGaql({ customerId: input.customer_id, query, pageSize, pageToken, loginCustomerId, sessionKey }); if (!res.ok) { const hint = mapAdsErrorMsg(res.status, res.errorText || ''); const lines = [`Error executing performance query (status ${res.status}): ${res.errorText || ''}`]; if (hint) lines.push(`Hint: ${hint}`); logEvent('get_performance', startTs, { sessionKey, customerId: input.customer_id, requestId: input?.request_id, error: { code: `HTTP_${res.status}`, message: String(res.errorText || '') } }); return { content: [{ type: "text", text: lines.join('\n') }] }; } const data = res.data; const results = (data?.results && Array.isArray(data.results)) ? data.results : []; all = all.concat(results); lastToken = data?.nextPageToken; pageToken = auto ? lastToken : undefined; pageCount++; } while (auto && pageToken && pageCount < maxPages); if (!all.length) { return { content: [{ type: "text", text: "No results found for the selected period." }] }; } const rows = (all as any[]).map((r: any) => { const out = { ...r }; const metrics = { ...(r?.metrics || {}) } as any; const micros = (metrics.cost_micros ?? metrics.costMicros); if (typeof micros === 'number') metrics.cost_units = microsToUnits(micros); (out as any).metrics = metrics; return out; }); const first = rows[0]; const fields: string[] = []; for (const key of Object.keys(first)) { const val = (first as any)[key]; if (val && typeof val === "object" && !Array.isArray(val)) { for (const sub of Object.keys(val)) fields.push(`${key}.${sub}`); } else { fields.push(key); } } const fmt = (input.output_format || 'table').toLowerCase(); if (fmt === 'json') return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] }; if (fmt === 'csv') { const { toCsv } = await import('./utils/formatCsv.js'); const csv = toCsv(rows, fields); return { content: [{ type: 'text', text: csv }] }; } const table = tabulate(rows, fields); const lines: string[] = [ `Performance (${input.level}) for last ${input.days ?? 30} days:`, table, ]; if (!auto && lastToken) lines.push(`Next Page Token: ${lastToken}`); if (auto) lines.push(`Pages fetched: ${pageCount}`); const out = { content: [{ type: "text", text: lines.join("\n") }] }; logEvent('get_performance', startTs, { sessionKey, customerId: input.customer_id, requestId: input?.request_id }); return out; } );
  • Zod schema definition for get_performance input validation (GetPerformanceZ) and JSON schema export.
    export const GetPerformanceZ = z.object({ customer_id: z.string().optional().describe('10-digit customer ID (no dashes). Optional.'), // Per-call login customer (MCC/manager) override login_customer_id: z.union([z.string(), z.number()]).optional().describe('Manager account (MCC) ID to use as login-customer for this request (10 digits, no dashes). Overrides env GOOGLE_ADS_MANAGER_ACCOUNT_ID.'), level: z.enum(['account','campaign','ad_group','ad']).describe('Aggregation level'), days: z.number().default(30).describe('Days back to query (1-365, default 30)'), limit: z.number().default(50).describe('GAQL LIMIT (1-1000, default 50)'), page_size: z.number().min(1).optional().describe('optional page size (1-10000)'), page_token: z.string().optional().describe('optional page token'), auto_paginate: z.boolean().default(false).describe('fetch multiple pages automatically'), max_pages: z.number().min(1).max(20).default(5).describe('limit when auto_paginate=true (1-20)'), output_format: z.enum(['table','json','csv']).default('table').describe('render format'), filters: z.object({ status: z.string().optional().describe('e.g., ENABLED, PAUSED'), name_contains: z.string().optional().describe('substring in entity name (case sensitive)'), campaign_name_contains: z.string().optional().describe('substring in campaign name (case sensitive)'), min_clicks: z.number().optional().describe('minimum clicks (>=0)'), min_impressions: z.number().optional().describe('minimum impressions (>=0)'), }).optional().describe('optional performance filters'), }); export const GetPerformanceSchema: JsonSchema = zodToJsonSchema(GetPerformanceZ, 'GetPerformance') as unknown as JsonSchema;
  • Helper function buildPerformanceQuery generates the GAQL query string tailored to the performance level, date range, limits, and filters.
    export function buildPerformanceQuery( level: PerformanceLevel, days = 30, limit = 50, filters: PerformanceFilters = {} ): string { // Runtime bounds const safeDays = Math.max(1, Math.min(365, Math.floor(days))); const safeLimit = Math.max(1, Math.min(1000, Math.floor(limit))); const baseMetrics = ` metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.average_cpc, customer.currency_code `; let fields = ''; let from = ''; let statusField = ''; let nameField = ''; const campaignNameField = 'campaign.name'; switch (level) { case 'account': fields = ` customer.id, customer.descriptive_name, customer.currency_code, ${baseMetrics} `; from = 'customer'; statusField = 'customer.status'; nameField = 'customer.descriptive_name'; break; case 'campaign': fields = ` campaign.id, campaign.name, campaign.status, ${baseMetrics} `; from = 'campaign'; statusField = 'campaign.status'; nameField = 'campaign.name'; break; case 'ad_group': fields = ` campaign.name, ad_group.id, ad_group.name, ad_group.status, ${baseMetrics} `; from = 'ad_group'; statusField = 'ad_group.status'; nameField = 'ad_group.name'; break; case 'ad': fields = ` campaign.name, ad_group.name, ad_group_ad.ad.id, ad_group_ad.status, ${baseMetrics} `; from = 'ad_group_ad'; statusField = 'ad_group_ad.status'; nameField = 'ad_group.name'; break; default: throw new Error('Invalid level. Use campaign | ad_group | ad'); } const whereClauses: string[] = [` SELECT ${fields} FROM ${from} WHERE segments.date DURING LAST_${safeDays}_DAYS`]; // Apply filters const esc = (v: string) => v.replace(/'/g, "''"); if (filters.status) whereClauses.push(`AND ${statusField} = '${esc(filters.status)}'`); if (filters.name_contains) whereClauses.push(`AND ${nameField} LIKE '%${esc(filters.name_contains)}%'`); if (filters.campaign_name_contains && level !== 'account') whereClauses.push(`AND ${campaignNameField} LIKE '%${esc(filters.campaign_name_contains)}%'`); if (typeof filters.min_clicks === 'number') whereClauses.push(`AND metrics.clicks >= ${Math.max(0, Math.floor(filters.min_clicks))}`); if (typeof filters.min_impressions === 'number') whereClauses.push(`AND metrics.impressions >= ${Math.max(0, Math.floor(filters.min_impressions))}`); const query = ` ${whereClauses.join('\n ')} ORDER BY metrics.cost_micros DESC LIMIT ${safeLimit} `; return query; }

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

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