Metadata-Version: 2.4
Name: tagmanager-mcp
Version: 0.3.0
Summary: MCP server for Google Tag Manager
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/jinchliu/tagmanager-mcp
Project-URL: Repository, https://github.com/jinchliu/tagmanager-mcp
Project-URL: Issues, https://github.com/jinchliu/tagmanager-mcp/issues
Keywords: mcp,google-tag-manager,gtm,analytics,llm
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp<2,>=1.28
Requires-Dist: google-api-python-client>=2.100
Requires-Dist: google-auth~=2.40
Requires-Dist: google-auth-httplib2>=0.2.0
Requires-Dist: cryptography<49
Provides-Extra: dev
Requires-Dist: mcp[cli]<2,>=1.28; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: nox; extra == "dev"
Dynamic: license-file

# tagmanager-mcp

An MCP (Model Context Protocol) server for Google Tag Manager.
Ask your AI assistant about your GTM setup — accounts, containers, tags,
triggers, variables, unpublished changes — and let it edit the workspace
draft: create, update and delete tags, triggers and variables, then version
and publish the container. Works from Claude Code, Claude Desktop, or any
MCP client.

## Why this one?

- **Authenticate once — no re-auth treadmill.** Auth is plain Google
  Application Default Credentials (ADC) with your own OAuth client: the
  refresh token does not expire, so you log in once and forget it. No hosted
  OAuth session that lapses every few days and demands another round of
  browser clicking.
- **No service account required.** The server runs as *you*, using the GTM
  permissions your Google account already has. There is no service-account
  JSON key to create, grant container access to, rotate, or accidentally
  commit.
- **Local and direct.** Runs on your machine over stdio; your GTM data flows
  straight between you and `tagmanager.googleapis.com`. No third-party proxy
  in the middle.
- **Built for LLM context windows.** GTM's raw tag JSON is enormous (a single
  GA4 event tag is easily hundreds of lines). `list_*` tools return slim
  skeletons; `get_*` tools fetch full detail only when asked.
- **Quota-aware by design.** The GTM API allows only 25 requests per 100
  seconds per project. The server retries rate limits (429/403) and server
  errors with exponential backoff, and self-throttles after the first hit.
  Errors come back as actionable messages, not raw stack traces.

## Tools

**Read**

| Tool | Purpose |
|---|---|
| `list_accounts` | GTM accounts you can access (optionally Google Tag accounts) |
| `list_containers` | Containers in an account |
| `list_workspaces` | Workspaces in a container |
| `get_workspace_status` | Unpublished changes and merge conflicts |
| `list_tags` / `get_tag` | Tags — skeleton list / full configuration |
| `list_triggers` / `get_trigger` | Triggers — skeleton list / full configuration |
| `list_variables` / `get_variable` | Variables — skeleton list / full configuration |

**Write**

| Tool | Purpose |
|---|---|
| `create_tag` / `create_trigger` / `create_variable` | Create an entity in the workspace draft |
| `update_tag` / `update_trigger` / `update_variable` | Merge partial changes into an entity |
| `delete_tag` / `delete_trigger` / `delete_variable` | Delete an entity (requires `confirm=true`) |

**Publish**

| Tool | Purpose |
|---|---|
| `list_versions` | Container version headers — skeleton list |
| `get_version` / `get_live_version` | One version / the currently live version, with slimmed contents |
| `create_version` | Snapshot the workspace into a version (consumes the workspace; returns `newWorkspacePath`) |
| `publish_version` | Publish a version live (requires `confirm=true`) |

The write safety model:

- **Editing and going live are separate.** Create/update/delete only touch
  the workspace draft; only `publish_version` changes what runs on the live
  site, and it needs `confirm=true`. You review changes in the GTM UI (or via
  `get_workspace_status` / `get_live_version`) before anything ships.
- **Updates are merge patches.** The model sends only the fields to change;
  the server re-reads the entity and submits its `fingerprint`, so a
  concurrent edit fails cleanly instead of being clobbered.
- **Deletes and publishing need explicit confirmation** (`confirm=true`) and
  are declared with `destructiveHint`. `create_version` is also destructive
  (it consumes the workspace) but does not gate on `confirm`.
- **No blind retries on writes.** Rate-limit rejections are retried (they
  happen before execution); ambiguous 5xx errors are not, so a create can
  never be silently duplicated.

## Prerequisites

- Python >= 3.10
- [pipx](https://pipx.pypa.io/stable/installation/)
- The [gcloud CLI](https://cloud.google.com/sdk/docs/install)
- A Google account with access to your GTM containers
- Any GCP project you can enable an API on (used only for quota attribution)

## Setup

**1. Install**

```bash
pipx install tagmanager-mcp
```

**2. Enable the Tag Manager API** on your quota project

```bash
gcloud services enable tagmanager.googleapis.com --project=YOUR_PROJECT
```

**3. Create a Desktop OAuth client**

Google blocks gcloud's built-in OAuth client for Tag Manager scopes
("This app is blocked"), so you bring your own:

- GCP Console → **Google Auth Platform → Clients → Create client** →
  Application type **Desktop app** → create, then download the JSON.
- On the **Audience** page, publish the app to **Production**. An app left
  in Testing status issues refresh tokens that expire after 7 days — the
  exact re-auth treadmill this project exists to avoid.

**4. Log in**

```bash
gcloud auth application-default login \
  --client-id-file=path/to/your-client.json \
  --scopes=https://www.googleapis.com/auth/tagmanager.readonly,https://www.googleapis.com/auth/tagmanager.edit.containers,https://www.googleapis.com/auth/tagmanager.edit.containerversions,https://www.googleapis.com/auth/tagmanager.publish,https://www.googleapis.com/auth/cloud-platform
gcloud auth application-default set-quota-project YOUR_PROJECT
```

Scopes are additive to what each tier needs: drop `tagmanager.publish` +
`tagmanager.edit.containerversions` for edit-only (no publishing), or also
drop `tagmanager.edit.containers` for a read-only setup. Tools outside your
granted scopes fail with a clear re-login hint while everything else keeps
working.

The browser will warn "Google hasn't verified this app" — it is your own
app; choose Advanced → Continue.

## Connect an MCP client

**Claude Code** starts from your shell and inherits its PATH, so the bare
command works:

```bash
claude mcp add gtm -- tagmanager-mcp
```

**Claude Desktop** (`claude_desktop_config.json`) is launched by the OS and
does *not* inherit your shell PATH, so give it the absolute path. Print it
with `which tagmanager-mcp`:

```json
{
  "mcpServers": {
    "gtm": {
      "command": "/Users/you/.local/bin/tagmanager-mcp"
    }
  }
}
```

## Example prompts

- "Which GTM accounts and containers do I have?"
- "How many tags are in the default workspace of container GTM-XXXXXXX, grouped by type?"
- "Which tags are paused?"
- "Show me the full config of the purchase tag and which triggers fire it."
- "Does the current workspace have unpublished changes? What changed?"
- "Find triggers that no tag references."
- "Pause every tag that fires on the checkout trigger."
- "Create a custom-event trigger for `sign_up` and a GA4 event tag that
  fires on it."

## Quota

The GTM API is tightly limited: **10,000 requests/day** and **0.25 QPS
(25 requests per 100-second window) per GCP project** — per-user quota
overrides do not raise it. Ordinary audit conversations fit comfortably;
avoid "every tag in every container" sweeps across many containers at once.

## Troubleshooting

- **"This app is blocked" during login** — you used gcloud's default OAuth
  client; pass your own with `--client-id-file` (Setup step 3).
- **403 mentioning insufficient scopes** — your ADC predates this setup;
  re-run the login command in Setup step 4.
- **Errors mention enabling the API / quota project** — run Setup step 2 and
  `set-quota-project`; the error message itself carries the exact commands.

## Development

```bash
git clone https://github.com/jinchliu/tagmanager-mcp && cd tagmanager-mcp
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"

.venv/bin/nox -s tests    # stdlib unittest, fully offline
.venv/bin/nox -s lint     # black --check
.venv/bin/mcp dev tagmanager_mcp/server.py   # MCP Inspector
```

Point your MCP client at `.venv/bin/tagmanager-mcp` to run the checkout
instead of the installed release.
