Skip to main content
Glama
ZLeventer

google-ads-mcp

gads_keyword_performance

Retrieve keyword-level performance data including match type, quality score, clicks, cost, and conversions. Filter by campaign or ad group with default last 28 days and enabled keywords.

Instructions

Keyword-level performance with match type, quality score, clicks, cost, conversions. Filter by campaign_id or ad_group_id. Default last 28 days, enabled keywords.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
start_dateNo28daysAgo
end_dateNoyesterday
customer_idNo
limitNo
campaign_idNoFilter to a specific campaign ID
ad_group_idNoFilter to a specific ad group ID
statusNoENABLED

Implementation Reference

  • src/index.ts:103-108 (registration)
    Registration of the 'gads_keyword_performance' tool with the MCP server. Maps the tool name to keywordPerformanceSchema and keywordPerformance handler.
    server.tool(
      "gads_keyword_performance",
      "Keyword-level performance with match type, quality score, clicks, cost, conversions. Filter by campaign_id or ad_group_id. Default last 28 days, enabled keywords.",
      keywordPerformanceSchema,
      async (args) => { try { return ok(await keywordPerformance(args)); } catch (e) { return err(e); } }
    );
  • Zod schema for keyword performance inputs: date range, campaign_id, ad_group_id, and status filter (default ENABLED).
    export const keywordPerformanceSchema = {
      ...dateRange,
      campaign_id: z.string().optional().describe("Filter to a specific campaign ID"),
      ad_group_id: z.string().optional().describe("Filter to a specific ad group ID"),
      status: z.enum(["ENABLED", "PAUSED", "REMOVED", "ALL"]).default("ENABLED"),
    };
  • Main handler function that executes the GAQL query against the Google Ads API. Builds dynamic filters, queries keyword_view for keyword-level metrics (quality score, match type, clicks, cost, conversions), and returns rows.
    export async function keywordPerformance(args: z.infer<z.ZodObject<typeof keywordPerformanceSchema>>) {
      const customer = getCustomer(args.customer_id);
      const start = resolveDate(args.start_date);
      const end = resolveDate(args.end_date);
      const filters = [
        args.campaign_id ? `AND campaign.id = ${args.campaign_id}` : "",
        args.ad_group_id ? `AND ad_group.id = ${args.ad_group_id}` : "",
        args.status === "ALL" ? "" : `AND ad_group_criterion.status = '${args.status}'`,
      ].filter(Boolean).join(" ");
      const rows = await customer.query(`
        SELECT
          campaign.name,
          ad_group.name,
          ad_group_criterion.criterion_id,
          ad_group_criterion.keyword.text,
          ad_group_criterion.keyword.match_type,
          ad_group_criterion.status,
          ad_group_criterion.quality_info.quality_score,
          metrics.impressions,
          metrics.clicks,
          metrics.ctr,
          metrics.average_cpc,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM keyword_view
        WHERE segments.date BETWEEN '${start}' AND '${end}'
          ${filters}
        ORDER BY metrics.cost_micros DESC
        LIMIT ${args.limit}
      `);
      return { rowCount: rows.length, rows };
    }
  • getCustomer helper used by keywordPerformance to obtain an authenticated Google Ads API Customer object.
    export function getCustomer(override?: string): Customer {
      const refresh_token = process.env.GOOGLE_ADS_REFRESH_TOKEN;
      if (!refresh_token) throw new GoogleAdsError("GOOGLE_ADS_REFRESH_TOKEN is not set");
      const customer_id = (override ?? process.env.GOOGLE_ADS_CUSTOMER_ID ?? "").replace(/-/g, "");
      if (!customer_id) throw new GoogleAdsError("GOOGLE_ADS_CUSTOMER_ID is not set and no customer_id was passed");
      const login_customer_id = process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID?.replace(/-/g, "") || undefined;
      return getApi().Customer({ customer_id, login_customer_id, refresh_token });
    }
  • resolveDate helper that converts date strings (e.g., '28daysAgo', 'yesterday') into YYYY-MM-DD format for the GAQL query.
    export function resolveDate(d: string): string {
      if (/^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
      if (d === "today") return toISO(new Date());
      if (d === "yesterday") return toISO(offsetDays(new Date(), -1));
      const m = d.match(/^(\d+)daysAgo$/);
      if (m) return toISO(offsetDays(new Date(), -parseInt(m[1], 10)));
      throw new GoogleAdsError(`Unrecognized date: ${d}. Use YYYY-MM-DD, today, yesterday, or NdaysAgo.`);
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description provides defaults and filters but lacks disclosure of read-only nature, rate limits, or behavior when no filters are applied. Adequate but not comprehensive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with key information, no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Without an output schema, the description lists metrics but does not explain return structure or pagination. Adequate for basic usage but leaves gaps for a tool with 7 parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds meaning to defaults (last 28 days, enabled keywords) and mentions filter parameters, but with 29% schema coverage, it does not fully compensate for undocumented params like limit or customer_id.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Keyword-level performance' and lists specific metrics (match type, quality score, clicks, cost, conversions), distinguishing it from sibling tools like gads_campaign_performance or gads_ad_group_performance.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description mentions filtering by campaign_id or ad_group_id and defaults, but does not explicitly guide when to use this vs alternatives (e.g., gads_search_terms_report) or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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

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