Google Marketing MCP
# Google Marketing MCP
A **full-capability** Model Context Protocol server that unifies **Google
Ads + Tag Manager + Search Console + Analytics (GA4)** behind one process —
reporting, monitoring, keyword research, campaign/tag/property creation, and
live mutations, all behind a dry-run safety gate. Built by merging three
already-proven standalone servers (Ads, GTM, GSC) plus a new GA4 module,
same skeleton throughout.
Plain MCP over stdio — no Claude-specific behavior. Works with **any**
MCP-compatible client: Claude Code, Claude Desktop, **OpenAI Codex CLI**,
and **Google Gemini CLI** (wiring for all four below).
> Built to run anywhere. `launch.py` auto-detects Python, builds its own
> virtualenv, installs dependencies, and self-heals if the repo is moved or
> copied to another machine. **You do not pre-install anything except Python.**
---
## 1. Quick start (any OS, any machine)
You need only **Python ≥ 3.10** on PATH.
```bash
python launch.py --setup # builds .venv + installs everything
python launch.py --check # per-domain tool counts + credential status
```
If `.venv` is missing, broken, or was built on a different computer,
`launch.py` rebuilds it automatically on the next run.
---
## 2. Tools — ~110, four domain-prefixed groups
Every tool name is prefixed so the four APIs never collide (`ads_list_accounts`
and `gtm_list_accounts` both exist — the prefix is what disambiguates them).
| Prefix | Domain | Tools |
|---|---|---|
| `ads_*` | Google Ads (22) | Accounts, `run_gaql` + performance reports, `monitor_account`, keyword research (Keyword Planner), campaign/ad-group/ad/keyword creation, bids/budgets, `list_recommendations`/`apply_recommendation`, `search_terms_report` |
| `gtm_*` | Tag Manager (29) | Accounts/containers/workspaces, generic entity CRUD (`list_entities`/`get_entity`/`create_entity`/`update_entity`/`delete_entity` across tags/triggers/variables/folders/templates/...), typed helpers (`create_ga4_event_tag`, `create_ads_conversion_tag`, `create_trigger`), `find_references`, versions + `publish_version`, `audit_container`, `tag_coverage` |
| `gsc_*` | Search Console (~20) | `list_sites`, flexible search-analytics queries, trend comparison, opportunity analysis (CTR gaps, striking-distance keywords, cannibalization, content decay), URL inspection, sitemaps |
| `ga4_*` | Analytics / GA4 (25) | Accounts/properties (create/update), data streams, conversion (Key) events, custom dimensions/metrics, audiences, **GA4↔Ads links**, Data API reporting + realtime |
Full per-tool docstrings are visible to any connected MCP client — this
table is the map, not the manual.
---
## 3. Credentials — fill in only what you need
Each domain is independent. Leave a whole block blank in `.env` and that
domain's tools still register (so `--check` shows the full count) but error
with an actionable message when actually called.
### Recommended: one unified token for all four domains
1. Google Cloud project → enable the Google Ads API, Tag Manager API,
Search Console API, and Analytics Admin + Data API.
2. Cloud Console → Credentials → **OAuth client ID** → type **Desktop app**.
3. `python launch.py --setup` then run:
```bash
.venv/Scripts/python -m google_marketing_mcp.auth_unified # Windows
.venv/bin/python -m google_marketing_mcp.auth_unified # macOS/Linux
```
One browser consent covering every scope this project uses → prints
`GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET` /
`GOOGLE_OAUTH_REFRESH_TOKEN` — paste all three into `.env`. Every domain's
client (`ads`, `gtm`, `gsc`, `ga4`) falls back to these automatically when
its own domain-specific vars are unset.
4. Still add `GOOGLE_ADS_DEVELOPER_TOKEN` separately (Ads UI → Tools &
Settings → API Center) — OAuth doesn't cover it, nothing does.
5. **This token only reaches products/accounts where that Google account is
already a user.** Add it under GTM Account User Management, Search
Console Users and permissions, and GA4 Property Access Management, same
as any human editor would need — this script gets you a token, not access.
This is the fastest path for a single owner/operator. For a multi-person
agency, or headless automation where a personal login is undesirable, use
the per-domain service-account setup below for GTM/GSC/GA4 instead (Google
Ads has no service-account path — it always needs a token from either
`auth_unified` above or `ads/auth.py` below).
### Google Ads (`ads_*`)
1. Google Cloud project → enable the **Google Ads API**.
2. Google Ads UI → Tools & Settings → API Center → apply for a **developer
token** (starts test-access; apply for **Basic** for production data).
3. Cloud Console → Credentials → **OAuth client ID** → type **Desktop app**.
4. `python launch.py --setup` then `.venv/Scripts/python -m google_marketing_mcp.ads.auth`
(Windows; `.venv/bin/python` elsewhere) → browser consent → copy the
printed `GOOGLE_ADS_REFRESH_TOKEN`.
5. Fill the `GOOGLE_ADS_*` block in `.env`. Set `GOOGLE_ADS_LOGIN_CUSTOMER_ID`
to your MCC id if you reach client accounts through a manager.
### Tag Manager / Search Console / GA4 — service-account alternative
Skip this if you used the unified token above. Otherwise these three share
**one service account** (`GOOGLE_APPLICATION_CREDENTIALS` in `.env`) —
create it once, add its email under each product's own permissions page:
1. Cloud Console → IAM & Admin → Service Accounts → create one → download
the JSON key. Point `GOOGLE_APPLICATION_CREDENTIALS` at it.
2. **GTM**: add the service account email under *Admin → Account User
Management* on each account, with Edit (and Publish if you'll publish).
3. **Search Console**: add it under *Settings → Users and permissions* on
each property — **Full**, not Restricted, or URL Inspection is blocked.
4. **GA4**: add it under *Admin → Property Access Management* on each
property — **Editor**, not Viewer, or every `ga4_*` write 403s. (This
exact mistake — Viewer-only, write rejected — is why this project has a
dedicated error message for it; see `ga4/client.py`.)
### Verify everything
```bash
python launch.py --check
```
Prints tool count per domain and `OK` / `MISSING <vars>` per domain — not
all-or-nothing.
---
## 4. Connect a client
`launch.py` is the universal entry point — every client below points at the
same file with any system Python.
### Claude Code
```bash
claude mcp add google-marketing -- python /ABSOLUTE/PATH/TO/google-marketing-mcp/launch.py
```
### Claude Desktop (`claude_desktop_config.json`)
```json
{
"mcpServers": {
"google-marketing": {
"command": "python",
"args": ["C:\\ABSOLUTE\\PATH\\TO\\google-marketing-mcp\\launch.py"]
}
}
}
```
(Credentials come from `.env` in the repo; omit `env` here unless you want
to pass them inline instead.)
### OpenAI Codex CLI
Codex reads MCP servers from `~/.codex/config.toml` (or `.codex/config.toml`
in the project):
```toml
[mcp_servers.google-marketing]
command = "python"
args = ["/ABSOLUTE/PATH/TO/google-marketing-mcp/launch.py"]
```
Or via the CLI: `codex mcp add google-marketing -- python /ABSOLUTE/PATH/TO/google-marketing-mcp/launch.py`
(flag name may differ by Codex CLI version — check `codex mcp --help`).
### Google Gemini CLI
Gemini reads MCP servers from `~/.gemini/settings.json` (or
`.gemini/settings.json` in the project) under `mcpServers`, same shape as
Claude Desktop:
```json
{
"mcpServers": {
"google-marketing": {
"command": "python",
"args": ["/ABSOLUTE/PATH/TO/google-marketing-mcp/launch.py"]
}
}
}
```
Or via the CLI: `gemini mcp add google-marketing python /ABSOLUTE/PATH/TO/google-marketing-mcp/launch.py`.
On Windows use the full path with `python.exe`; on macOS/Linux use `python3`
if `python` isn't aliased.
---
## 5. Safety model
- **Dry-run by default**, per domain. Every mutation runs as a validate/preview
call unless the tool is called with `confirm=true`.
- **Read-only kill switch**, per domain: `{ADS,GTM,GSC,GA4}_READ_ONLY=true`
blocks all mutations in that domain.
- **Publish is gated separately** in GTM (`GTM_ALLOW_PUBLISH`) — it pushes a
container to every live visitor instantly, with no staging step.
- **GA4 deletes/archives always require `confirm=true` explicitly**, on top
of the dry-run gate — there's no preview for an archive and GA4 has no
undo for one.
- **PAUSED by default.** New Ads campaigns and ads are created PAUSED.
- Every mutation response includes a `note` stating what happened
(DRY-RUN vs APPLIED) and why.
## 6. Error handling
- **Google Ads**: the official `google-ads` gRPC SDK retries transient
UNAVAILABLE/DEADLINE_EXCEEDED errors internally. Application errors
(`GoogleAdsException`) are flattened into a readable `code: message @ field`
string — never a raw proto dump.
- **GTM / GSC / GA4** (plain REST): every call routes through a shared retry
helper (`_retry.py`) that retries network exceptions and HTTP 429/5xx with
exponential backoff + jitter (honoring `Retry-After` on 429) before
surfacing an error. Non-transient errors (400/401/403/404/409) are never
retried — each domain's `client.py` has a `_explain()` that turns the raw
status into the specific, actionable fix (which permission to add, which
id format is expected, which page to check), not a generic HTTP message.
---
## 7. Layout
```
google-marketing-mcp/
├── launch.py # universal self-bootstrapping entry point
├── pyproject.toml
├── .env.example # all 4 domains' vars, prefixed
└── google_marketing_mcp/
├── __main__.py # `--check`: per-domain tool count + creds
├── app.py # ONE shared MCP instance, combined instructions
├── server.py # imports app + all 4 domains' tools
├── _retry.py # shared backoff for the REST domains
├── ads/ config.py client.py helpers.py tools/*.py (Google Ads SDK)
├── gtm/ config.py client.py helpers.py tools/*.py (REST)
├── gsc/ config.py client.py helpers.py tools/*.py (REST)
└── ga4/ config.py client.py helpers.py tools/*.py (REST)
```
Each domain keeps its own `config.py`/`client.py`/safety flags — merging
only shares the MCP instance and the assembly point, not the auth or error
logic, so a credential problem in one domain never affects another.
---
## 8. Troubleshooting
| Symptom | Fix |
|---|---|
| `did not find executable .../python.exe` | `.venv` was built elsewhere — run `python launch.py` again; it rebuilds automatically. |
| `No Python >= 3.10 found` | Install Python 3.10+ and ensure it's on PATH. |
| `MISSING credentials: ...` for one domain | Fill that domain's block in `.env` (§3). Other domains still work. |
| Mutation "did nothing" | Dry-run gate — re-call with `confirm=true`. |
| GA4 write 403s | Service account has Viewer, not Editor, on that property — Admin → Property Access Management. |
| GTM 409 Conflict | Workspace is behind the live container — call `gtm_sync_workspace`. |
| GTM publish refused | `GTM_ALLOW_PUBLISH=true` needed in addition to `confirm=true`. |
---
## 9. Related
This project also ships a **Google Ads optimization skill** (account
structure, bidding-strategy selection, conversion-tracking audit checklist,
scaling rules) — see `.claude/skills/` in the parent workspace. The skill
and this server are independent; the skill's playbook cross-references this
server's tool names.
---
Built by **Konko Maji** ([@konkomaji](https://github.com/konkomaji)).
TDQS
Scored across 94 tools
Tools are grouped by clear product prefixes (ads_, gtm_, gsc_, ga4_) with a consistent resource+action structure, making selection unambiguous. Even where convenience wrappers overlap generic tools (e.g., gtm_create_entity vs specialized GTM creators), the descriptions explicitly delineate when to use which.
The dominant <prefix>_<verb>_<noun> pattern is very consistent, with clear get/list/create/update/delete/run verbs. Minor deviations exist: several read-only/report tools use noun-only names (e.g., ads_campaign_performance, gtm_workspace_status, gsc_top_queries), and GA4 reports use run_ while Ads/GSC search analytics do not.
94 tools is far beyond the 25+ threshold, even for a server spanning four Google products. Many convenience wrappers (specialized GTM tag creators, GSC insight shortcuts) duplicate generic tools like gtm_create_entity or gsc_search_analytics, and the sheer surface area creates a heavy context burden for agents.
The set covers the full end-to-end marketing lifecycle: GA4 property/data stream setup, GTM container and tag publishing, Google Ads campaign/ad group/ad creation, Search Console insights, and cross-product linking. Minor gaps exist (no keyword removal/update in Ads, no GA4 data stream getter or audience update/delete, no GSC property creation), but generic query tools partially compensate.