Skip to main content
Glama
konkomaji

Google Marketing MCP

by konkomaji
README.md
# 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

A3.5/5.0

Scored across 94 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count2/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues