Skip to main content
Glama
alloufj

Grips Intelligence MCP Server

by alloufj

Get AdWords spend over time

grips_get_adwords
Read-onlyIdempotent

Get monthly paid-media spend, ad clicks, and CPC for up to 50 domains. Analyze paid-search cost trends, auction inflation, and total investment over a custom date range.

Instructions

Returns paid-media spend, ad clicks, and CPC for one or more domains — monthly timeseries plus an aggregated roll-up. Use this to evaluate paid-spend trends, auction-cost inflation, or total investment in paid search.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
domainsYesOne or more domains (e.g. ['adidas.com', 'nike.com']). Protocol and trailing slash are stripped automatically.
date_fromYesStart of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-01-01'.
date_toYesEnd of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-12-31'.
countryNoOptional country filter. Defaults to the server's GRIPS_DEFAULT_COUNTRY (usually 'US').
formatNoResponse format. 'markdown' is human-readable; 'json' is machine-parseable.markdown

Implementation Reference

  • Main handler function for grips_get_adwords. Calls the Grips API with ADWORDS_QUERY, processes timeseries and aggregated results, and returns either JSON or markdown.
    export async function runAdwords(
      client: GripsApiClient,
      args: AdwordsInput,
      defaultCountry: string,
    ): Promise<string> {
      const country = args.country ?? defaultCountry;
      const variables = buildFilters({
        domains: args.domains,
        date_from: args.date_from,
        date_to: args.date_to,
        country,
      });
    
      let raw: { timeseries?: unknown; aggregated?: unknown } = {};
      try {
        raw = await client.query<{ timeseries?: unknown; aggregated?: unknown }>({
          query: ADWORDS_QUERY,
          variables,
        });
      } catch (err) {
        const msg = formatUpstreamError(err);
        const hint = errorHint(err);
        return `Error: ${msg}${hint ? `\n\nHint: ${hint}` : ""}`;
      }
    
      const rows = toArray<AdwordsTimeseriesRow>(raw.timeseries).map((r) => ({
        ...r,
        date: normalizeDate(r?.date),
      }));
      // Grips returns `aggregated` as a single object (not an array) for the
      // adwords endpoint — use toObject, not toArray.
      const agg = toObject<AdwordsAggregated>(raw.aggregated);
    
      if (rows.length === 0 && Object.keys(agg).length === 0) {
        return `**Grips AdWords spend — ${args.domains.join(", ")} (${country}, ${args.date_from} → ${args.date_to})**\n\n_No paid-search data returned by Grips for this filter set. The domain may not be running paid search, or may not be covered by Grips for this country/window._`;
      }
    
      if (args.format === "json") {
        return toJson({
          domains: args.domains,
          country,
          date_from: args.date_from,
          date_to: args.date_to,
          timeseries: rows.map((r) => ({
            date: r.date,
            adcost: safeNumberOrNull(r.adcost),
            adclicks: safeNumberOrNull(r.adclicks),
            cpc: safeNumberOrNull(r.cpc),
          })),
          aggregated: {
            adcost: safeNumberOrNull(agg.adcost),
            adclicks: safeNumberOrNull(agg.adclicks),
            cpc: safeNumberOrNull(agg.cpc),
          },
        });
      }
    
      const tsTable = toMarkdownTable(
        rows.map((r) => ({
          Date: r.date,
          "Ad Cost": formatCurrency(r.adcost),
          "Ad Clicks": formatInt(r.adclicks),
          CPC: formatCurrency(r.cpc),
        })),
      );
    
      const aggTable = toMarkdownTable([
        {
          "Ad Cost (total)": formatCurrency(agg.adcost),
          "Ad Clicks (total)": formatInt(agg.adclicks),
          "CPC (avg)": formatCurrency(agg.cpc),
        },
      ]);
    
      const header = `**Grips AdWords spend — ${args.domains.join(", ")} (${country}, ${args.date_from} → ${args.date_to})**`;
      return truncateIfNeeded(
        `${header}\n\n### Aggregated\n\n${aggTable}\n\n### Monthly timeseries\n\n${tsTable}`,
      );
    }
  • src/index.ts:103-112 (registration)
    Tool registration in the MCP server using adwordsToolDef.name ('grips_get_adwords').
    server.registerTool(
      adwordsToolDef.name,
      {
        title: adwordsToolDef.title,
        description: adwordsToolDef.description,
        inputSchema: adwordsToolDef.inputSchema,
        annotations: adwordsToolDef.annotations,
      },
      async (args) => asText(await runAdwords(client, args as any, defaultCountry)),
    );
  • Input schema definition (baseFilterFields) used by grips_get_adwords. Defines domains, date_from, date_to, country, and format fields.
    export const baseFilterFields = {
      domains: domainsArray.describe(
        "One or more domains (e.g. ['adidas.com', 'nike.com']). Protocol and trailing slash are stripped automatically.",
      ),
      date_from: isoDate.describe(
        "Start of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-01-01'.",
      ),
      date_to: isoDate.describe(
        "End of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-12-31'.",
      ),
      country: countryEnum
        .optional()
        .describe("Optional country filter. Defaults to the server's GRIPS_DEFAULT_COUNTRY (usually 'US')."),
      format: outputFormat,
    };
  • Typed input schema (z.object wrapping baseFilterFields) and exported tool definition with name 'grips_get_adwords'.
    export const adwordsInputSchema = z.object(baseFilterFields);
    export type AdwordsInput = z.infer<typeof adwordsInputSchema>;
    
    export const adwordsToolDef = {
      name: "grips_get_adwords",
      title: "Get AdWords spend over time",
      description:
        "Returns paid-media spend, ad clicks, and CPC for one or more domains — monthly timeseries plus an aggregated roll-up. Use this to evaluate paid-spend trends, auction-cost inflation, or total investment in paid search.",
      inputSchema: baseFilterFields,
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: true,
      },
    };
  • GraphQL query (ADWORDS_QUERY) used by the handler to fetch adwords timeseries and aggregated data from the Grips API.
    export const ADWORDS_QUERY = `
      input Date {
        gte: String!
        lte: String!
      }
      input OrArray {
        in: [String!]
      }
      input Filters {
        country: String
        domain: OrArray!
        date: Date!
      }
      query ii_transactional($filters: Filters) {
        timeseries: fetch(filters: $filters) {
          date(type: Array, sort_asc: date) {
            adcost: sum(a: adcost)
            adclicks: sum(a: adclicks)
            cpc: divide(a: adcost, by: adclicks)
          }
        }
        aggregated: fetch(filters: $filters) {
          adcost: sum(a: adcost)
          adclicks: sum(a: adclicks)
          cpc: divide(a: adcost, by: adclicks)
        }
      }
    `;
  • TypeScript interfaces for AdwordsTimeseriesRow and AdwordsAggregated.
    /** One monthly row from the adwords endpoint. */
    export interface AdwordsTimeseriesRow {
      date?: string;
      adcost?: number | string | null;
      adclicks?: number | string | null;
      cpc?: number | string | null;
      [key: string]: unknown;
    }
    
    /** Aggregated totals for the adwords endpoint. */
    export interface AdwordsAggregated {
      adcost?: number | string | null;
      adclicks?: number | string | null;
      cpc?: number | string | null;
Behavior4/5

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

Annotations already declare the tool as read-only, idempotent, and non-destructive. The description adds context on the output format (monthly timeseries + roll-up), which complements the annotations without contradiction.

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-loading the return value and purpose. Every sentence adds value, with no redundancy or fluff.

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?

Given the well-documented schema and annotations, the description covers the essential context: what data is returned and in what structure (monthly timeseries + roll-up). Could briefly mention pagination or limitations but overall sufficient.

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 coverage is 100%, so the schema already documents all parameters. The description adds general context about the return values but does not provide additional parameter-specific semantics beyond what the schema offers.

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 the tool returns paid-media spend, ad clicks, and CPC for domains as monthly timeseries plus a roll-up. It distinguishes itself from sibling tools by specifying the exact metrics and output structure.

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

Usage Guidelines4/5

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

Explicitly lists use cases: evaluating paid-spend trends, auction-cost inflation, or total investment. Does not include when-not-to-use or mention alternatives, but the guidance is clear enough for an agent.

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/alloufj/grips-mcp-server'

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