Skip to main content
Glama
YerayRodri

gtm-mcp

by YerayRodri
README.md
# gtm-mcp

MCP server for full Google Tag Manager management via the GTM API v2: tags,
triggers, variables and versions.

## Tools (22)

Hierarchy: Account → Container → Workspace → (Tags / Triggers / Variables)

| Tool | What it does |
|---|---|
| `get_accounts` | List accessible GTM accounts — entry point |
| `get_containers` | Containers in an account |
| `get_workspaces` | Workspaces in a container (usually "Default Workspace") |
| `get_workspace_status` | Pending changes vs. the published version |
| `get_tags` / `get_tag` | List or detail tags |
| `create_tag` / `update_tag` / `delete_tag` / `revert_tag` | Tag CRUD |
| `get_triggers` / `get_trigger` | List or detail triggers |
| `create_trigger` / `update_trigger` / `delete_trigger` | Trigger CRUD |
| `get_variables` | List variables |
| `create_variable` / `update_variable` / `delete_variable` | Variable CRUD |
| `get_versions` | History of published versions |
| `create_version` | Snapshot the current workspace (restore point) |
| `publish_version` | Publish a version to production |

## Typical flow

```
1. get_accounts()                                    → account_id
2. get_containers(account_id)                        → container_id
3. get_workspaces(account_id, container_id)          → workspace_id
4. get_workspace_status(...)                          → review pending changes
5. [create/edit tags, triggers, variables]
6. create_version(account_id, container_id, workspace_id, name="v1")
7. publish_version(account_id, container_id, version_id)
```

## Setup

1. Create a Google Cloud project (or reuse one) and enable the
   **Tag Manager API**.
2. Create an OAuth 2.0 Client ID of type "Desktop app" and download it as
   `client_secret.json`.
3. If the app is in "Testing" mode, add your Google account as a test user.
4. Install dependencies:
   ```bash
   python3 -m venv .venv
   source .venv/bin/activate
   pip install -r requirements.txt
   ```
5. Run the OAuth flow once:
   ```bash
   CLIENT_SECRET_PATH=~/.config/gtm-mcp/client_secret.json python3 setup_auth.py
   ```
   This opens a browser — log in and grant access. Credentials are saved to
   `~/.config/gtm-mcp/credentials.json`.

⚠️ Only enable this MCP when you're actually working on GTM — leaving it
always-on in a client that doesn't cache the OAuth session can trigger the
auth flow on every startup.

## MCP client configuration

```json
{
  "mcpServers": {
    "gtm": {
      "command": "/path/to/.venv/bin/python3",
      "args": ["/path/to/gtm-mcp/server.py"]
    }
  }
}
```

| Env var | Default | Purpose |
|---|---|---|
| `GOOGLE_GTM_CREDENTIALS` | `~/.config/gtm-mcp/credentials.json` | Path to the credentials file generated by `setup_auth.py` |

## Confirmation required for critical operations

These tools require `confirmed=True`. Called without it, they return a JSON
with `requires_confirmation: true` that the calling agent MUST show to the
user and wait for a response before repeating the call with `confirmed=True`.

| Tool | What it does |
|---|---|
| `delete_tag` | Delete a tag from the workspace |
| `revert_tag` | Discard workspace changes on a tag, reverting to the published version |
| `delete_trigger` | Delete a trigger from the workspace |
| `delete_variable` | Delete a variable from the workspace |
| `publish_version` | Publish a version to production — changes what's live on a client's website |

```
# 1. First call — no confirmed
publish_version(account_id="123456789", container_id="7654321", version_id="12")
# → returns requires_confirmation: true → show it to the user

# 2. Second call — after the user confirms
publish_version(account_id="123456789", container_id="7654321", version_id="12", confirmed=True)
```

## Response size

List/get tools strip fields the GTM API always includes but no workflow ever uses (`path`, `fingerprint`, `monitoringMetadata`, `tagManagerUrl`, `features`), and parent IDs already known from the call's own parameters. Measured against real data: 39-81% smaller responses depending on the tool. `create_version`/`publish_version` return a summary instead of the full nested container snapshot (every tag/trigger/variable embedded) that the underlying API sends back.

## Security

- Every tool carries MCP Tool Annotations (`readOnlyHint`, `destructiveHint`,
  `idempotentHint`, `openWorldHint`) describing its effect up front.
- Execution errors propagate as real MCP protocol errors (`isError=true`),
  never as a JSON payload that looks successful with an `error` key buried
  inside it.

## Notes

- **All changes** (create/edit/delete) stay in the workspace until published
  with `create_version` + `publish_version`.
- `update_tag`/`update_trigger`/`update_variable` do a GET before the PUT to
  preserve unspecified fields — you won't lose parameters by omission. Verified
  live with a full create → edit → delete round trip on each.
- ⚠️ `revert_tag` on a tag that was never published **deletes** it rather than
  restoring a prior state — "revert" means "go back to the last published
  version," and if the tag never existed in a published version, that version
  is "doesn't exist." Verified live; this is documented API behavior, not a
  bug in this server.
- Common `tag_type` values: `googtag` (GA4/Google Tag), `html` (custom HTML),
  `gclidw` (Ads conversion), `ua` (legacy Universal Analytics).
- Common `trigger_type` values: `pageview`, `customEvent`, `linkClick`,
  `formSubmission`, `domReady`.
- Common `variable_type` values: `v` (Data Layer), `k` (Cookie), `u` (URL),
  `jsm` (Custom JS).
- `parameters` is a list of `{type, key, value}` where `type` is one of
  `template`, `boolean`, `integer`, `list` or `map`.

## License

MIT — see [LICENSE](LICENSE).