mcp-google-ads
Pilot Google Ads campaigns from Claude with built-in safety guardrails
Sponsors
Interested in sponsoring? Get in touch
What is this?
An MCP server that gives Claude full read + write access to Google Ads accounts via the REST API. Every write operation goes through a two-step safety layer: draft a change, review the preview, then confirm to execute. Budget caps, bid limits, and audit logging prevent accidental spend.
Features
- 63 tools - Campaign management, RSA ads, keywords, extensions, PMax, audiences, bidding, scheduling, keyword planner, conversions, geo targeting, policy
- Two-step safety - All mutations return a preview; nothing executes until you confirm
- Budget guardrails - Configurable daily budget cap, bid increase limits, broad+manual CPC blocker
- Audit logging - Every mutation logged to a local JSON file with timestamp and dry-run status
- Read-only mode - Expose only read tools for safe observability
- Zero config files - Configured entirely via environment variables
Quick Start
1. Build
git clone https://github.com/FGRibreau/mcp-google-ads.git
cd mcp-google-ads
cargo build --release
2. Set up credentials
# Generate OAuth2 refresh token
./scripts/generate_token.sh ~/.mcp-google-ads/credentials.json
3. Configure Claude Code
Add to ~/.claude/settings.json:
{
"mcpServers": {
"google-ads": {
"command": "/path/to/mcp-google-ads/target/release/mcp-google-ads",
"env": {
"GOOGLE_ADS_DEVELOPER_TOKEN": "your-developer-token",
"GOOGLE_ADS_CUSTOMER_ID": "123-456-7890",
"GOOGLE_ADS_CREDENTIALS_PATH": "~/.mcp-google-ads/credentials.json",
"GOOGLE_ADS_TOKEN_PATH": "~/.mcp-google-ads/token.json"
}
}
}
}
Configuration
All configuration is via environment variables. No config files.
Tools
Read (19 tools)
Write (39 tools)
All write tools return a preview. Call confirm_and_apply with dry_run=false to execute.
Safety Guardrails
Credentials Setup
1. Create a test account
- Create a Manager Account (MCC) at ads.google.com
- In the MCC, create a Test Account (Settings → Sub-account settings)
- Note both IDs
2. Get a developer token
- In MCC → Tools & Settings → API Center
- Request a developer token (test access is sufficient)
3. Create OAuth2 credentials
- Google Cloud Console → Enable Google Ads API
- Credentials → Create → OAuth 2.0 Client ID → Desktop App
- Download JSON to
~/.mcp-google-ads/credentials.json
4. Generate refresh token
./scripts/generate_token.sh
Comparison with Other Google Ads MCP Servers
Features
Safety & Reliability
Migrating from v0.2.x
v0.3.0 introduces breaking changes that strengthen the safety surface and
unlock zero-UI workflows for agents. Most callers only need to add a
status: None field to their existing draft structs.
MCP-tool callers (the common case)
If you only invoke MCP tools through Claude / an MCP host, no migration needed — the new params are all optional. You can now:
- Pass
status: "ENABLED"when callingdraft_campaign/draft_responsive_search_ad/create_ad_groupto start the entity serving immediately. Default remainsPAUSED. - Pass
bypass_require_dry_run: truetoconfirm_and_applywhen you intentionally want to skip the dry-run guard (one-shot override). - Pass
confirmed_twice: truetoconfirm_and_applyfor destructive plans flaggedrequires_double_confirm. - Pass
exempt_policy_violations: truetoconfirm_and_applyto request a Google ad policy exemption when a mutate is blocked by an exemptible violation. Required in practice for medical/health keywords, which tripHEALTH_IN_PERSONALIZED_ADS(and, for contraception terms,BIRTH_CONTROL). Only keywords can be exempted — responsive search ads cannot. - Read
status_after_applyandnext_action_hintfrom any draft / apply response to know whether a follow-upenable_entitycall is needed.
Library callers (Rust API)
// v0.2.x
DraftRsaParams { config, customer_id, ad_group_id, headlines, descriptions,
final_url, path1, path2 }
// v0.3.0
DraftRsaParams { config, customer_id, ad_group_id, headlines, descriptions,
final_url, path1, path2, status: None }
// ^^^^^^^^^^^^^ new field
// v0.2.x
DraftCampaignParams { ..., geo_target_ids, language_ids }
// v0.3.0
DraftCampaignParams { ..., geo_target_ids, language_ids, status: None }
// v0.2.x — defaulted to ENABLED (the only outlier)
create_ad_group(&config, cid, campaign_id, name, bid)
// v0.3.0 — default PAUSED (consistent with other write tools)
create_ad_group(&config, cid, campaign_id, name, bid, None)
// Equivalent v0.2.x behaviour:
create_ad_group(&config, cid, campaign_id, name, bid, Some(AdStatus::Enabled))
// v0.2.x
tools::confirm::confirm_and_apply(&config, plan_id, dry_run).await
// v0.3.0
tools::confirm::confirm_and_apply(&config, ConfirmApplyInput {
plan_id: plan_id.to_string(),
dry_run,
bypass_require_dry_run: false,
confirmed_twice: false,
}).await
Behaviour changes (no API change needed)
apply_recommendation/dismiss_recommendationnow actually work. v0.2.x routed them throughgoogleAds:mutateand got 400.require_dry_run = truenow blocks rather than just warning.create_pmax_campaign(start_paused = false)actually produces anENABLEDcampaign (v0.2.x ignored the param).
See CHANGELOG.md for the full breakdown.
Development
cargo build # Build debug
cargo build --release # Build release binary
cargo test # Unit + wiremock integration tests (no credentials needed)
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --check
The tests/integration_test.rs suite requires real Google Ads test
credentials (GOOGLE_ADS_TEST_* env vars); the rest of the test files
(13 wiremock-driven suites) run without any credentials.