Google Ads MCP Server — Spec
Google Ads MCP Server
Replica o toolkit demonstrado por Austin Lau (@helloitsaustin) — um MCP Server que expõe a Google Ads API (via GAQL) para o Claude, permitindo skills como
mine-search-terms,budget-optimize,audit-ad-copy,weekly-review, etc.
Referência: https://x.com/helloitsaustin/status/2036553581625745511
1. O que é
Um servidor MCP (Model Context Protocol) em TypeScript/Node que:
- Conecta ao Google Ads API via OAuth 2.0
- Expõe tools que o Claude pode chamar (GAQL queries, mutations, relatórios)
- Permite criar skills no Claude.ai que encapsulam workflows de performance marketing
- Funciona no Claude Desktop, Claude Code e Cowork
2. Tools expostos
2.1 — get_search_terms
Puxa termos de busca de uma ou mais campanhas.
Parâmetros:
{
customer_id: string;
date_range: "LAST_7_DAYS" | "LAST_14_DAYS" | "LAST_30_DAYS" | "LAST_90_DAYS";
min_clicks?: number; // default: 1
limit?: number; // default: 2000
order_by?: "cost" | "clicks" | "impressions" | "conversions"; // default: "cost"
campaign_ids?: string[]; // filtra por campanhas específicas (opcional)
status_filter?: "NONE" | "ALL"; // default: "NONE" (só não-acionados)
}
GAQL gerado:
SELECT
search_term_view.search_term,
search_term_view.status,
campaign.id,
campaign.name,
ad_group.name,
metrics.clicks,
metrics.impressions,
metrics.cost_micros,
metrics.conversions,
metrics.ctr,
metrics.average_cpc
FROM search_term_view
WHERE
segments.date DURING {date_range}
AND metrics.clicks >= {min_clicks}
AND search_term_view.status = {status_filter}
ORDER BY metrics.cost_micros DESC
LIMIT {limit}
Retorno: Array de SearchTerm com campos normalizados (cost em $ não micros).
2.2 — get_campaigns
Lista campanhas com métricas de período.
Parâmetros:
{
customer_id: string;
date_range: DateRange;
status?: "ENABLED" | "PAUSED" | "REMOVED"; // default: "ENABLED"
}
2.3 — get_ad_groups
Lista ad groups de uma campanha específica.
Parâmetros:
{
customer_id: string;
campaign_id?: string;
date_range: DateRange;
}
2.4 — get_keywords
Keywords ativas com performance.
Parâmetros:
{
customer_id: string;
campaign_id?: string;
date_range: DateRange;
min_impressions?: number;
}
2.5 — get_ads
Anúncios com copy e performance.
Parâmetros:
{
customer_id: string;
campaign_id?: string;
date_range: DateRange;
include_paused?: boolean;
}
2.6 — add_negative_keywords
Adiciona negative keywords em bulk (exact match por padrão).
Parâmetros:
{
customer_id: string;
negatives: Array<{
keyword: string;
match_type: "EXACT" | "PHRASE" | "BROAD";
level: "campaign" | "ad_group";
campaign_id: string;
ad_group_id?: string; // obrigatório se level = "ad_group"
}>;
}
Comportamento:
- Batch mutate (único request para N keywords)
- Retorna
{ added: N, failed: M, errors: [...] } - Idempotente: se keyword já existe como negative, ignora sem erro
2.7 — get_account_budget
Budget e spend atual da conta/campanhas.
Parâmetros:
{
customer_id: string;
date_range: DateRange;
}
2.8 — update_campaign_budget
Atualiza budget diário de uma campanha.
Parâmetros:
{
customer_id: string;
campaign_id: string;
new_daily_budget_usd: number;
}
2.9 — get_account_conventions
Tool especial — lê arquivo account-conventions.md do working directory e retorna como contexto. Usado pelas skills para carregar convenções de nomenclatura da conta antes de avaliar campanhas. Não chama a Google Ads API.
2.10 — run_gaql
Escape hatch — executa GAQL arbitrário. Read-only: rejeita queries com INSERT, UPDATE, DELETE, MUTATE.
Parâmetros:
{
customer_id: string;
query: string;
max_rows?: number; // default: 5000, max: 10000
}
3. Arquitetura
google-ads-mcp/
├── src/
│ ├── index.ts # Entry point — registra tools MCP
│ ├── server.ts # MCP Server setup (stdio ou HTTP)
│ ├── auth/
│ │ ├── oauth.ts # Google OAuth 2.0 flow
│ │ └── token-store.ts # Refresh token persistence
│ ├── api/
│ │ ├── client.ts # Google Ads API client (REST v18)
│ │ ├── gaql.ts # GAQL query builder + executor
│ │ └── mutations.ts # Bulk mutate helpers
│ ├── tools/
│ │ ├── get-search-terms.ts
│ │ ├── get-campaigns.ts
│ │ ├── get-ad-groups.ts
│ │ ├── get-keywords.ts
│ │ ├── get-ads.ts
│ │ ├── add-negative-keywords.ts
│ │ ├── get-account-budget.ts
│ │ ├── update-campaign-budget.ts
│ │ ├── get-account-conventions.ts
│ │ └── run-gaql.ts
│ └── utils/
│ ├── micros.ts # micros ↔ $ conversion
│ ├── date-ranges.ts # DateRange enum → GAQL string
│ └── csv.ts # CSV bulk-upload-ready (Google Ads Editor format)
├── skills/
│ ├── mine-search-terms.md
│ ├── search-term-methodology.md
│ ├── budget-optimize.md
│ ├── audit-ad-copy.md
│ ├── investigate-campaign.md
│ ├── weekly-review.md
│ ├── performance-analysis.md
│ └── account-conventions.md # Template — usuário customiza
├── package.json
├── tsconfig.json
└── README.md
4. Auth — Google Ads API
4.1 — Credenciais necessárias
| Credencial | Onde obter | Env var |
|---|---|---|
client_id |
Google Cloud Console → OAuth 2.0 | GOOGLE_CLIENT_ID |
client_secret |
Google Cloud Console | GOOGLE_CLIENT_SECRET |
refresh_token |
OAuth flow (1x, persiste) | GOOGLE_REFRESH_TOKEN |
developer_token |
Google Ads API Center | GOOGLE_ADS_DEVELOPER_TOKEN |
customer_id |
Google Ads (sem hífens) | GOOGLE_ADS_CUSTOMER_ID |
login_customer_id |
MCC account ID (se MCC) | GOOGLE_ADS_LOGIN_CUSTOMER_ID |
4.2 — Obter refresh token (1x)
npm run auth
# Abre browser → Google OAuth → grava refresh_token em ~/.google-ads-mcp/credentials.json
4.3 — Headers de request
Authorization: Bearer {access_token}
developer-token: {developer_token}
login-customer-id: {login_customer_id} // só se MCC
5. Stack
| Item | Escolha |
|---|---|
| Runtime | Node.js 20+ |
| Linguagem | TypeScript 5 |
| MCP SDK | @modelcontextprotocol/sdk |
| Google Ads | REST API v18 (não gRPC) |
| Auth | googleapis package (OAuth2 client) |
| Transport | stdio (Claude Desktop) + HTTP/SSE opcional |
| CSV output | csv-stringify |
6. Skills (prompts estruturados)
6.1 — mine-search-terms
Load `search-term-methodology` and `account-conventions` skills before starting.
Mine the search term report for negative keyword candidates, using relevance-based
evaluation (not just conversion count). Final deliverable is a bulk-upload-ready
CSV importable into Google Ads Editor.
Steps:
1. Call get_search_terms with date_range=LAST_30_DAYS, min_clicks=1,
limit=2000, order_by=cost. Pass campaign_ids if $ARGUMENTS provided.
2. Evaluate every term using three-way cross-reference:
- Search term (what user typed)
- Keyword (what triggered the ad)
- Campaign + ad group theme (use account-conventions to interpret names)
Decide: Negate or Keep. Relevance matters more than conversion count.
3. Build CSV with only negation candidates.
Format: Google Ads Editor bulk upload. Filename: search-term-negatives-YYYY-MM-DD.csv
4. Present top 10 negatives by spend with reasoning + category breakdown.
Offer to execute add_negative_keywords campaign-by-campaign with explicit
user YES before each batch.
6.2 — search-term-methodology
Search Term Mining Methodology
Goal: relevance to campaign theme — NOT "zero conversions = bad".
Core Approach:
1. Filter to unactioned terms (status=NONE) — get_search_terms does this automatically.
2. Sort by spend descending — $5 CPC with 20 clicks matters more than $0.10 with 200.
3. Cross-reference: search term + matched keyword + campaign/ad group name.
Question: does this search term fit the theme?
Decision Rules:
- Competitor brand → Negate (exact)
- Job/hiring intent → Negate (broad)
- Research/educational intent → Negate (phrase)
- Free/open-source alternative → Negate (exact)
- Unrelated industry → Negate (broad)
- Adjacent product, same industry → Flag for review
- On-theme, low conversion → Keep (more data needed)
- High-volume, on-theme, no keyword yet → Keyword opportunity
6.3 — budget-optimize
Analyze budget allocation and surface reallocation opportunities.
Steps:
1. Call get_account_budget for LAST_30_DAYS.
2. Call get_campaigns for same period.
3. For each campaign compute: utilization % (spend/cap), CPA vs average, ROAS vs average.
4. Flag:
- Budget-constrained (utilization >90%) + CPA ≤ average → increase candidate
- Under-delivering (utilization <50%) + poor CPA → pause or cut
- High cost + zero conversions + 30 days → immediate cut
5. Propose reallocation table: From → To, amount, expected impact.
6. Ask confirmation before calling update_campaign_budget on any campaign.
6.4 — weekly-review
Generate weekly performance review.
Sections:
1. Executive summary — 3 bullets: what worked, what didn't, one action item.
2. Campaign performance table — this week vs last week (impressions, clicks,
CTR, conversions, CPA, spend).
3. Top movers — biggest positive/negative WoW changes with hypothesis.
4. Search term highlights — call get_search_terms LAST_7_DAYS, top 5 spend
with no conversions as immediate negative candidates.
5. Budget pacing — on track / over / under for the month.
6. Recommendations — max 5, prioritized, with effort estimate (Quick/Medium/Big).
Output: Markdown report, ready to paste into Notion or email.
6.5 — audit-ad-copy
Audit ad copy quality and flag improvement opportunities.
Steps:
1. Call get_ads for LAST_30_DAYS.
2. For each ad evaluate:
- Headlines: presence of keyword, value prop, CTA
- Descriptions: specificity, differentiator, urgency
- CTR vs campaign average (below average = copy problem)
- Pinning issues (headline 1/2 pinned unnecessarily?)
3. Flag: underperforming ads (CTR < 50% of campaign avg) + reason.
4. Suggest rewrites for top 3 underperformers.
5. Output table: Ad | CTR | Issue | Suggested Fix.
7. Setup — Claude Desktop (local)
7.1 — Instalar
git clone https://github.com/kodama-labs/google-ads-mcp
cd google-ads-mcp
npm install && npm run build
npm run auth # OAuth browser flow
7.2 — claude_desktop_config.json
{
"mcpServers": {
"google-ads": {
"command": "node",
"args": ["/absolute/path/to/google-ads-mcp/dist/index.js"],
"env": {
"GOOGLE_CLIENT_ID": "...",
"GOOGLE_CLIENT_SECRET": "...",
"GOOGLE_REFRESH_TOKEN": "...",
"GOOGLE_ADS_DEVELOPER_TOKEN": "...",
"GOOGLE_ADS_CUSTOMER_ID": "1234567890"
}
}
}
}
7.3 — Skills no Claude.ai
Claude.ai → Settings → Skills → New Skill → colar conteúdo de cada arquivo em /skills/.
8. Deploy remoto (HTTP/SSE)
Para multi-user ou Cowork:
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const transport = new StreamableHTTPServerTransport({ port: 3100 });
await server.connect(transport);
Auth multi-user: refresh_token por usuário em DB, passado via header X-Google-Customer-Id.
9. Gotchas
- Developer token: requer aprovação Google (~1 semana com conta MCC com spend). Test accounts liberados imediatamente.
- Micros: Google Ads retorna cost em micros (÷1.000.000 pra $). Sempre normalizar antes de exibir.
- MCC:
login-customer-id= MCC ID,customer-id= conta do cliente. Ambos nos headers. - Rate limits: 15.000 operações/dia tier básico. Bulk mutate: até 5.000 operations por request.
- CSV format: colunas específicas do Google Ads Editor — ver https://support.google.com/google-ads/editor/answer/57747
10. Roadmap
- Fase 1 — MCP Server local (stdio), tools de leitura (get_*)
- Fase 2 — Mutations (
add_negative_keywords,update_campaign_budget) - Fase 3 — Skills completas em
/skills/ - Fase 4 — Auth CLI (
npm run auth) + token persistence - Fase 5 — Deploy HTTP/SSE multi-user
- Fase 6 — Publicar no npm como
@kodama/google-ads-mcp
11. Referências
- Google Ads API REST v18: https://developers.google.com/google-ads/api/rest/overview
- GAQL reference: https://developers.google.com/google-ads/api/fields/v18/overview
- MCP SDK TypeScript: https://github.com/modelcontextprotocol/typescript-sdk
- Google Ads Editor bulk upload: https://support.google.com/google-ads/editor/answer/57747
- Post original Austin Lau: https://x.com/helloitsaustin/status/2036553581625745511