Google Ads MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Google Ads MCP Servershow me campaign performance for the last 30 days"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Ads MCP Server
MCP (Model Context Protocol) server that integrates with the Google Ads API. Lets AI agents (Claude, Cursor, etc.) query campaigns, metrics, audiences, and geography, and analyze budgets — all through natural language.
Two operating modes
Mode | Who uses it | Auth | Google refresh token |
stdio | Just you (Claude Desktop / Cursor local) | None — local subprocess | Single, in |
HTTP multi-tenant | Team / remote access | Native MCP OAuth 2.1 — each user authenticates with their own Google account | One per user, encrypted in local SQLite |
Pick stdio for personal use (simpler). Pick HTTP when multiple users (with different Google accounts) need to share the same server.
Related MCP server: Altaviz
Available tools
Tool | Description |
| Lists accessible accounts (useful under an MCC) |
| Campaigns with KPIs (cost, clicks, CTR, ROAS, CPA) |
| Ad groups with performance grouped by campaign |
| Individual ads with final URL |
| Keywords with performance |
| Search terms that triggered ads (filters: |
| Aggregated or daily metrics (campaign or account) |
| Generic breakdown by |
| Performance by location |
| Performance by audience (includes CUSTOM_INTENT) |
| "Budget drains" — high cost + low ROAS |
| Executive summary, period comparison, alerts |
| Detailed change history (CREATE/UPDATE/REMOVE) with per-field diff — last 30 days |
| Lightweight indicator of recent changes per resource (no diff) |
All accept: flexible date ranges (last_7_days, last_30_days, this_month, last_month, custom YYYY-MM-DD), output format (markdown or json), and optional customer_id.
The synthesis tools google_ads_find_wasted_spend and google_ads_diagnose_campaign_health also accept a language parameter ("pt" or "en", default "pt") that controls the stopword set used to group search terms by their first meaningful word.
Setup A — stdio mode (single-tenant)
1. Prerequisites
Python 3.11+
Google Ads account
Project in Google Cloud Console with the Google Ads API enabled
Approved Developer Token (how to get one)
2. Installation
git clone <repo>
cd google-ads-mcp
uv sync3. "Desktop" OAuth Client in GCP
APIs & Services → Credentials → Create Credentials → OAuth client IDApplication type: Desktop app
Save the
Client IDandClient secret
4. Refresh token
Interactive wizard:
uv run python scripts/get_refresh_token.pyPaste the Client ID/Client secret when prompted; it opens the browser, you log in, and copy the resulting refresh_token.
5. .env
GOOGLE_ADS_DEVELOPER_TOKEN=...
GOOGLE_ADS_CLIENT_ID=...apps.googleusercontent.com
GOOGLE_ADS_CLIENT_SECRET=...
GOOGLE_ADS_REFRESH_TOKEN=...
GOOGLE_ADS_CUSTOMER_ID=123-456-7890
# Optional, if the account above is under an MCC:
# GOOGLE_ADS_LOGIN_CUSTOMER_ID=999-888-77776. Client configuration
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"google-ads": {
"command": "uv",
"args": ["run", "python", "main.py", "--transport", "stdio"],
"cwd": "/path/to/google-ads-mcp"
}
}
}Cursor — .cursor/mcp.json:
{
"mcpServers": {
"google-ads": {
"command": "uv",
"args": ["run", "python", "main.py", "--transport", "stdio"],
"cwd": "/path/to/google-ads-mcp"
}
}
}uv run loads the .env automatically via python-dotenv.
Setup B — HTTP multi-tenant mode
How it works
Claude Desktop ──(MCP OAuth: tokens issued by us)──▶ our MCP (AS+RS)
│
└──(Google OAuth: user's refresh_token)──▶ Google Ads APIThere are two chained OAuth flows: the server is simultaneously an Authorization Server (issues its own JWTs to Claude Desktop) and a Google OAuth Client (holds the encrypted refresh_token for the user's Google account). Desktop never sees the Google refresh_token.
UX in Claude Desktop:
User adds the MCP URL under Settings → Connectors (once).
Clicks "Connect" → browser opens → Google login →
adwordsconsent → returns connected.From then on, all calls are authenticated. Refresh is silent.
1. Prerequisites
Public domain with TLS (e.g.,
mcp.your-domain.com)Caddy (or Nginx) running on the host — will terminate TLS and reverse-proxy to
127.0.0.1:8000Docker + Docker Compose
2. Developer Token
Same as Setup A (step 1). It belongs to the MCP operator and is shared across all users.
3. "Web application" OAuth Client in GCP
⚠️ Separate client from the Desktop one used in Setup A.
Enable the Google Ads API if you haven't already.
Configure the OAuth consent screen:
User type: External
Add the scope
https://www.googleapis.com/auth/adwordsUnder "Test users" add the emails that will test (or click Publish — the
adwordsscope is restricted and Google may require verification).
Create Credentials → OAuth client ID:
Application type: Web application
Authorized redirect URIs:
https://mcp.your-domain.com/auth/callback(must match theMCP_PUBLIC_URLyou configure exactly)
Save the
Client IDandClient secret.
4. (Optional) Generate a JWT signing key
# Only if you want to pin the key (e.g., multiple replicas).
# Without this, GoogleProvider derives the key from the client_secret.
uv run python -c "import secrets; print(secrets.token_urlsafe(48))"5. .env
# MCP operator (shared)
GOOGLE_ADS_DEVELOPER_TOKEN=AbCdEf123...
# Web OAuth Client (step 3)
GOOGLE_OAUTH_WEB_CLIENT_ID=123456789-abc.apps.googleusercontent.com
GOOGLE_OAUTH_WEB_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxx
# Public URL — no trailing slash, with https
MCP_PUBLIC_URL=https://mcp.your-domain.com
# Optional: pinned key for signing MCP JWTs (step 4)
# OAUTH_JWT_SIGNING_KEY=Z9K1u7c-paste-the-output-of-secrets-token-urlsafe-48
# Server
MCP_TRANSPORT=http
MCP_HOST=0.0.0.0
MCP_PORT=8000Setup A variables (GOOGLE_ADS_CLIENT_ID, GOOGLE_ADS_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN, GOOGLE_ADS_CUSTOMER_ID) are not used in HTTP mode — you can remove or comment them out.
6. Host Caddy
mcp.your-domain.com {
reverse_proxy 127.0.0.1:8000
}7. Start
mkdir -p data
docker compose up -d --build
docker compose logs -f google-ads-mcp8. Verification
curl https://mcp.your-domain.com/.well-known/oauth-authorization-serverShould return JSON with issuer, authorization_endpoint, etc.
9. Connect from Claude Desktop
Settings → Connectors → Add custom connector → paste https://mcp.your-domain.com/mcp → click Connect → Google flow opens → log in → account authorized.
Done. Other users can do the same on their machines.
Environment variables
Common (both modes)
Variable | Description |
| Google Ads API developer token |
|
|
| HTTP port (default |
| HTTP host (default |
stdio mode
Variable | Required | Description |
| ✅ | Desktop OAuth Client ID |
| ✅ | Desktop OAuth Client Secret |
| ✅ | Refresh token generated by |
| ⚠️ | Default account ID (can be passed per call) |
| — | MCC ID, if applicable |
HTTP multi-tenant mode
Variable | Required | Description |
| ✅ | Public URL, no trailing |
| ✅ | Web OAuth Client ID |
| ✅ | Web OAuth Client Secret |
| — | Optional. If absent, |
Architecture
google-ads-mcp/
├── main.py # Entry point — stdio or HTTP (mcp.http_app)
├── src/
│ ├── server.py # 14 MCP tools (fastmcp) + conditional GoogleProvider
│ ├── client.py # REST/GAQL client (Google Ads API v24)
│ ├── auth.py # GoogleAdsAuth — env vars (stdio) or injected access_token (HTTP)
│ └── formatters.py # Markdown / JSON
├── scripts/
│ ├── get_refresh_token.py # OAuth wizard (stdio mode)
│ └── update_geo_targets.py # Updates src/data/geo_targets.json
├── Dockerfile
├── docker-compose.yml # Exposes 127.0.0.1:8000 (host Caddy handles TLS)
├── pyproject.toml
└── .env.exampleData flow (HTTP mode): tool call → JWT verified by GoogleProvider (token-swap JTI → decrypted upstream Google access_token) → get_access_token().token → GoogleAdsAuth.for_access_token → Google Ads REST v24 → formatter. The Google refresh_token stays encrypted in GoogleProvider's internal key-value store; refresh is transparent when the access_token expires.
Usage examples (natural language)
"List active campaigns from the last 7 days"
"Which campaign has the worst ROAS in the last month?"
"Show daily metrics for campaign 123456 over the last 14 days"
"Which keywords are spending a lot without converting?"
"Which search terms appeared most in the last 30 days?"
"Show performance by state in the last month"
"Which audiences are performing best?"
"Analyze the account's budget and identify drains"
"Give me an executive summary comparing this week to the previous one"Operations
Logs
docker compose logs -f google-ads-mcpBackup
GoogleProviderpersists DCR clients and encrypted Google refresh_tokens in its own key-value store (default: file inside the container'splatformdirs— to survive restarts, mount the directory on the host).OAUTH_JWT_SIGNING_KEY— if you change it (or if you're using the default derivation and changeGOOGLE_OAUTH_WEB_CLIENT_SECRET), all users must reconnect.
Rebuild after code changes
docker compose up -d --buildUpdate the geo targets table
uv run python scripts/update_geo_targets.py # default: BR
uv run python scripts/update_geo_targets.py --country BR AR MX
git add src/data/geo_targets.json && git commitTroubleshooting
Symptom | Likely cause | Fix |
| URI in GCP ≠ | Check character by character (https, no trailing slash in |
"Google did not return a refresh_token" in the callback | User previously authorized the app (Google only returns | Ask them to revoke at myaccount.google.com/permissions and reconnect |
| MCP JWT expired or | Desktop refreshes on its own; if it persists, reconnect from Connectors |
Tool returns | User doesn't have access to the requested | MCC auto-resolve tries to sort it out; if it fails, check access in Google Ads |
Docker healthcheck failing | Missing |
|
| Broken DNS/TLS or Caddy not routing |
|
Security
Credentials never in code — always in
.env(already in.gitignore).Host Caddy — terminates TLS. The container only listens on
127.0.0.1:8000, not reachable directly from the internet.Rate limiting — configure on the host Caddy, not in the app.
Container runs as a non-root user.
Google refresh_tokens encrypted at rest by
GoogleProvider's internal key-value store.Short-lived MCP JWTs with transparent upstream Google refresh and MCP refresh token rotation (OAuth 2.1) — all managed by
GoogleProvider.Treat
OAUTH_JWT_SIGNING_KEY(if set) andGOOGLE_OAUTH_WEB_CLIENT_SECRETas critical secrets (vault/secret manager in production).
This server cannot be deployed
Maintenance
Related MCP Connectors
Conversational access to advertising performance data, creative analysis, and campaign insights
Conversational access to advertising performance data, creative analysis, and campaign insights
- AdCrunchOAuthdev.adcrunch
Ask AI about your ads — query Meta, TikTok, and Google Ads performance in natural language.
- mcp-serverOAuthco.flyweel
Access Google & Meta Ads data via AI. Analyse campaign performance in seconds.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables natural language querying of Amazon Advertising data including Sponsored Products, Brands, Display, DSP reports, and AMC SQL workflows, with live data from the Amazon Ads API.23MIT
- FlicenseNot gradedqualityBmaintenanceEnables monitoring and managing multi-platform media buying accounts via natural language, detecting anomalies like creative fatigue and spend spikes, with AI-powered recommendations and a human-in-the-loop approval queue.-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to browse and query Facebook Ads data including ad accounts, campaigns, ad sets, and performance insights via natural language.13 npmMIT

AdCrunch MCP Serverofficial
FlicenseNot gradedqualityDmaintenanceEnables querying ad campaign performance and setup across Meta, TikTok, and Google Ads using natural language through AI agents.1-