adloop 0.10.0__tar.gz → 0.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {adloop-0.10.0 → adloop-0.11.0}/PKG-INFO +64 -11
  2. {adloop-0.10.0 → adloop-0.11.0}/README.md +62 -10
  3. {adloop-0.10.0 → adloop-0.11.0}/pyproject.toml +2 -1
  4. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/__init__.py +5 -1
  5. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/read.py +400 -5
  6. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/write.py +421 -3
  7. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/auth.py +30 -1
  8. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/crossref.py +262 -0
  9. adloop-0.11.0/src/adloop/gtm/__init__.py +1 -0
  10. adloop-0.11.0/src/adloop/gtm/client.py +20 -0
  11. adloop-0.11.0/src/adloop/gtm/read.py +648 -0
  12. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/adloop.md +56 -4
  13. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/commands/analyze-performance.md +2 -0
  14. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/commands/optimize-campaign.md +2 -0
  15. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/server.py +405 -2
  16. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/__main__.py +0 -0
  17. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/_mcp_patches.py +0 -0
  18. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/__init__.py +0 -0
  19. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/client.py +0 -0
  20. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/currency.py +0 -0
  21. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/enums.py +0 -0
  22. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/forecast.py +0 -0
  23. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/gaql.py +0 -0
  24. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ads/pmax.py +0 -0
  25. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/cli.py +0 -0
  26. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/config.py +0 -0
  27. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/diagnostics.py +0 -0
  28. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ga4/__init__.py +0 -0
  29. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ga4/client.py +0 -0
  30. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ga4/reports.py +0 -0
  31. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/ga4/tracking.py +0 -0
  32. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/__init__.py +0 -0
  33. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/commands/budget-plan.md +0 -0
  34. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/commands/create-ad.md +0 -0
  35. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/commands/create-campaign.md +0 -0
  36. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  37. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/rules_install.py +0 -0
  38. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/runtime.py +0 -0
  39. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/safety/__init__.py +0 -0
  40. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/safety/audit.py +0 -0
  41. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/safety/guards.py +0 -0
  42. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/safety/preview.py +0 -0
  43. {adloop-0.10.0 → adloop-0.11.0}/src/adloop/tracking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: adloop
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: The AI command center for Google Ads, GA4, and tracking code.
5
5
  Keywords: mcp,google-ads,google-analytics,ga4,cursor,marketing
6
6
  Author: Daniel Klose
@@ -10,6 +10,7 @@ Requires-Dist: fastmcp>=3.0.0
10
10
  Requires-Dist: google-ads>=29.0.0
11
11
  Requires-Dist: google-analytics-data>=0.20.0
12
12
  Requires-Dist: google-analytics-admin>=0.27.0
13
+ Requires-Dist: google-api-python-client>=2.100.0
13
14
  Requires-Dist: google-auth-oauthlib>=1.0.0
14
15
  Requires-Dist: pyyaml>=6.0
15
16
  Requires-Dist: pytest>=8.0 ; extra == 'dev'
@@ -38,12 +39,31 @@ Description-Content-Type: text/markdown
38
39
 
39
40
  An MCP server that gives your AI assistant read + write access to Google Ads and GA4 — with safety guardrails that prevent accidental spend.
40
41
 
41
- `pip install adloop`
42
+ **[☁️ Skip the setup — use AdLoop Cloud (free beta)](https://getadloop.com)**  ·  or self-host: `pip install adloop`
42
43
 
43
44
  </div>
44
45
 
46
+ > [!TIP]
47
+ > **[AdLoop Cloud](https://getadloop.com) is the hosted version of this project — live now, free during beta (limited seats).** Connect Google in two clicks and use the same 61 tools from claude.ai, ChatGPT, Claude Code, Cursor, or Gemini. No Google Cloud project, no developer token, no OAuth verification wait. EU-hosted, GDPR-first, DPA included.
48
+
45
49
  ---
46
50
 
51
+ ## Cloud or Self-Hosted?
52
+
53
+ Both versions run the same 61 tools with the same safety model. The difference is who handles the plumbing:
54
+
55
+ | | ☁️ [AdLoop Cloud](https://getadloop.com) | 🛠️ Self-hosted (this repo) |
56
+ |---|---|---|
57
+ | **Setup** | Connect Google in two clicks | ~5 min: own Google Cloud project + `adloop init` |
58
+ | **Google Cloud project** | Not needed | Required (free) |
59
+ | **Ads developer token** | Not needed | Required (from your MCC) |
60
+ | **Works with** | claude.ai, ChatGPT, Claude Code, Cursor, Gemini | Claude Code, Cursor, Claude Desktop, any local MCP client |
61
+ | **Where your data flows** | EU servers (Germany), GDPR-first, DPA included | 100% your machine — nothing leaves it |
62
+ | **Updates** | Automatic | `pip install -U adloop` |
63
+ | **Price** | Free during beta | Free forever (MIT) |
64
+
65
+ **Not sure? [Start with Cloud](https://getadloop.com)** — it's the fastest way to see what AdLoop can do, and it's the only way to use AdLoop from claude.ai or ChatGPT. Self-host when you want everything on your own machine or need to modify the code. And if you're here to hack on AdLoop itself: welcome, keep scrolling.
66
+
47
67
  ## What It Solves
48
68
 
49
69
  AdLoop exists because managing Google Ads alongside your code is a mess. These are the specific problems it handles:
@@ -58,6 +78,8 @@ AdLoop exists because managing Google Ads alongside your code is a mess. These a
58
78
 
59
79
  - **"My landing page gets paid traffic but nobody converts."** AdLoop joins your ad final URLs with GA4 page-level data. See which pages get clicks but no conversions, which have high bounce rates, and which ones are orphaned from any ad campaign.
60
80
 
81
+ - **"Are conversions even being tagged on every page?"** AdLoop reads your live Google Tag Manager container, joins it against the events in your codebase and the events firing in GA4, and tells you exactly which conversions are being captured, which tags are paused, which page-scope filters are too narrow, and which codebase events have no tag at all — the kind of three-way audit GTM Preview can't give you in a single view.
82
+
61
83
  - **"I don't know if my EU consent setup is causing data gaps."** In Europe, 30-70% of users reject analytics cookies. AdLoop accounts for this automatically — it won't diagnose a normal GDPR consent gap as broken tracking.
62
84
 
63
85
  ## Built From Real Usage
@@ -66,9 +88,9 @@ Every tool exists because of an actual problem hit while running real Google Ads
66
88
 
67
89
  The best features come from real workflows. If you're using AdLoop and find yourself wishing it could do something it can't, **open an issue describing your situation** — not just "add feature X" but "I was trying to do Y and couldn't because Z." The context matters more than the request.
68
90
 
69
- ## All 43 Tools
91
+ ## All 61 Tools
70
92
 
71
- > **Quick start:** `pip install adloop` or `git clone https://github.com/kLOsk/adloop.git && cd adloop && uv sync && uv run adloop init`
93
+ > **Quick start:** `pip install adloop` or `git clone https://github.com/kLOsk/adloop.git && cd adloop && uv sync && uv run adloop init` — or zero setup on [AdLoop Cloud](https://getadloop.com)
72
94
 
73
95
  ### Diagnostics
74
96
 
@@ -103,8 +125,11 @@ The best features come from real workflows. If you're using AdLoop and find your
103
125
  | `get_asset_performance` | Per-asset details for PMax — field type, serving status, content |
104
126
  | `get_detailed_asset_performance` | Top-performing asset combinations — which headline+description+image combos Google selects most |
105
127
  | `get_audience_performance` | Audience segment performance — remarketing, in-market, affinity, demographics |
128
+ | `get_demographic_targeting` | List demographic criteria (age/gender/parental status/income) on an ad group or campaign |
106
129
  | `run_gaql` | Arbitrary GAQL queries for anything else |
107
130
 
131
+ > **Compact mode** — `get_campaign_performance`, `get_keyword_performance`, `get_search_terms`, and `get_ad_performance` accept `compact=true`: account totals, breakdowns, top-10 rows, and pre-computed offender lists (zero-conversion spenders, low-QS keywords, negative-keyword candidates, thin RSAs) instead of every row. ~90% smaller responses — built for account audits so raw tables don't flood your AI's context.
132
+
108
133
  ### Cross-Reference Tools (GA4 + Ads Combined)
109
134
 
110
135
  These tools call both APIs internally and return unified results with auto-generated insights. They're the core of what makes AdLoop different from having separate GA4 and Ads tools.
@@ -122,6 +147,27 @@ These tools call both APIs internally and return unified results with auto-gener
122
147
  | `validate_tracking` | Compare event names found in your codebase against what GA4 actually records. Returns matched, missing, and unexpected events with diagnostics. |
123
148
  | `generate_tracking_code` | Generate ready-to-paste GA4 gtag JavaScript for any event, with recommended parameters for well-known events (sign_up, purchase, etc.) and optional trigger wrappers. |
124
149
 
150
+ ### Google Tag Manager Tools
151
+
152
+ These tools read the live GTM container and join it with the codebase + GA4 to find tracking gaps that pure GA4 inspection can't catch — page-scoped triggers, paused tags, dynamic event names, brittle CSS selectors, and codebase events with no tag wired up at all.
153
+
154
+ | Tool | What It Does |
155
+ |------|-------------|
156
+ | `audit_event_coverage` | **The flagship.** Three-way join: codebase events ↔ GTM tags ↔ GA4 actual fires. For each event name in `expected_events`, returns one of 10 statuses (`ok`, `no_tag_no_fire`, `tag_paused`, `tag_active_but_not_firing`, `gtm_only_firing`, `ga4_only`, etc.) plus auto-generated insights for the gaps. |
157
+ | `list_gtm_accounts` | Discover accessible GTM accounts |
158
+ | `list_gtm_containers` | List containers under an account — returns numeric `container_id` (needed by other tools), public `GTM-XXXXXXX` ID, and usage context (web/iOS/Android/server) |
159
+ | `list_gtm_tags` | Every tag in the live container with parsed event names and resolved firing/blocking trigger names |
160
+ | `get_gtm_tag` | Full raw config for a single tag — every parameter, firing/blocking triggers with filter conditions, priority, pause status, sampling |
161
+ | `list_gtm_triggers` | Every trigger with filter conditions parsed to readable text (e.g. `{{Page Path}} contains service-promotions`, `{{Form ID}} NOT contains wf-form-...`). Renders the `negate` flag explicitly. |
162
+ | `get_gtm_trigger` | Full trigger config + reverse lookup of every tag that uses it. Includes parsed `element_visibility` block (selector, on-screen ratio, firing frequency) for elementVisibility triggers and `group_member_trigger_ids` for triggerGroup types |
163
+ | `list_gtm_variables` | Custom variables (data layer, constants, JS) plus enabled built-in variables |
164
+ | `list_gtm_workspaces` | List drafts (workspaces) under a container — workspace IDs are needed by `get_gtm_workspace_diff` |
165
+ | `get_gtm_workspace_diff` | Drafted-but-not-published changes — common cause of "I edited a tag but nothing happened". Returns `is_clean: true` when nothing is pending. |
166
+ | `list_gtm_versions` | Publish history with version IDs and entity counts. Use to correlate a metric drop with a recent publish. |
167
+ | `get_gtm_version` | Full metadata + tag/trigger names for a single historical container version |
168
+
169
+ > **Setup for GTM tools** — Enable the **Tag Manager API v2** in your GCP project, then add your AdLoop credentials' email (the OAuth user, or the service account email if using a service account) as a **Read** user on the GTM container under Admin → User Management. Service accounts pick up access on the next call. **OAuth users upgrading from an earlier AdLoop version must re-authorize once**: the GTM scope is new, so delete `~/.adloop/token.json` and run any tool to re-consent — until then GTM tools return a permissions error.
170
+
125
171
  ### Planning Tools
126
172
 
127
173
  | Tool | What It Does |
@@ -145,6 +191,8 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
145
191
  | `draft_image_assets` | Create campaign image assets from local PNG, JPEG, or GIF files. |
146
192
  | `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — blocks BROAD match on Manual CPC campaigns. |
147
193
  | `add_negative_keywords` | Propose negative keywords directly on a campaign |
194
+ | `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
195
+ | `draft_demographic_targeting` | Propose demographic criteria (age, gender, parental status, income) — exclusions by default |
148
196
  | `propose_negative_keyword_list` | Draft a shared negative keyword list (SharedSet) and attach it to a campaign — reusable across multiple campaigns |
149
197
  | `pause_entity` | Pause a campaign, ad group, ad, or keyword |
150
198
  | `enable_entity` | Re-enable a paused entity |
@@ -344,6 +392,7 @@ Ask your AI assistant things like:
344
392
  - *"Draft a new responsive search ad for my main campaign."*
345
393
  - *"Which landing pages get paid traffic but don't convert?"*
346
394
  - *"Is my tracking set up correctly? Compare my codebase events against GA4."*
395
+ - *"Audit my Google Tag Manager container — which conversions are being captured and where are the gaps?"*
347
396
  - *"What keywords should I target for [product]? Find ideas and estimate the budget."*
348
397
  - *"How much budget would I need for these keywords in Germany?"*
349
398
  - *"Create a new search campaign for [product feature] with a €20/day budget."*
@@ -370,11 +419,11 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
370
419
  ```
371
420
  src/adloop/
372
421
  ├── __init__.py # Entry point — routes 'adloop init' to wizard, otherwise starts MCP server
373
- ├── server.py # FastMCP server — 43 tool registrations with safety annotations
422
+ ├── server.py # FastMCP server — 61 tool registrations with safety annotations
374
423
  ├── config.py # Config loader (~/.adloop/config.yaml)
375
- ├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts
424
+ ├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts; GA4 / Ads / GTM scopes
376
425
  ├── cli.py # Interactive 'adloop init' setup wizard
377
- ├── crossref.py # Cross-reference tools (GA4 + Ads combined analysis)
426
+ ├── crossref.py # Cross-reference tools (GA4 + Ads + GTM combined analysis)
378
427
  ├── tracking.py # Tracking validation + code generation tools
379
428
  ├── ga4/
380
429
  │ ├── client.py # GA4 Data + Admin API clients
@@ -387,6 +436,9 @@ src/adloop/
387
436
  │ ├── pmax.py # Performance Max tools — campaign/asset group performance, asset labels, top combinations
388
437
  │ ├── write.py # Draft campaign, RSA, keywords; pause, enable, remove, confirm
389
438
  │ └── forecast.py # Budget estimation + keyword discovery via Keyword Planner API
439
+ ├── gtm/
440
+ │ ├── client.py # Google Tag Manager API v2 client
441
+ │ └── read.py # Live container fetching, tag/trigger/variable parsing, workspace diff, version history
390
442
  └── safety/
391
443
  ├── guards.py # Budget caps, bid limits, blocked operations, Broad Match safety
392
444
  ├── preview.py # Change plans and previews
@@ -408,9 +460,10 @@ What's been shipped and what's next:
408
460
  - ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
409
461
  - **Claude Desktop one-click install** — `adloop install claude-desktop` (and/or a `.dxt` extension bundle) that writes the AdLoop MCP entry into `claude_desktop_config.json` automatically, so Claude Desktop + Cowork users don't have to hand-edit JSON
410
462
  - ~~PyPI package~~ ✓ — `pip install adloop`
411
- - **[AdLoop Cloud](https://getadloop.com)** — the hosted version: no Google Cloud project, no developer token, connect Google in two clicks (EU-hosted, GDPR-first)
463
+ - ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version, live in beta: no Google Cloud project, no developer token, connect Google in two clicks (EU-hosted, GDPR-first)
412
464
  - ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
413
465
  - ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
466
+ - ~~Google Tag Manager integration~~ ✓ — read tools for tags, triggers, variables, workspaces, and version history, plus the `audit_event_coverage` three-way join across codebase events, GTM tags, and GA4 actual fires
414
467
  - **Community launch** — HN, Indie Hackers, r/cursor, Twitter
415
468
  - **Video walkthrough**
416
469
 
@@ -424,14 +477,14 @@ MIT — see [LICENSE](LICENSE).
424
477
 
425
478
  ## Privacy
426
479
 
427
- AdLoop runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy.
480
+ The open-source version runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy. AdLoop Cloud has its own [privacy policy](https://getadloop.com/datenschutz) and [DPA](https://getadloop.com/avv).
428
481
 
429
482
  ---
430
483
 
431
484
  <div align="center">
432
485
 
433
- **If AdLoop helps you run Google Ads, GA4, and tracking code from one place — [give it a star](https://github.com/kLOsk/adloop).**
486
+ **If AdLoop helps you run Google Ads, GA4, and tracking code from one place — [give it a star](https://github.com/kLOsk/adloop) or [try the hosted version](https://getadloop.com).**
434
487
 
435
- Made by [@kLOsk](https://github.com/kLOsk) | [Privacy Policy](PRIVACY.md)
488
+ Made by [@kLOsk](https://github.com/kLOsk) | [AdLoop Cloud](https://getadloop.com) | [Privacy Policy](PRIVACY.md)
436
489
 
437
490
  </div>
@@ -14,12 +14,31 @@
14
14
 
15
15
  An MCP server that gives your AI assistant read + write access to Google Ads and GA4 — with safety guardrails that prevent accidental spend.
16
16
 
17
- `pip install adloop`
17
+ **[☁️ Skip the setup — use AdLoop Cloud (free beta)](https://getadloop.com)** &nbsp;·&nbsp; or self-host: `pip install adloop`
18
18
 
19
19
  </div>
20
20
 
21
+ > [!TIP]
22
+ > **[AdLoop Cloud](https://getadloop.com) is the hosted version of this project — live now, free during beta (limited seats).** Connect Google in two clicks and use the same 61 tools from claude.ai, ChatGPT, Claude Code, Cursor, or Gemini. No Google Cloud project, no developer token, no OAuth verification wait. EU-hosted, GDPR-first, DPA included.
23
+
21
24
  ---
22
25
 
26
+ ## Cloud or Self-Hosted?
27
+
28
+ Both versions run the same 61 tools with the same safety model. The difference is who handles the plumbing:
29
+
30
+ | | ☁️ [AdLoop Cloud](https://getadloop.com) | 🛠️ Self-hosted (this repo) |
31
+ |---|---|---|
32
+ | **Setup** | Connect Google in two clicks | ~5 min: own Google Cloud project + `adloop init` |
33
+ | **Google Cloud project** | Not needed | Required (free) |
34
+ | **Ads developer token** | Not needed | Required (from your MCC) |
35
+ | **Works with** | claude.ai, ChatGPT, Claude Code, Cursor, Gemini | Claude Code, Cursor, Claude Desktop, any local MCP client |
36
+ | **Where your data flows** | EU servers (Germany), GDPR-first, DPA included | 100% your machine — nothing leaves it |
37
+ | **Updates** | Automatic | `pip install -U adloop` |
38
+ | **Price** | Free during beta | Free forever (MIT) |
39
+
40
+ **Not sure? [Start with Cloud](https://getadloop.com)** — it's the fastest way to see what AdLoop can do, and it's the only way to use AdLoop from claude.ai or ChatGPT. Self-host when you want everything on your own machine or need to modify the code. And if you're here to hack on AdLoop itself: welcome, keep scrolling.
41
+
23
42
  ## What It Solves
24
43
 
25
44
  AdLoop exists because managing Google Ads alongside your code is a mess. These are the specific problems it handles:
@@ -34,6 +53,8 @@ AdLoop exists because managing Google Ads alongside your code is a mess. These a
34
53
 
35
54
  - **"My landing page gets paid traffic but nobody converts."** AdLoop joins your ad final URLs with GA4 page-level data. See which pages get clicks but no conversions, which have high bounce rates, and which ones are orphaned from any ad campaign.
36
55
 
56
+ - **"Are conversions even being tagged on every page?"** AdLoop reads your live Google Tag Manager container, joins it against the events in your codebase and the events firing in GA4, and tells you exactly which conversions are being captured, which tags are paused, which page-scope filters are too narrow, and which codebase events have no tag at all — the kind of three-way audit GTM Preview can't give you in a single view.
57
+
37
58
  - **"I don't know if my EU consent setup is causing data gaps."** In Europe, 30-70% of users reject analytics cookies. AdLoop accounts for this automatically — it won't diagnose a normal GDPR consent gap as broken tracking.
38
59
 
39
60
  ## Built From Real Usage
@@ -42,9 +63,9 @@ Every tool exists because of an actual problem hit while running real Google Ads
42
63
 
43
64
  The best features come from real workflows. If you're using AdLoop and find yourself wishing it could do something it can't, **open an issue describing your situation** — not just "add feature X" but "I was trying to do Y and couldn't because Z." The context matters more than the request.
44
65
 
45
- ## All 43 Tools
66
+ ## All 61 Tools
46
67
 
47
- > **Quick start:** `pip install adloop` or `git clone https://github.com/kLOsk/adloop.git && cd adloop && uv sync && uv run adloop init`
68
+ > **Quick start:** `pip install adloop` or `git clone https://github.com/kLOsk/adloop.git && cd adloop && uv sync && uv run adloop init` — or zero setup on [AdLoop Cloud](https://getadloop.com)
48
69
 
49
70
  ### Diagnostics
50
71
 
@@ -79,8 +100,11 @@ The best features come from real workflows. If you're using AdLoop and find your
79
100
  | `get_asset_performance` | Per-asset details for PMax — field type, serving status, content |
80
101
  | `get_detailed_asset_performance` | Top-performing asset combinations — which headline+description+image combos Google selects most |
81
102
  | `get_audience_performance` | Audience segment performance — remarketing, in-market, affinity, demographics |
103
+ | `get_demographic_targeting` | List demographic criteria (age/gender/parental status/income) on an ad group or campaign |
82
104
  | `run_gaql` | Arbitrary GAQL queries for anything else |
83
105
 
106
+ > **Compact mode** — `get_campaign_performance`, `get_keyword_performance`, `get_search_terms`, and `get_ad_performance` accept `compact=true`: account totals, breakdowns, top-10 rows, and pre-computed offender lists (zero-conversion spenders, low-QS keywords, negative-keyword candidates, thin RSAs) instead of every row. ~90% smaller responses — built for account audits so raw tables don't flood your AI's context.
107
+
84
108
  ### Cross-Reference Tools (GA4 + Ads Combined)
85
109
 
86
110
  These tools call both APIs internally and return unified results with auto-generated insights. They're the core of what makes AdLoop different from having separate GA4 and Ads tools.
@@ -98,6 +122,27 @@ These tools call both APIs internally and return unified results with auto-gener
98
122
  | `validate_tracking` | Compare event names found in your codebase against what GA4 actually records. Returns matched, missing, and unexpected events with diagnostics. |
99
123
  | `generate_tracking_code` | Generate ready-to-paste GA4 gtag JavaScript for any event, with recommended parameters for well-known events (sign_up, purchase, etc.) and optional trigger wrappers. |
100
124
 
125
+ ### Google Tag Manager Tools
126
+
127
+ These tools read the live GTM container and join it with the codebase + GA4 to find tracking gaps that pure GA4 inspection can't catch — page-scoped triggers, paused tags, dynamic event names, brittle CSS selectors, and codebase events with no tag wired up at all.
128
+
129
+ | Tool | What It Does |
130
+ |------|-------------|
131
+ | `audit_event_coverage` | **The flagship.** Three-way join: codebase events ↔ GTM tags ↔ GA4 actual fires. For each event name in `expected_events`, returns one of 10 statuses (`ok`, `no_tag_no_fire`, `tag_paused`, `tag_active_but_not_firing`, `gtm_only_firing`, `ga4_only`, etc.) plus auto-generated insights for the gaps. |
132
+ | `list_gtm_accounts` | Discover accessible GTM accounts |
133
+ | `list_gtm_containers` | List containers under an account — returns numeric `container_id` (needed by other tools), public `GTM-XXXXXXX` ID, and usage context (web/iOS/Android/server) |
134
+ | `list_gtm_tags` | Every tag in the live container with parsed event names and resolved firing/blocking trigger names |
135
+ | `get_gtm_tag` | Full raw config for a single tag — every parameter, firing/blocking triggers with filter conditions, priority, pause status, sampling |
136
+ | `list_gtm_triggers` | Every trigger with filter conditions parsed to readable text (e.g. `{{Page Path}} contains service-promotions`, `{{Form ID}} NOT contains wf-form-...`). Renders the `negate` flag explicitly. |
137
+ | `get_gtm_trigger` | Full trigger config + reverse lookup of every tag that uses it. Includes parsed `element_visibility` block (selector, on-screen ratio, firing frequency) for elementVisibility triggers and `group_member_trigger_ids` for triggerGroup types |
138
+ | `list_gtm_variables` | Custom variables (data layer, constants, JS) plus enabled built-in variables |
139
+ | `list_gtm_workspaces` | List drafts (workspaces) under a container — workspace IDs are needed by `get_gtm_workspace_diff` |
140
+ | `get_gtm_workspace_diff` | Drafted-but-not-published changes — common cause of "I edited a tag but nothing happened". Returns `is_clean: true` when nothing is pending. |
141
+ | `list_gtm_versions` | Publish history with version IDs and entity counts. Use to correlate a metric drop with a recent publish. |
142
+ | `get_gtm_version` | Full metadata + tag/trigger names for a single historical container version |
143
+
144
+ > **Setup for GTM tools** — Enable the **Tag Manager API v2** in your GCP project, then add your AdLoop credentials' email (the OAuth user, or the service account email if using a service account) as a **Read** user on the GTM container under Admin → User Management. Service accounts pick up access on the next call. **OAuth users upgrading from an earlier AdLoop version must re-authorize once**: the GTM scope is new, so delete `~/.adloop/token.json` and run any tool to re-consent — until then GTM tools return a permissions error.
145
+
101
146
  ### Planning Tools
102
147
 
103
148
  | Tool | What It Does |
@@ -121,6 +166,8 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
121
166
  | `draft_image_assets` | Create campaign image assets from local PNG, JPEG, or GIF files. |
122
167
  | `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — blocks BROAD match on Manual CPC campaigns. |
123
168
  | `add_negative_keywords` | Propose negative keywords directly on a campaign |
169
+ | `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
170
+ | `draft_demographic_targeting` | Propose demographic criteria (age, gender, parental status, income) — exclusions by default |
124
171
  | `propose_negative_keyword_list` | Draft a shared negative keyword list (SharedSet) and attach it to a campaign — reusable across multiple campaigns |
125
172
  | `pause_entity` | Pause a campaign, ad group, ad, or keyword |
126
173
  | `enable_entity` | Re-enable a paused entity |
@@ -320,6 +367,7 @@ Ask your AI assistant things like:
320
367
  - *"Draft a new responsive search ad for my main campaign."*
321
368
  - *"Which landing pages get paid traffic but don't convert?"*
322
369
  - *"Is my tracking set up correctly? Compare my codebase events against GA4."*
370
+ - *"Audit my Google Tag Manager container — which conversions are being captured and where are the gaps?"*
323
371
  - *"What keywords should I target for [product]? Find ideas and estimate the budget."*
324
372
  - *"How much budget would I need for these keywords in Germany?"*
325
373
  - *"Create a new search campaign for [product feature] with a €20/day budget."*
@@ -346,11 +394,11 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
346
394
  ```
347
395
  src/adloop/
348
396
  ├── __init__.py # Entry point — routes 'adloop init' to wizard, otherwise starts MCP server
349
- ├── server.py # FastMCP server — 43 tool registrations with safety annotations
397
+ ├── server.py # FastMCP server — 61 tool registrations with safety annotations
350
398
  ├── config.py # Config loader (~/.adloop/config.yaml)
351
- ├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts
399
+ ├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts; GA4 / Ads / GTM scopes
352
400
  ├── cli.py # Interactive 'adloop init' setup wizard
353
- ├── crossref.py # Cross-reference tools (GA4 + Ads combined analysis)
401
+ ├── crossref.py # Cross-reference tools (GA4 + Ads + GTM combined analysis)
354
402
  ├── tracking.py # Tracking validation + code generation tools
355
403
  ├── ga4/
356
404
  │ ├── client.py # GA4 Data + Admin API clients
@@ -363,6 +411,9 @@ src/adloop/
363
411
  │ ├── pmax.py # Performance Max tools — campaign/asset group performance, asset labels, top combinations
364
412
  │ ├── write.py # Draft campaign, RSA, keywords; pause, enable, remove, confirm
365
413
  │ └── forecast.py # Budget estimation + keyword discovery via Keyword Planner API
414
+ ├── gtm/
415
+ │ ├── client.py # Google Tag Manager API v2 client
416
+ │ └── read.py # Live container fetching, tag/trigger/variable parsing, workspace diff, version history
366
417
  └── safety/
367
418
  ├── guards.py # Budget caps, bid limits, blocked operations, Broad Match safety
368
419
  ├── preview.py # Change plans and previews
@@ -384,9 +435,10 @@ What's been shipped and what's next:
384
435
  - ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
385
436
  - **Claude Desktop one-click install** — `adloop install claude-desktop` (and/or a `.dxt` extension bundle) that writes the AdLoop MCP entry into `claude_desktop_config.json` automatically, so Claude Desktop + Cowork users don't have to hand-edit JSON
386
437
  - ~~PyPI package~~ ✓ — `pip install adloop`
387
- - **[AdLoop Cloud](https://getadloop.com)** — the hosted version: no Google Cloud project, no developer token, connect Google in two clicks (EU-hosted, GDPR-first)
438
+ - ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version, live in beta: no Google Cloud project, no developer token, connect Google in two clicks (EU-hosted, GDPR-first)
388
439
  - ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
389
440
  - ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
441
+ - ~~Google Tag Manager integration~~ ✓ — read tools for tags, triggers, variables, workspaces, and version history, plus the `audit_event_coverage` three-way join across codebase events, GTM tags, and GA4 actual fires
390
442
  - **Community launch** — HN, Indie Hackers, r/cursor, Twitter
391
443
  - **Video walkthrough**
392
444
 
@@ -400,14 +452,14 @@ MIT — see [LICENSE](LICENSE).
400
452
 
401
453
  ## Privacy
402
454
 
403
- AdLoop runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy.
455
+ The open-source version runs entirely on your machine. No data is collected, stored, or transmitted to any server. See [PRIVACY.md](PRIVACY.md) for the full privacy policy. AdLoop Cloud has its own [privacy policy](https://getadloop.com/datenschutz) and [DPA](https://getadloop.com/avv).
404
456
 
405
457
  ---
406
458
 
407
459
  <div align="center">
408
460
 
409
- **If AdLoop helps you run Google Ads, GA4, and tracking code from one place — [give it a star](https://github.com/kLOsk/adloop).**
461
+ **If AdLoop helps you run Google Ads, GA4, and tracking code from one place — [give it a star](https://github.com/kLOsk/adloop) or [try the hosted version](https://getadloop.com).**
410
462
 
411
- Made by [@kLOsk](https://github.com/kLOsk) | [Privacy Policy](PRIVACY.md)
463
+ Made by [@kLOsk](https://github.com/kLOsk) | [AdLoop Cloud](https://getadloop.com) | [Privacy Policy](PRIVACY.md)
412
464
 
413
465
  </div>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.10.0"
3
+ version = "0.11.0"
4
4
  description = "The AI command center for Google Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -14,6 +14,7 @@ dependencies = [
14
14
  "google-ads>=29.0.0",
15
15
  "google-analytics-data>=0.20.0",
16
16
  "google-analytics-admin>=0.27.0",
17
+ "google-api-python-client>=2.100.0",
17
18
  "google-auth-oauthlib>=1.0.0",
18
19
  "pyyaml>=6.0",
19
20
  ]
@@ -1,8 +1,12 @@
1
1
  """AdLoop — MCP server connecting Google Ads + GA4 + codebase."""
2
2
 
3
3
  import sys
4
+ from importlib.metadata import PackageNotFoundError, version
4
5
 
5
- __version__ = "0.9.0"
6
+ try:
7
+ __version__ = version("adloop")
8
+ except PackageNotFoundError: # running from a source tree without install
9
+ __version__ = "0.0.0.dev0"
6
10
 
7
11
 
8
12
  def main() -> None: