Skip to main content
Glama
kiarashedraki

google-ads-mcp

README.md
# google-ads-api-mcp

An MCP ([Model Context Protocol](https://modelcontextprotocol.io)) server for the [Google Ads API](https://developers.google.com/google-ads/api/docs/start). Lets any MCP client — Claude, Cursor, obot, etc. — report on and manage Google Ads accounts: GAQL queries, campaign/ad group/keyword performance, search terms, budgets, pausing, negatives.

Zero Google client-library dependency: it talks to the REST endpoint directly with `fetch`, so it starts fast under `npx` and tracks new API versions with one environment variable.

## Tools

**Discover**

| Tool | Description |
|---|---|
| `list_accessible_customers` | Accounts the credentials can reach, plus the full MCC client tree |
| `account_overview` | Account details + account-wide totals for a date range |
| `gaql_fields` | Field metadata: data types, enum values, what selects with what |

**Read**

| Tool | Description |
|---|---|
| `list_campaigns` / `list_ad_groups` / `list_ads` | Structure with status, budgets, bids, RSA headlines/descriptions, policy status |
| `campaign_performance` / `ad_group_performance` | Metrics by date range, optionally segmented by date/week/month/device/network |
| `keyword_performance` | Keywords with quality score, match type, bid, metrics |
| `search_terms_report` | Real search queries with matched keyword — the negative-keyword goldmine |
| `geo_performance` | Metrics by user location |
| `gaql_search` | Any GAQL query, paginated, with micros converted |

**Write**

| Tool | Description |
|---|---|
| `set_campaign_status` / `set_ad_group_status` / `set_ad_status` | Enable or pause |
| `set_campaign_budget` | Daily budget in currency units; refuses shared budgets unless told otherwise |
| `update_keyword` | Status and/or max CPC bid |
| `add_keywords` / `add_negative_keywords` | Bulk add, campaign- or ad-group-level negatives, `validate_only` dry run |
| `remove_resource` | Remove a keyword, ad, ad group, campaign by resource name |
| `mutate` | Raw `GoogleAdsService.mutate` passthrough for everything else, with `validate_only` |

Niceties handled for you:

- **Micros → money** — `cost_micros: 3300000` comes back as `cost: 3.3`; budgets and bids are given and returned in currency units.
- **Pagination** — `gaql_search` follows page tokens until your `limit`.
- **Readable errors** — `GoogleAdsFailure` details are flattened to `ERROR_CODE — message (field: …)` plus the request id.
- **MCC aware** — set `GOOGLE_ADS_LOGIN_CUSTOMER_ID` to a manager account and every tool can target any client under it via `customer_id`.
- **Token caching** — the refresh token is exchanged once per hour, not per call.

## Setup

You need four things from Google, all one-time:

1. **Developer token** — Google Ads → a *manager* account → Tools → API Center. A test-account token works for test accounts only; apply for Basic access for production.
2. **OAuth client** — Google Cloud Console → APIs & Services → Credentials → OAuth client ID (Desktop app). Enable the *Google Ads API* on the project.
3. **Refresh token** — run any OAuth flow for scope `https://www.googleapis.com/auth/adwords` with that client. The [official guide](https://developers.google.com/google-ads/api/docs/oauth/cloud-project) has scripts; any generic "get refresh token" helper works.
4. **Customer IDs** — the account to report on, and (if you go through an MCC) the manager account ID.

Then configure your MCP client:

```json
{
  "mcpServers": {
    "google-ads": {
      "command": "npx",
      "args": ["-y", "google-ads-api-mcp"],
      "env": {
        "GOOGLE_ADS_DEVELOPER_TOKEN": "...",
        "GOOGLE_ADS_OAUTH_CLIENT_ID": "....apps.googleusercontent.com",
        "GOOGLE_ADS_OAUTH_CLIENT_SECRET": "...",
        "GOOGLE_ADS_REFRESH_TOKEN": "1//...",
        "GOOGLE_ADS_LOGIN_CUSTOMER_ID": "1234567890",
        "GOOGLE_ADS_CUSTOMER_ID": "0987654321"
      }
    }
  }
}
```

### Environment variables

| Variable | Required | Description |
|---|---|---|
| `GOOGLE_ADS_DEVELOPER_TOKEN` | yes | From API Center in a manager account |
| `GOOGLE_ADS_OAUTH_CLIENT_ID` | yes | OAuth 2.0 client ID |
| `GOOGLE_ADS_OAUTH_CLIENT_SECRET` | yes | OAuth 2.0 client secret |
| `GOOGLE_ADS_REFRESH_TOKEN` | yes | Refresh token with the `adwords` scope |
| `GOOGLE_ADS_LOGIN_CUSTOMER_ID` | no | Manager (MCC) account to authenticate through; needed when the account is accessed via a manager |
| `GOOGLE_ADS_CUSTOMER_ID` | no | Default client account when a tool call omits `customer_id` |
| `GOOGLE_ADS_API_VERSION` | no | Default `v24`. Bump when Google releases a new version; versions sunset roughly yearly |

### Docker

```bash
docker build -t google-ads-api-mcp .
docker run -e GOOGLE_ADS_DEVELOPER_TOKEN=... -e GOOGLE_ADS_OAUTH_CLIENT_ID=... -e GOOGLE_ADS_OAUTH_CLIENT_SECRET=... -e GOOGLE_ADS_REFRESH_TOKEN=... google-ads-api-mcp
```

The container speaks MCP over stdio; use your platform's stdio wrapper (e.g. obot's runtime) to expose it over HTTP.

## GAQL cheatsheet

Ask the model to use `gaql_fields` when unsure, but the shape is always:

```sql
SELECT campaign.name, metrics.clicks, metrics.cost_micros
FROM campaign
WHERE segments.date DURING LAST_30_DAYS AND campaign.status = 'ENABLED'
ORDER BY metrics.cost_micros DESC
LIMIT 50
```

Useful `FROM` resources: `campaign`, `ad_group`, `ad_group_ad`, `keyword_view`, `search_term_view`, `geographic_view`, `conversion_action`, `campaign_budget`, `asset`, `change_event`, `recommendation`, `customer_client`.

## Development

```bash
npm install
npm run build
GOOGLE_ADS_DEVELOPER_TOKEN=... GOOGLE_ADS_OAUTH_CLIENT_ID=... GOOGLE_ADS_OAUTH_CLIENT_SECRET=... GOOGLE_ADS_REFRESH_TOKEN=... GOOGLE_ADS_CUSTOMER_ID=... node dist/index.js
npm run smoke   # spawns the server over stdio and exercises the read tools against your account
```

## License

MIT

TDQS

A3.5/5.0

Scored across 21 tools

Disambiguation1/5

Multiple tools have overlapping purposes, e.g., set_ad_group_status, set_campaign_status, set_ad_status, update_keyword, and mutate all modify statuses or bids. The generic mutate duplicates nearly all specific tools, creating significant ambiguity in choosing the right tool.

Naming Consistency2/5

Naming conventions are inconsistent. Some tools follow verb_noun (set_ad_group_status), others use noun_verb (gaql_search), and some are generic (mutate). The mix of snake_case and camelCase in tool names (e.g., gaql_search vs. update_keyword) further reduces predictability.

Tool Count3/5

With 21 tools, the count is within the 16-25 range, but many tools are redundant (e.g., multiple status setters, update_keyword vs. mutate). The surface area could be trimmed to ~10 distinct tools without losing functionality, making the set feel heavy.

Completeness4/5

The tool set covers most Google Ads entities (campaigns, ad groups, ads, keywords, search terms, geo) and provides a generic GAQL query and mutate for advanced operations. However, it lacks specific create/read operations for some entities and relies on generic tools, leaving minor gaps in direct coverage.

Maintenance

ActivityMaintained
ResponsivenessNo issues