|
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
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.
- 48 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
git clone https://github.com/FGRibreau/mcp-google-ads.git
cd mcp-google-ads
cargo build --release# Generate OAuth2 refresh token
./scripts/generate_token.sh ~/.mcp-google-ads/credentials.jsonAdd 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"
}
}
}
}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 dry_run to true on confirm_and_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 |
| 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 |
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) |
set_campaign_geo_target_type |
Set a campaign's geo target type (PRESENCE vs PRESENCE_OR_INTEREST) for positive and/or negative targeting |
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 and optional per-keyword final_url (landing page override) |
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 (UPLOAD_CLICKS, 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 (dry_run=true default) |
| 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 MANUAL_CPC (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, dry_run status |
- Create a Manager Account (MCC) at ads.google.com
- In the MCC, create a Test Account (Settings → Sub-account settings)
- Note both IDs
- In MCC → Tools & Settings → API Center
- Request a developer token (test access is sufficient)
- Google Cloud Console → Enable Google Ads API
- Credentials → Create → OAuth 2.0 Client ID → Desktop App
- Download JSON to
~/.mcp-google-ads/credentials.json
./scripts/generate_token.sh| mcp-google-ads (this) | adloop | mikdeangelis | grantweston | Official Google | |
|---|---|---|---|---|---|
| Language | Rust | Python | Python | Python | Python |
| API version | v23 | v23 | v23 | v21 | v23 |
| Total tools | 48 | 43 | 52 | 63 | 2 |
| Write tools | 32 | 16 | 26 | 25 | 0 |
| Tests | 322 | partial | 0 | 0 | N/A |
| 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 | - | - | - |
| 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 | 322 tests | partial | 0 | 0 | N/A |
| Binary size / startup | 10.8 MB / instant | Python | Python | Python | Python |
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.
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. - Read
status_after_applyandnext_action_hintfrom any draft / apply response to know whether a follow-upenable_entitycall is needed.
// 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,
}).awaitapply_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.
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 --checkThe 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.

