{
  "markdown": "# MCP Google Ads Server\n\nAn MCP (Model Context Protocol) server for the Google Ads API with built-in safeguards for review before changes go live. Production-proven with MCC (Manager Account) support, 36 tools for campaign management, reporting, and optimization. v1.2.0 adds Demand Gen campaign creation end-to-end.\n\n## Features\n\n- **MCC Support**: Works with Manager accounts and multiple client accounts\n- **Auto-Context**: Detects which client account based on your working directory\n- **Safe by Default**: All new items created in PAUSED state\n- **Approval Workflow**: Enable items only after manual review\n- **Validation**: Validates ads before creating to catch errors early\n- **Resilience**: Circuit breakers, retry with backoff, and timeout handling (cockatiel)\n- **Structured Logging**: Pino-based logging with build fingerprinting\n\n## Setup\n\n### 1. Google Ads API Access\n\nYou need:\n- A Google Ads **Developer Token** (apply at [Google Ads API Center](https://developers.google.com/google-ads/api/docs/get-started/dev-token))\n- **OAuth credentials** (Client ID & Secret from Google Cloud Console)\n- A **Refresh Token** for your MCC account\n\n#### Getting OAuth Credentials\n\n1. Go to [Google Cloud Console](https://console.cloud.google.com/)\n2. Create a project or select existing\n3. Enable the **Google Ads API**\n4. Go to **Credentials** → **Create Credentials** → **OAuth Client ID**\n5. Choose **Desktop App**\n6. Download the JSON (contains client_id and client_secret)\n\n#### Getting a Refresh Token\n\nBring your own OAuth client (the `client_id` / `client_secret` from the step\nabove) and run the bundled helper. It runs Google's installed-app loopback flow\n**with PKCE (S256)** and prints your refresh token. It reads nothing from your\nhome directory and needs no shared OAuth keyfile.\n\n```bash\nexport GOOGLE_ADS_CLIENT_ID=\"YOUR_CLIENT_ID.apps.googleusercontent.com\"\nexport GOOGLE_ADS_CLIENT_SECRET=\"YOUR_CLIENT_SECRET\"\nnode get-refresh-token.cjs\n```\n\nYour browser opens for Google sign-in; approve as the Google account that owns\nthe Ads data. On success the helper prints one line to stdout:\n\n```\nGOOGLE_ADS_REFRESH_TOKEN=1//0a...\n```\n\nSet that value in your environment (or `config.json`, below). The OAuth scope\nrequested is read from `config.json` (`oauth.scope`), falling back to\n`config.example.json`, so the helper and the running server always request the\nsame scope. This MCP requests only the minimum scope it needs:\n`https://www.googleapis.com/auth/adwords`.\n\n> Do not run this with stdout redirected to a shared log file — the refresh\n> token is printed to stdout by design.\n\nNote: `GOOGLE_ADS_DEVELOPER_TOKEN` is a separate Google Ads API credential, not\nan OAuth scope — set it independently (see Environment Variables below).\n\n### 2. Install\n\n```bash\nnpm install mcp-google-ads\n```\n\nOr clone and build from source:\n\n```bash\ngit clone https://github.com/mharnett/mcp-google-ads.git\ncd mcp-google-ads\nnpm install\nnpm run build\n```\n\n**Security:** Never share your `.mcp.json` file or commit it to git -- it may contain API credentials. Add `.mcp.json` to your `.gitignore`.\n\n### 3. Configure\n\n```bash\ncp config.example.json config.json\n```\n\nEdit `config.json` with your credentials:\n\n```json\n{\n  \"oauth\": {\n    \"scope\": \"https://www.googleapis.com/auth/adwords\"\n  },\n  \"google_ads\": {\n    \"developer_token\": \"YOUR_DEVELOPER_TOKEN\",\n    \"client_id\": \"YOUR_CLIENT_ID.apps.googleusercontent.com\",\n    \"client_secret\": \"YOUR_CLIENT_SECRET\",\n    \"refresh_token\": \"YOUR_REFRESH_TOKEN\",\n    \"mcc_customer_id\": \"123-456-7890\"\n  },\n  \"clients\": {\n    \"my-client\": {\n      \"customer_id\": \"111-222-3333\",\n      \"name\": \"My Client\",\n      \"folder\": \"/path/to/client/workspace\"\n    },\n    \"another-client\": {\n      \"customer_id\": \"444-555-6666\",\n      \"name\": \"Another Client\",\n      \"folder\": \"/path/to/another/workspace\"\n    }\n  },\n  \"defaults\": {\n    \"create_paused\": true,\n    \"label_prefix\": \"claude-\",\n    \"require_approval_for_enable\": true\n  }\n}\n```\n\n### Environment Variables\n\nAlternatively, set credentials via environment variables (these override `config.json`):\n\n| Variable | Required | Description |\n|---|---|---|\n| `GOOGLE_ADS_DEVELOPER_TOKEN` | Yes | Google Ads API developer token |\n| `GOOGLE_ADS_CLIENT_ID` | Yes | OAuth 2.0 client ID |\n| `GOOGLE_ADS_CLIENT_SECRET` | Yes | OAuth 2.0 client secret |\n| `GOOGLE_ADS_REFRESH_TOKEN` | Yes | OAuth 2.0 refresh token |\n| `GOOGLE_ADS_MCP_WRITE` | No | Set to `true` to expose mutating tools (create/update/pause/enable/remove/apply). Default: read-only. |\n\n### Read-only by default\n\nThe server ships read-only. Mutating tools (anything that creates, updates,\npauses, enables, removes, links, or applies) are hidden from the tool list\nuntil you set `GOOGLE_ADS_MCP_WRITE=true` in the MCP server environment.\nIf a write tool is somehow invoked without that flag, the server returns a\nclear error pointing at the env var.\n\nThis is deliberate: a casual chat message like \"activate the Fundraising\ncampaign\" should not move live ad spend without an explicit opt-in.\n\n### 4. Add to Claude Code\n\nAdd to your Claude Code MCP settings (`~/.claude/settings.json` or project settings):\n\n```json\n{\n  \"mcpServers\": {\n    \"google-ads\": {\n      \"command\": \"node\",\n      \"args\": [\"node_modules/mcp-google-ads/dist/index.js\"]\n    }\n  }\n}\n```\n\nOr if installed from source:\n\n```json\n{\n  \"mcpServers\": {\n    \"google-ads\": {\n      \"command\": \"node\",\n      \"args\": [\"/path/to/mcp-google-ads/dist/index.js\"]\n    }\n  }\n}\n```\n\nRestart Claude Code.\n\n**Claude Desktop:** Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows).\n\n## Usage\n\n### Workflow\n\n```\n1. cd into client folder → auto-detects account context\n2. Ask Claude to create campaigns/ads → all created PAUSED\n3. Review in Google Ads UI or Editor\n4. Tell Claude to enable approved items\n5. Claude enables (requires your approval prompt)\n```\n\n### Available Tools (36)\n\n#### Context & Discovery\n| Tool | Description |\n|------|-------------|\n| `google_ads_get_client_context` | Detect which account from working directory |\n| `google_ads_list_campaigns` | List all campaigns with status and metrics |\n| `google_ads_list_ad_groups` | List ad groups in a campaign |\n| `google_ads_list_pending_changes` | Show paused items with claude- label |\n| `google_ads_list_conversion_actions` | List conversion actions |\n\n#### Campaign Management\n| Tool | Description |\n|------|-------------|\n| `google_ads_create_campaign` | Create campaign (PAUSED). Supports SEARCH + DEMAND_GEN channels, richer bidding (MANUAL_CPC / MAXIMIZE_CLICKS / MAXIMIZE_CONVERSIONS / TARGET_CPA), geo + language targeting, start/end dates |\n| `google_ads_create_ad_group` | Create ad group (PAUSED). `type` accepts SEARCH_STANDARD (default) or DEMAND_GEN_MULTI_ASSET_AD_GROUP |\n| `google_ads_create_responsive_search_ad` | Create RSA with validation (PAUSED) |\n| `google_ads_create_image_asset` | Upload PNG/JPG/GIF image asset (validates ≤5MB, ≥600×314) for use in Demand Gen ads |\n| `google_ads_create_demand_gen_multi_asset_ad` | Create a Demand Gen multi-asset ad (PAUSED) — validates char/count caps before API call, fails fast if ad_group isn't DG |\n| `google_ads_create_keywords` | Create keywords (PAUSED) |\n| `google_ads_validate_ad` | Validate RSA without creating |\n| `google_ads_enable_items` | Enable items (make LIVE) — **requires approval** |\n| `google_ads_pause_items` | Pause active items |\n| `google_ads_pause_keywords` | Pause specific keywords |\n| `google_ads_update_campaign_budget` | Update campaign daily budget |\n\n#### Tracking & URLs\n| Tool | Description |\n|------|-------------|\n| `google_ads_get_campaign_tracking` | Get tracking templates and URL parameters |\n| `google_ads_update_campaign_tracking` | Update tracking templates |\n\n#### Negative Keywords\n| Tool | Description |\n|------|-------------|\n| `google_ads_create_shared_set` | Create shared negative keyword list |\n| `google_ads_link_shared_set` | Link shared set to campaign |\n| `google_ads_unlink_shared_set` | Unlink shared set from campaign |\n| `google_ads_add_shared_negatives` | Add keywords to shared negative list |\n| `google_ads_remove_shared_negatives` | Remove keywords from shared list |\n| `google_ads_add_campaign_negatives` | Add campaign-level negatives |\n| `google_ads_remove_campaign_negatives` | Remove campaign-level negatives |\n| `google_ads_remove_adgroup_negatives` | Remove ad group-level negatives |\n\n#### Performance & Reporting\n| Tool | Description |\n|------|-------------|\n| `google_ads_keyword_performance` | Keyword metrics with quality score |\n| `google_ads_keyword_performance_by_conversion` | Keyword metrics by conversion action |\n| `google_ads_ad_performance` | Ad-level performance metrics |\n| `google_ads_ad_performance_by_conversion` | Ad metrics by conversion action |\n| `google_ads_search_term_report` | Search term query report |\n| `google_ads_search_term_report_by_conversion` | Search terms by conversion action |\n| `google_ads_search_term_insights` | Search term category insights |\n| `google_ads_search_term_insight_terms` | Terms within insight categories |\n| `google_ads_keyword_volume` | Keyword planner volume estimates |\n\n#### Advanced\n| Tool | Description |\n|------|-------------|\n| `google_ads_gaql_query` | Run raw GAQL queries |\n\n### Example Commands\n\n```\n# Check which account you're working with\n\"What Google Ads account am I connected to?\"\n\n# List campaigns\n\"Show me all campaigns in this account\"\n\n# Create a new campaign\n\"Create a Search campaign for brand terms with $50/day budget\"\n\n# Check what's pending review\n\"What changes are pending my review?\"\n\n# After reviewing in Google Ads UI\n\"Enable the approved ads in the Brand campaign\"\n\n# Performance analysis\n\"Show me keyword performance for the last 30 days, sorted by cost\"\n\n# Run custom GAQL\n\"Run a GAQL query to get all ad groups with CTR below 2%\"\n```\n\n### Example: Create a Demand Gen Campaign End-to-End\n\n```\n# 1. Campaign: $75/day, DEMAND_GEN channel, MAXIMIZE_CONVERSIONS default,\n#    targeting Alaska (21134) + Maine (21141) in English\ngoogle_ads_create_campaign({\n  name: \"DG - Spring Promo\",\n  daily_budget: 75,\n  channel_type: \"DEMAND_GEN\",\n  geo_target_ids: [\"21134\", \"21141\"],\n  start_date: \"2026-05-01\",\n  end_date: \"2026-06-30\"\n})\n# → campaign_id: 555123\n\n# 2. Ad group: DEMAND_GEN_MULTI_ASSET_AD_GROUP\ngoogle_ads_create_ad_group({\n  campaign_id: \"555123\",\n  name: \"DG AG 1\",\n  type: \"DEMAND_GEN_MULTI_ASSET_AD_GROUP\"\n})\n# → ad_group_id: 555456\n\n# 3. Image assets (PNG/JPG/GIF, ≥600×314, ≤5MB). Returns {asset_id, ...}\ngoogle_ads_create_image_asset({ name: \"hero-landscape\", file_path: \"/abs/path/hero.png\" })\n# → asset_id: 42001\ngoogle_ads_create_image_asset({ name: \"hero-square\",    file_path: \"/abs/path/square.png\" })\n# → asset_id: 42002\ngoogle_ads_create_image_asset({ name: \"logo\",           file_path: \"/abs/path/logo.png\" })\n# → asset_id: 42003\n\n# 4. Demand Gen multi-asset ad (PAUSED). Validates char + count caps first.\ngoogle_ads_create_demand_gen_multi_asset_ad({\n  ad_group_id: \"555456\",\n  final_urls: [\"https://example.com/spring\"],\n  business_name: \"Example Org\",\n  call_to_action: \"LEARN_MORE\",\n  marketing_image_asset_ids: [\"42001\"],          // 1.91:1 landscape, ≥1 required\n  square_marketing_image_asset_ids: [\"42002\"],   // 1:1 optional\n  logo_image_asset_ids: [\"42003\"],               // logo optional\n  headlines: [\"Spring Sale Now On\", \"Save 20% Today\"],     // max 5, ≤40 chars each\n  long_headlines: [\"A longer pitch under ninety characters.\"], // max 5, ≤90 chars\n  descriptions: [\"Shop the latest looks.\", \"Free returns.\"]   // max 5, ≤90 chars each\n})\n# → resource_name: customers/.../adGroupAds/555456~67890000\n```\n\nAfter all four calls the campaign, ad group, and ad all live in your account in PAUSED state and are labeled `Claude-MM-DD-YY`. Review in the Google Ads UI, then enable via `google_ads_enable_items`.\n\n## Safety Features\n\n1. **Everything starts PAUSED** — Nothing goes live until you explicitly enable it\n2. **Label tracking** — All Claude-created items get a `claude-pending` label\n3. **Validation** — Ads are validated before creation (headline/description lengths, etc.)\n4. **Approval prompts** — The `enable_items` tool requires explicit approval in Claude Code\n5. **Client isolation** — Working directory determines which account, preventing cross-client mistakes\n\n## Adding New Clients\n\nEdit `config.json` to add clients. Map each client to a working directory:\n\n```json\n{\n  \"clients\": {\n    \"client-slug\": {\n      \"customer_id\": \"123-456-7890\",\n      \"name\": \"Client Name\",\n      \"folder\": \"/path/to/client/workspace\"\n    }\n  }\n}\n```\n\nNo server restart needed — config is read on each request.\n\n## Troubleshooting\n\n### \"No client found for working directory\"\n- Make sure you're in a folder that matches one of your `clients` entries\n- Check that the folder path in config.json matches exactly\n\n### \"Developer token not approved\"\n- New developer tokens need approval from Google\n- Use a test account while waiting for approval\n\n### \"Authentication failed\"\n- Refresh token may be expired — regenerate it\n- Check that client_id and client_secret are correct\n\n## License\n\nMIT — see [LICENSE](LICENSE) for details.\n",
  "bytes": 13229,
  "sha": "1400ab4fe338c320d9128fd72070f12450ccc13365474eef06df8076a3b9b4a0",
  "repo_slug": "mharnett/mcp-google-ads",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_mharnett_google_ads_3e231616/readme"
}