An MCP server for Google Tag Manager + Google Analytics 4 with tiered safety controls.
Existing servers cover slices of this surface: Google's official GA4 server is read-only, and GTM servers expose the API without a safety model. gtm-ga4-mcp is built for the full surface — read, write, and admin operations — with safeguards you opt into deliberately.
Every tool belongs to a tier. Higher tiers are off by default and enforced twice:
| Tier | Examples | Default | Enable with |
|---|---|---|---|
| Read | list, get, reports, metadata | ✅ on | — |
| Write | create/update tags, triggers, custom dimensions | ❌ off | --allow-write or GTM_GA4_MCP_ALLOW_WRITE=1 |
| Destructive | delete, publish, user permissions | ❌ off | --allow-destructive or GTM_GA4_MCP_ALLOW_DESTRUCTIVE=1 |
- Registration gate — tools above your tier are never registered, so they're invisible to the model (not just erroring at call time).
- Token gate — the OAuth scopes requested from Google are derived from the same tier. A read-only process asks for
tagmanager.readonly+analytics.readonlyand holds a token that cannot mutate anything, even if the application code misbehaves.
Individual tools can also be disabled by name: --deny gtm_get (repeatable) or GTM_GA4_MCP_DENY=tool_a,tool_b.
Beyond the two gates, mutations carry their own guards:
- Write tools default to dry-run — they echo the exact API request without sending it; execution requires an explicit
dry_run: false. - Destructive tools are two-phase — the first call changes nothing and returns a one-time
confirm_tokenplus a plain-language summary; only a second call with that token executes. Tokens are fingerprinted to the exact operation (a confirmation for deleting tag X can never authorize deleting tag Y), single-use, and expire in 10 minutes. This follows the Multi Round-Trip Requests pattern from MCP spec 2026-07-28 and works on every client. - Blast-radius exclusions — accounts, containers, and GA4 properties can never be deleted through this server, by design.
Read tier (always on):
| Tool | What it does |
|---|---|
gtm_list |
List GTM entities level by level (accounts → containers → workspaces → tags/triggers/variables/templates/permissions/…), trimmed summaries |
gtm_get |
Full JSON for one GTM entity by path (including container versions) |
ga4_account_summaries |
Every GA4 account + property you can access — the entry point |
ga4_property_get |
One property's full configuration |
ga4_admin_list |
Data streams, key events, custom dimensions/metrics, Ads/Firebase links |
ga4_run_report |
GA4 report over a date range, rows as clean dicts |
ga4_run_realtime_report |
Last-30-minutes activity (verify events are firing) |
ga4_metadata |
Discover dimension/metric API names (standard + custom), searchable |
Write tier (--allow-write), all dry-run by default:
| Tool | What it does |
|---|---|
gtm_create |
Create tags, triggers, variables, folders, templates, clients, transformations, zones, workspaces, environments |
gtm_update |
Replace a GTM entity (full-body update with optional optimistic-lock fingerprint) |
ga4_admin_create |
Create custom dimensions/metrics, key events, data streams |
ga4_admin_update |
Patch GA4 entities or property settings (partial update via update mask) |
Destructive tier (--allow-destructive), all two-phase confirmed:
| Tool | What it does |
|---|---|
gtm_delete |
Delete workspace entities, workspaces, versions, environments (never accounts/containers) |
gtm_publish |
Compile a workspace into a version and publish it LIVE (aborts on compile errors) |
gtm_permissions |
Grant / update / revoke GTM account access |
ga4_admin_delete |
Archive custom dimensions/metrics, delete key events/data streams (never properties) |
Design choices worth knowing: ~8 consolidated tools instead of ~120 endpoint wrappers (smaller agent context, deterministic alphabetical ordering for prompt caching), list results are trimmed summaries with gtm_get for deep dives, and all GTM calls flow through a rate limiter tuned to the GTM API's ~15 requests/minute default quota with backoff on 429/5xx.
1. Enable APIs in a Google Cloud project: Tag Manager API, Analytics Admin API, Analytics Data API.
2. Authenticate with Application Default Credentials. Log in with the scopes matching the tier you run — this is the token-level gate, so a read-only login is a hard guarantee:
Read-only (default):
gcloud auth application-default login --scopes=https://www.googleapis.com/auth/tagmanager.readonly,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platformEverything, for a full read/write/destructive session:
gcloud auth application-default login --scopes=https://www.googleapis.com/auth/tagmanager.readonly,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/tagmanager.edit.containers,https://www.googleapis.com/auth/tagmanager.edit.containerversions,https://www.googleapis.com/auth/analytics.edit,https://www.googleapis.com/auth/tagmanager.delete.containers,https://www.googleapis.com/auth/tagmanager.publish,https://www.googleapis.com/auth/tagmanager.manage.users,https://www.googleapis.com/auth/cloud-platform3. Add the server. Claude Code (read-only):
claude mcp add gtm-ga4 -- uvx --from git+https://github.com/kb223/gtm-ga4-mcp gtm-ga4-mcpAppend --allow-write or --allow-destructive to that command to enable higher tiers.
Or any MCP client via .mcp.json / Claude Desktop config:
{
"mcpServers": {
"gtm-ga4": {
"command": "uvx",
"args": ["--from", "git+https://github.com/kb223/gtm-ga4-mcp", "gtm-ga4-mcp"]
}
}
}PyPI package coming with v1.0 (uvx gtm-ga4-mcp).
Google blocks Tag Manager / Analytics scopes on gcloud's shared default OAuth client, so the plain gcloud auth application-default login above may fail with "This app tried to access sensitive info in your Google Account." The fix — same as Google documents for their own analytics-mcp — is a two-minute OAuth client of your own:
- In a Google Cloud project with the three APIs enabled, open APIs & Services → OAuth consent screen: user type External, publishing status Testing, and add your own Google account as a test user.
- APIs & Services → Credentials → Create Credentials → OAuth client ID → Desktop app, then download the client JSON.
- Re-run the login with your client:
gcloud auth application-default login --client-id-file=path/to/client_secret.json --scopes=<same scopes as above>Heads-up: while the consent screen is in Testing mode, Google expires the refresh token after ~7 days, so expect to re-run the login weekly (or publish the app and click through the unverified-app warning).
Ask your agent things like:
- "List my GTM accounts, then show me every tag in the main container's default workspace."
- "Which GA4 properties do I have access to, and what custom dimensions does property 123456 define?"
- "Run a report on sessions and conversions by default channel group for the last 28 days."
- "Is the
purchaseevent firing right now?" - (write tier) "Create a
lead_typeevent-scoped custom dimension on property 123456." — you'll see the dry-run payload first - (destructive tier) "Delete the paused tag called Old Pixel." — you'll get a summary + confirmation token before anything happens
uv sync
uv run pytest
uv run ruff check .
npx @modelcontextprotocol/inspector uv run gtm-ga4-mcp # interactive testingCI runs the test suite plus an MCP Inspector CLI smoke test (tools/list over stdio, no credentials needed).
v0.2 — write tier✅ shippedv0.3 — destructive tier✅ shipped- v1.0: PyPI, MCP registry listing, MCPB bundle
MIT