Install
$ agentstack add mcp-fgribreau-mcp-google-ads ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
mcp-google-ads
Pilot Google Ads campaigns from Claude with built-in safety guardrails
[](https://crates.io/crates/mcp-google-ads) [](LICENSE) [](https://rust-lang.org) [](https://modelcontextprotocol.io) [](https://developers.google.com/google-ads/api)
Sponsors
France-Nuage
Sovereign French cloud for your ad data pipelines. EU data residency.
Hook0
Stream Google Ads conversions as signed webhooks. Self-hosted option.
Natalia
AI voice agent qualifies the leads your ads bring in. 24/7 follow-up.
Netir
Hire vetted French freelance ad ops experts via mentored marketplace.
NoBullshitConseil
No-bullshit tech advisory. Marketing tech strategy for founders.
Qualneo
Qualiopi LMS for French training orgs running ad-funded acquisition.
Recapro
Sovereign AI that transcribes client ad reviews & drafts the report.
> Interested in sponsoring? [Get in touch](mailto:rust@fgribreau.com)
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
- 47 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.
| Variable | Default | Description | |----------|---------|-------------| | GOOGLE_ADS_DEVELOPER_TOKEN | required | Google Ads API developer token | | GOOGLE_ADS_CUSTOMER_ID | required | Target account ID (e.g. 123-456-7890) | | GOOGLE_ADS_CREDENTIALS_PATH | ~/.mcp-google-ads/credentials.json | OAuth2 credentials JSON | | GOOGLE_ADS_TOKEN_PATH | ~/.mcp-google-ads/token.json | Refresh token JSON | | GOOGLE_ADS_LOGIN_CUSTOMER_ID | | MCC manager account ID (optional) | | GOOGLE_ADS_MAX_DAILY_BUDGET | 50.0 | Maximum daily budget allowed per campaign | | GOOGLE_ADS_MAX_BID_INCREASE_PCT | 100 | Maximum bid increase percentage | | GOOGLE_ADS_REQUIRE_DRY_RUN | true | Default dryrun to true on confirmand_apply | | GOOGLE_ADS_AUDIT_LOG | ~/.mcp-google-ads/audit.log | Path for mutation audit log | | GOOGLE_ADS_BLOCKED_OPS | | Comma-separated list of blocked operations | | GOOGLE_ADS_READ_ONLY | false | Disable all write tools |
Tools
Read (17 tools)
| Tool | Description | |------|-------------| | health_check | Check server connectivity and configuration | | list_accounts | List all accessible Google Ads accounts | | get_account_info | Account details (currency, timezone, status) | | get_campaign_performance | Campaign metrics (cost, clicks, conversions, CPA) | | get_ad_performance | Ad-level metrics with headlines and descriptions | | get_keyword_performance | Keyword metrics with quality scores | | get_search_terms | Actual user queries that triggered ads | | get_negative_keywords | List campaign negative keywords | | run_gaql | Execute arbitrary GAQL queries (json/table/csv) | | search_geo_targets | Find location IDs for geo-targeting | | get_geo_performance | Performance breakdown by location | | list_recommendations | Active Google Ads recommendations | | list_extensions | List campaign extensions (sitelinks, callouts, snippets) | | discover_keywords | Keyword ideas from seed keywords (Keyword Planner) | | get_keyword_forecasts | Historical keyword metrics for forecasting | | get_policy_issues | Disapproved or limited ads and policy violations | | get_conversion_actions | Conversion actions configured in the account |
Write (31 tools)
All write tools return a preview. Call confirm_and_apply with dry_run=false to execute.
| Tool | Description | |------|-------------| | draft_campaign | Create campaign + ad group + keywords. Defaults to PAUSED; pass status: "ENABLED" to opt out. | | update_campaign | Modify budget, bidding, targeting | | exclude_geo_target | Exclude a location from a campaign (negative location criterion) | | remove_geo_target | Remove a positively-targeted location from a campaign (destructive) | | draft_responsive_search_ad | Create RSA (3-15 headlines, 2-4 descriptions). Defaults to PAUSED; pass status: "ENABLED" to opt out. | | create_ad_group | Create ad group in existing campaign. Defaults to PAUSED; pass status: "ENABLED" to opt out. | | update_ad_group | Modify ad group name or CPC bid | | draft_keywords | Add keywords with match types | | remove_keywords | Remove keywords from ad group (destructive) | | add_negative_keywords | Block irrelevant searches | | remove_negative_keywords | Remove negative keywords (destructive) | | draft_sitelinks | Sitelink extensions | | create_callouts | Callout extensions | | create_structured_snippets | Structured snippet extensions | | remove_extension | Remove a campaign extension (destructive) | | create_pmax_campaign | Performance Max campaign with text assets | | create_custom_audience | Remarketing / customer match audiences | | add_audience_targeting | Target audiences (TARGETING/OBSERVATION) | | create_portfolio_bidding_strategy | Portfolio bidding (CPA, ROAS, impression share) | | create_conversion_action | Create a conversion action for server-side click uploads (UPLOADCLICKS, gclid-based) | | set_conversion_action_primary_status | Mark a conversion action primary or secondary (demote a value-0 signup out of bidding) | | update_keyword_bid | Modify keyword CPC bid | | upload_image_asset | Upload base64-encoded image | | upload_text_asset | Create reusable text asset | | set_campaign_schedule | Ad scheduling / dayparting | | apply_recommendation | Apply a Google recommendation | | dismiss_recommendation | Dismiss a recommendation | | pause_entity | Pause campaign/ad group/ad/keyword | | enable_entity | Enable paused entity | | remove_entity | Permanently remove entity (destructive) | | confirm_and_apply | Execute a previewed change (dryrun=true default) |
Safety Guardrails
| Guard | Behavior | |-------|----------| | Two-step confirm | Every write returns a plan preview; must call confirm_and_apply to execute | | Dry-run default | confirm_and_apply defaults to dry_run=true — must explicitly set false | | Budget cap | Rejects campaigns exceeding GOOGLE_ADS_MAX_DAILY_BUDGET | | Bid limit | Rejects bid increases exceeding GOOGLE_ADS_MAX_BID_INCREASE_PCT | | Broad + Manual CPC | Blocks BROAD match with MANUALCPC (burns budget) | | PAUSED by default | New campaigns/ad groups/ads created PAUSED. Response carries status_after_apply + next_action_hint describing how to flip to ENABLED via the enable_entity MCP tool — zero UI step needed. | | require_dry_run hard guard | When GOOGLE_ADS_REQUIRE_DRY_RUN=true, calling confirm_and_apply with dry_run=false returns Err(DryRunRequired) BEFORE any HTTP traffic. Pass bypass_require_dry_run=true for a one-shot override. | | Double-confirm hard guard | Destructive ops flagged requires_double_confirm need confirmed_twice=true or return Err(DoubleConfirmRequired). | | MutateOperation whitelist | Unknown top-level operation keys (typos, recommendation ops mis-routed) are rejected client-side BEFORE the HTTP call with a clear error pointing at the correct tool. | | Blocked operations | Configure GOOGLE_ADS_BLOCKED_OPS to disable specific tools | | Read-only mode | Set GOOGLE_ADS_READ_ONLY=true to disable all writes | | Audit logging | Every mutation logged with timestamp, operation, changes, dryrun status |
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
| | mcp-google-ads (this) | adloop | mikdeangelis | grantweston | Official Google | |---|---|---|---|---|---| | Language | Rust | Python | Python | Python | Python | | API version | v23 | v23 | v23 | v21 | v23 | | Total tools | 47 | 43 | 52 | 63 | 2 | | Write tools | 30 | 16 | 26 | 25 | 0 | | Tests | 287 | partial | 0 | 0 | N/A |
Features
| Feature | this | adloop | mikdeangelis | grantweston | Official | |---------|------|--------|-------------|-------------|----------| | Campaign creation | Search + PMax | Search | Search + PMax | 7 types | - | | RSA ads | yes | yes | yes | yes | - | | Ad group CRUD | yes | yes | yes | yes | - | | Keywords (positive + negative) | yes | yes | yes | yes | - | | Keyword removal | yes | - | yes | yes | - | | Extensions (sitelinks, callouts, snippets) | yes | yes | yes | yes | - | | Extension listing & removal | yes | - | yes | yes | - | | Audiences | yes | - | - | yes | - | | Bid adjustments | yes | - | - | yes | - | | Geo targeting (set/remove) | yes | via update | yes | hardcoded US | - | | Ad scheduling | yes | - | yes | yes | - | | Asset upload (image/text) | yes | - | yes | yes | - | | Recommendations (apply/dismiss) | yes | - | yes | yes | - | | Keyword Planner (ideas + forecasts) | yes | yes | yes | - | - | | Conversion tracking | yes | - | yes | - | - | | Policy issues (disapproved ads) | yes | - | yes | - | - | | Arbitrary GAQL queries | yes | yes | - | - | yes | | GA4 integration | - | yes | - | - | - |
Safety & Reliability
| Guard | this | adloop | mikdeangelis | grantweston | Official | |-------|------|--------|-------------|-------------|----------| | Two-step confirm (draft → review → apply) | yes | yes | - | - | N/A | | Dry-run default | yes | yes | - | - | N/A | | Budget cap | yes | yes (50EUR) | - | - | N/A | | Bid increase limit | yes | - | - | - | N/A | | Broad + Manual CPC blocker | yes | yes | - | - | N/A | | Campaigns created PAUSED | yes | yes | default | ENABLED | N/A | | Audit logging (JSON) | yes | yes | - | - | N/A | | Read-only mode | yes | - | - | - | N/A | | Blocked operations list | yes | - | - | - | N/A | | Input validation (char limits, URLs) | yes | yes | Pydantic | partial | N/A | | Unit test coverage | 210 tests | partial | 0 | 0 | N/A | | Binary size / startup | 10.8 MB / instant | Python | Python | Python | Python |
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_group to start the entity serving immediately. Default remains PAUSED.
- 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 flagged requires_double_confirm.
- Read
status_after_applyandnext_action_hintfrom any draft / apply
response to know whether a follow-up enable_entity call 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 through googleAds:mutate and got 400.
require_dry_run = truenow blocks rather than just warning.create_pmax_campaign(start_paused = false)actually produces an
ENABLED campaign (v0.2.x ignored the param).
See [CHANGELOG.md](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.
License
[MIT](LICENSE)
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: FGRibreau
- Source: FGRibreau/mcp-google-ads
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.