LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Cloud & AI / GTM Integrations for Claude Code: Three MCP...
Read this article in other languages:

GTM Integrations for Claude Code: Three MCP Servers, Their Tool Count and Their Context Cost

GTM Integrations for Claude Code: Three MCP Servers, Their Tool Count and Their Context Cost
Contents
  1. Three ways a container reaches the model
  2. What sits underneath: 106 methods, seven scopes, one workspace
  3. Three MCP servers, three cuts through the same API
  4. The context price is not in the tool count
  5. What an integration may change is decided by the token
  6. Where it goes wrong
  7. What this does not solve
  8. Questions and answers
  9. Sources

A Google Tag Manager container is a JSON document that decides which scripts a website loads and when. Changing it from a prompt means handing a language model write access to that document. The interesting question is not whether that works — it does — but what exactly the model is allowed to touch, and what the connection costs before a single tag is written.

Three integrations were measured for this article on 9 September 2026: three MCP servers, each started over stdio with npx on a Raspberry Pi 5 with Node 20.19.2 and asked for its tool list, plus the Tag Manager API v2 that all of them sit on. The numbers below come from those runs and from the API’s own discovery document, not from the READMEs.

The path of a change from a prompt through Claude Code and an MCP server to the Tag Manager API, a workspace and the live container, with the publish step marked; below it three measured MCP servers with their tool count and the size of their tool list as bars, and the seven OAuth scopes with the number of methods each covers
The route a change takes, the size of three measured tool lists, and how the 106 API methods spread across seven scopes.

Three ways a container reaches the model

Claude Code knows nothing about Tag Manager on its own. Everything it can do with a container arrives through one of three routes, and only two of them add a capability.

An MCP server is a process that Claude Code starts and talks to over stdio or HTTP. It publishes a list of tools, and each tool appears in the session under the name mcp__<server>__<tool>. That name is what permission rules and hooks match on later, which is the reason it is worth knowing.

The API through the shell needs no server at all. An access token and curl are enough, and the model writes the request itself. Nothing is added to the context up front, and nothing is hidden behind a wrapper either — the trade is convenience against transparency.

A skill or slash command adds no capability whatsoever. It carries a recipe: a naming convention, a review checklist, the order in which a trigger has to exist before a tag can reference it. It changes what the model does with the other two routes, never what it may do. Confusing the two is the most common misreading of the word “integration” here.

What sits underneath: 106 methods, seven scopes, one workspace

Every route ends at the same place. The Tag Manager API v2 discovery document, revision 20260902 and 270,858 bytes long, describes 106 methods across 18 resources: 38 GET, 38 POST, 15 PUT and 15 DELETE.

Ten of those resources live inside a workspace — tags, triggers, variables, built-in variables, folders, templates, clients, transformations, zones and the gtag configuration. A workspace is a draft. Nothing written into it reaches a single visitor, no matter how wrong it is.

Two further steps stand between the draft and the live site. workspaces.create_version freezes a workspace into a container version, and versions.publish makes that version live. The scope tagmanager.publish covers exactly two of the 106 methods: the publish call and environments.reauthorize. Every other thing an integration can do is, by construction, invisible to visitors until a separate call says otherwise.

# Reading tags: the path carries account, container and workspace.
# A workspace id is mandatory - there is no route to the live tags.
ACC=6012345678; CONT=123456789; WS=42
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://tagmanager.googleapis.com/tagmanager/v2/accounts/$ACC/containers/$CONT/workspaces/$WS/tags"

# Without a token every method answers the same way:
# {"error":{"code":401,"status":"UNAUTHENTICATED",
#   "details":[{"reason":"CREDENTIALS_MISSING"}]}}

Fifteen of the API’s schemas carry a fingerprint, a value that changes whenever the entity changes. An update that sends a stale fingerprint is refused rather than allowed to overwrite an edit somebody made in the browser meanwhile.

Three MCP servers, three cuts through the same API

Server Tools tools/list ≈ tokens Shape
google-tag-manager-mcp-server 5.1.1 18 108,201 B 27,000 one tool per entity, action as a parameter
gtm-mcp 1.0.0 104 49,656 B 12,400 roughly one tool per API method
@theethosteam/gtm-mcp 1.1.1 33 19,797 B 4,900 task-shaped, five recipes and a raw call

The first packs the whole API behind 18 tools. Seventeen of them take an action parameter carrying 101 actions in total, of which 38 only read; gtm_tag alone covers create, get, list, update, remove and revert. The eighteenth, gtag_destination, only lists.

The second is close to a one-to-one mapping of the API: 104 tools against 106 methods, 37 of them named get_… or list_….

The third server organizes its tools around tasks rather than the API structure. Alongside ordinary calls, it provides five recipes — gtm_recipe_ga4_config, gtm_recipe_ga4_event, gtm_recipe_meta_pixel and two trigger recipes — plus a 215-byte tool named gtm_raw for raw calls that the other 32 tools do not cover.

A fourth candidate never started. @yoryoboy/gtm-mcp 4.1.0 stopped with Missing required configuration: DATABASE_URL, DB_ENCRYPTION_KEY, because it is built as a hosted multi-tenant service with a PostgreSQL store behind it, not as a local process. Publication on npm with “MCP server” in the description is no promise that a package runs on a workstation.

The context price is not in the tool count

The three tool lists differ in size by a factor of 5.5, and the order does not follow the number of tools. The 18-tool server sends 108,201 bytes of definitions, the 33-tool server 19,797.

Two tools account for nearly half of the largest list: gtm_version at 28,212 bytes and gtm_workspace at 24,594, together 48.8 per cent of 108,201. The cause is the ContainerVersion resource. It embeds tags, triggers, variables, clients, folders, zones, templates, transformations and the gtag configuration, so a fully typed schema of it is necessarily large — and it is sent whether or not a version is ever touched.

This is not a one-off cost at connection time. Tool definitions belong to the request, so they travel with every turn of the session. Two consequences follow. Connecting several servers of this shape at once fills a sizeable part of a context window before any work begins. And a server with looser schemas is not automatically the weaker choice: a wrong field type is caught by the API within a second, while a precise schema is paid for on every single turn.

What an integration may change is decided by the token

All three servers authenticate against Google in the same way, and the real guard rail is the OAuth scope. The largest server’s default asks for all seven, publishing and container deletion included — visible in its src/constants/scopes.ts as a flat list.

The requested scopes can be narrowed. The server reads GTM_SCOPES from its environment and splits the value on spaces or commas. This setting works in one of the three authentication modes only.

In the source, createAuthFromEnv passes the parsed scopes only to the service-account branch. The refresh-token branch receives the client id, secret and refresh token; the access-token branch receives the token. A refresh token already has the scopes it was issued with. Setting GTM_SCOPES alongside it therefore changes nothing and produces no notice.

{
  "mcpServers": {
    "gtm": {
      "command": "npx",
      "args": ["-y", "google-tag-manager-mcp-server"],
      "env": {
        "GOOGLE_SERVICE_ACCOUNT_KEY": "{...}",
        "GTM_SCOPES": "https://www.googleapis.com/auth/tagmanager.readonly"
      }
    }
  }
}

A second gate sits inside Tag Manager itself. A service account only reaches a container after its address has been added there as a user, and that permission is graded per container: read, edit, approve, publish. An account with edit rights and a publish scope still cannot publish.

The third gate is on the Claude Code side and matches on the tool name. A deny rule on mcp__gtm__gtm_version takes the publishing tool out of reach even where the token would allow the call, and a PreToolUse hook on the same name can demand a confirmation instead. Three independent gates, and the useful arrangement is to close the outermost one — the token — because it is the only one that also holds when the container is reached from somewhere other than this session.

Where it goes wrong

Update replaces, it does not merge. Fifteen of the 18 tool descriptions state it themselves: any field left out of the payload is deleted. A model that sends only the field it wants to change strips the rest of the tag. Eleven of the tools additionally require the fingerprint of the state they last read, which forces a read before every write and turns a careless edit into an error message instead of a loss.

A tag is not a flat object. Its settings live in a parameter array of key, type and value, with eight types in the enumeration — template, integer, boolean, list, map, triggerReference, tagReference and the unspecified one — where list and map nest further parameters inside themselves. This structure, not the tag name, is where generated payloads go wrong.

{
  "name": "GA4 - purchase",
  "type": "gaawe",
  "parameter": [
    { "type": "template", "key": "eventName", "value": "purchase" },
    { "type": "tagReference", "key": "measurementId",
      "value": "GA4 Configuration" },
    { "type": "list", "key": "eventParameters", "list": [
      { "type": "map", "map": [
        { "type": "template", "key": "name",  "value": "value" },
        { "type": "template", "key": "value", "value": "{{DLV - value}}" }
      ]}
    ]}
  ],
  "firingTriggerId": ["17"]
}

“GTM” is ambiguous on the package registry. A search for gtm-mcp returns servers for go-to-market frameworks alongside the Tag Manager ones; @shashwatgtmalpha/craft-gtm-mcp lists go-to-market among its keywords, and growth-mcp describes itself as an “open-source GTM MCP server” without meaning Tag Manager at all. The keyword list separates the two faster than the description does.

What this does not solve

None of these integrations can tell whether a tag actually fires. quick_preview compiles a workspace into a preview version; it does not load a page, and it says nothing about a data layer that arrives too late. The judgement about correctness stays with a preview session in a browser, exactly as before.

Neither does the API record why something changed. A container version carries a name and a description, and both stay empty unless they are filled in the same call — which makes the version description the cheapest place to record which prompt caused which change, and the first thing to go missing when a tag is created in a hurry.

And the file route has not become obsolete. An exported container is a single JSON file, and reviewing it against a diff needs no token, no scope and no server. For a change that is written by a model and read by a human, the export remains the shorter path — the API’s advantage begins where the change has to be applied, not where it has to be understood.

Questions and answers

How precisely can publishing be blocked when one tool bundles many actions?

Only as precisely as the server’s shape allows. A permission rule in Claude Code matches the tool name, not its parameters. On the 18-tool server, publishing sits inside gtm_version together with that tool’s other actions, and a deny rule on mcp__gtm__gtm_version takes all of them out of reach. On a server with roughly one tool per API method, the same kind of rule can hit the publish call on its own, provided it has a tool of its own.

A PreToolUse hook allows finer control. It sees the tool’s input and can decide by the value of action: let reading through, refuse publishing or ask for a confirmation. That is a small script rather than a line in the settings, and it has to be checked against every new server release, because action names can change.

Servers with a raw call leave a gap. gtm_raw is explicitly meant for everything the other tools do not reach, and a raw call can in principle address any method the token permits, publishing included. Blocking the recipes achieves nothing while the raw call stays open. For the same reason, the article recommends the token as the outermost gate: it holds regardless of which tool sends the call.

Does a deny rule on an MCP tool still hold when the model may use the shell?

Only if no token is reachable there. According to the article, the direct route needs nothing but an access token and curl, and a rule on a tool name such as mcp__gtm__gtm_version does not touch a curl call. If a token with the publish scope sits in an environment variable or a file the session can read, the scope alone decides.

Does prompt caching help against the context cost of large tool lists?

Prompt caching can largely reduce the monetary cost, but it does not reduce the space used in the context window. Tool definitions sit at the start of the request and remain unchanged from turn to turn, so a prompt cache can reuse them. After the first turn, they are usually billed at the cache read rate rather than the full input rate.

In the context window, however, cached tokens count in full. The roughly 27,000 tokens of the largest server occupy the window on every turn, cached or not, and are then missing for container JSON, responses and history. Adding or removing a server mid-session also changes the start of the request, and the cache has to be written again.

Is a separate workspace worth it for changes made by the model?

Yes, for two reasons. A workspace is a draft, and a separate one keeps the model’s changes apart from whatever someone is editing in the browser at the same time. The fingerprint does protect individual entities from being overwritten, but only in the tools that require it, and not against two drafts that do not fit together in substance.

Second, a separate workspace can be reviewed and discarded as a whole. Nothing the model created there reaches a visitor before create_version and publish, and if the result is no good, it disappears with the workspace instead of being unpicked item by item.

Two constraints come with it. The free version of Tag Manager allows only a few workspaces per container, up to three, so a model workspace kept open permanently uses up one of them. And if something was published from another workspace in the meantime, the model’s workspace has to be brought up to date before it can be frozen into a version; the API provides workspaces.sync for this, and conflicts become visible instead of being silently overwritten.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Write a comment

Experience with other models or providers and questions about the implementation are welcome here.

The email address is not published. Required fields are marked with an asterisk.

Articles & categories

CCTV

All 6 articles in this category Follow this category by RSS

Cloud & AI

All 18 articles in this category Follow this category by RSS

Data Privacy

All 20 articles in this category Follow this category by RSS

Digital Analytics

All 60 articles in this category Follow this category by RSS

Digital Marketing

All 38 articles in this category Follow this category by RSS

IT & Networks

All 19 articles in this category Follow this category by RSS

Music Production

All 18 articles in this category Follow this category by RSS

Raspberry PI

All 11 articles in this category Follow this category by RSS

SaaS & Internet Earning

Follow this category by RSS

Smart Home

All 18 articles in this category Follow this category by RSS

Web Development

All 11 articles in this category Follow this category by RSS

WordPress Plugins & Tricks

All 15 articles in this category Follow this category by RSS