Skip to main content
Glama
andrealufino

aapl-ads-mcp

get_keyword_report

Retrieve keyword-level performance metrics for an Apple Search Ads ad group, including impressions, taps, spend, installs, and install rate, grouped by country with grand totals.

Instructions

Fetch performance metrics for targeting keywords in a specific Apple Search Ads ad group: impressions, taps, TTR, spend, CPI, installs, and install rate broken down per keyword. Requires ASA authentication; read-only. Use get_search_terms_report to see the actual user queries that triggered these keywords. Results are grouped by country/region and include grand totals. Defaults to the last 30 days with WEEKLY granularity.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
startDateNoStart of the reporting period (YYYY-MM-DD). Defaults to 30 days ago.
endDateNoEnd of the reporting period (YYYY-MM-DD). Defaults to today.
granularityNoTime granularity for the breakdown rows: HOURLY, DAILY, WEEKLY, or MONTHLY. Defaults to WEEKLY.
campaignIdYesID of the campaign containing the ad group. Obtain from list_campaigns.
adGroupIdYesID of the ad group to report on. Obtain from list_ad_groups.

Implementation Reference

  • The get_keyword_report tool handler: calls the ASA API POST /reports/campaigns/{campaignId}/keywords with a filter on adGroupId, builds the report body using shared helper, and returns parsed JSON results.
    // --- get_keyword_report ---------------------------------------------------
    
    server.tool(
      "get_keyword_report",
      "Fetch performance metrics for targeting keywords in a specific Apple Search Ads ad group: impressions, taps, TTR, spend, CPI, installs, and install rate broken down per keyword. Requires ASA authentication; read-only. Use get_search_terms_report to see the actual user queries that triggered these keywords. Results are grouped by country/region and include grand totals. Defaults to the last 30 days with WEEKLY granularity.",
      {
        startDate: z
          .string()
          .optional()
          .describe("Start of the reporting period (YYYY-MM-DD). Defaults to 30 days ago."),
        endDate: z
          .string()
          .optional()
          .describe("End of the reporting period (YYYY-MM-DD). Defaults to today."),
        granularity: GranularityEnum.optional().describe(
          "Time granularity for the breakdown rows: HOURLY, DAILY, WEEKLY, or MONTHLY. Defaults to WEEKLY."
        ),
        campaignId: z
          .number()
          .int()
          .positive()
          .describe("ID of the campaign containing the ad group. Obtain from list_campaigns."),
        adGroupId: z
          .number()
          .int()
          .positive()
          .describe("ID of the ad group to report on. Obtain from list_ad_groups."),
      },
      async (args) => {
        const input = GetKeywordReportInputSchema.parse(args);
        const defaults = defaultDateRange();
        const startDate = input.startDate ?? defaults.startDate;
        const endDate = input.endDate ?? defaults.endDate;
        const granularity = input.granularity ?? "WEEKLY";
    
        validateDateRange(startDate, endDate);
    
        // campaignId is already in the URL path — adding it as a condition causes
        // INVALID_CONDITION_INPUT from ASA. Only adGroupId is needed here.
        const selector: ReportRequest["selector"] = {
          conditions: [
            {
              field: "adGroupId",
              operator: "EQUALS",
              values: [String(input.adGroupId)],
            },
          ],
        };
    
        const body = buildReportBody(startDate, endDate, granularity, "keywordId", selector);
        const response = await client.post<ReportResponse>(
          `/reports/campaigns/${input.campaignId}/keywords`,
          body
        );
        const result = parseReportResponse(response.data);
    
        return {
          content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
        };
      }
    );
  • GetKeywordReportInputSchema: Zod schema defining input fields (startDate, endDate, granularity, campaignId, adGroupId).
    export const GetKeywordReportInputSchema = z.object({
      ...dateRangeFields,
      campaignId: z.number().int().positive().describe("Campaign ID."),
      adGroupId: z.number().int().positive().describe("Ad group ID."),
    });
  • GetKeywordReportInput: TypeScript type inferred from the input schema.
    export type GetKeywordReportInput = z.infer<typeof GetKeywordReportInputSchema>;
    export type GetSearchTermsReportInput = z.infer<typeof GetSearchTermsReportInputSchema>;
  • registerReportsTools function — registers all report tools including get_keyword_report.
    /** Registers all four report tools. */
    export function registerReportsTools(server: McpServer, client: AsaClient): void {
  • src/server.ts:10-31 (registration)
    registerReportsTools is imported and called in createServer() to register the tool on the MCP server.
      registerReportsTools,
    } from "./tools/index.js";
    
    const SERVER_INFO = {
      name: "aapl-ads-mcp",
      version: "0.1.0",
    };
    
    /** Creates and configures the MCP server with all registered tools. */
    export function createServer(): McpServer {
      const server = new McpServer(SERVER_INFO);
      const client = new AsaClient(getConfig());
    
      registerHealthTool(server);
      registerOrgsTools(server, client);
      registerCampaignsTools(server, client);
      registerAdGroupsTools(server, client);
      registerKeywordsTools(server, client);
      registerReportsTools(server, client);
    
      return server;
    }
Behavior4/5

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

No annotations provided, so description carries full burden. It states read-only, authentication requirement, results grouping by country/region, inclusion of grand totals, and default time range/granularity. Lacks details on rate limits or error behavior but is sufficient for typical use.

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 concise sentences that front-load the purpose and provide necessary context without unnecessary detail. Every sentence adds value.

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

Completeness4/5

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

No output schema, but description lists expected metrics, grouping, defaults, and authentication. It references sibling tools for obtaining required IDs. Could be improved by mentioning pagination or data limits, but overall complete for a reporting tool.

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?

Schema description coverage is 100%, with each parameter having a clear description. The description does not add additional parameter-level meaning beyond what the schema already provides, so baseline score of 3 is appropriate.

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 explicitly states 'Fetch performance metrics for targeting keywords' and lists specific metrics (impressions, taps, TTR, etc.). It clearly differentiates from the sibling tool get_search_terms_report by stating that tool shows actual user queries.

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

Usage Guidelines5/5

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

Provides explicit guidance on when to use the alternative tool ('Use get_search_terms_report to see the actual user queries') and mentions prerequisites ('Requires ASA authentication; read-only'). Also indicates default parameters.

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/andrealufino/aapl-ads-mcp'

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