adloop 0.9.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.
- {adloop-0.9.0 → adloop-0.11.0}/PKG-INFO +71 -21
- {adloop-0.9.0 → adloop-0.11.0}/README.md +69 -20
- {adloop-0.9.0 → adloop-0.11.0}/pyproject.toml +2 -1
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/__init__.py +14 -1
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/gaql.py +38 -1
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/read.py +400 -5
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/write.py +527 -15
- adloop-0.11.0/src/adloop/auth.py +290 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/cli.py +26 -50
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/config.py +1 -1
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/crossref.py +262 -0
- adloop-0.11.0/src/adloop/gtm/__init__.py +1 -0
- adloop-0.11.0/src/adloop/gtm/client.py +20 -0
- adloop-0.11.0/src/adloop/gtm/read.py +648 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/adloop.md +70 -4
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/commands/analyze-performance.md +2 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/commands/optimize-campaign.md +2 -0
- adloop-0.11.0/src/adloop/runtime.py +127 -0
- adloop-0.11.0/src/adloop/safety/audit.py +78 -0
- adloop-0.11.0/src/adloop/safety/preview.py +110 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/server.py +499 -101
- adloop-0.9.0/src/adloop/auth.py +0 -196
- adloop-0.9.0/src/adloop/bundled_credentials.json +0 -13
- adloop-0.9.0/src/adloop/safety/audit.py +0 -40
- adloop-0.9.0/src/adloop/safety/preview.py +0 -58
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/__main__.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/client.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/currency.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/enums.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/forecast.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/diagnostics.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ga4/client.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ga4/reports.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/ga4/tracking.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/rules_install.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.9.0 → adloop-0.11.0}/src/adloop/safety/guards.py +0 -0
- {adloop-0.9.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.
|
|
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
|
|
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 |
|
|
@@ -196,12 +244,11 @@ AdLoop manages real ad spend, so safety is not optional.
|
|
|
196
244
|
|
|
197
245
|
## Setup
|
|
198
246
|
|
|
199
|
-
>
|
|
200
|
-
> Google limits unverified OAuth apps to 100 users, and AdLoop has reached that cap. New users will see a **"This app is blocked"** error if they pick the built-in option in the wizard.
|
|
247
|
+
> **AdLoop uses your own (free) Google Cloud project for OAuth.** The `adloop init` wizard walks you through it — a one-time setup of about 5 minutes, with no shared user caps and no waiting on anyone's verification review. AdLoop does not ship built-in OAuth credentials.
|
|
201
248
|
>
|
|
202
|
-
> **
|
|
249
|
+
> **Prefer zero setup?** [**AdLoop Cloud**](https://getadloop.com) is the hosted version: connect Google in two clicks — no Cloud project, no developer token, EU-hosted.
|
|
203
250
|
>
|
|
204
|
-
> *(
|
|
251
|
+
> *(Upgrading from ≤0.9 with built-in credentials? Those sign-ins were retired in 0.10 — run `adloop init` once to switch to your own project.)*
|
|
205
252
|
|
|
206
253
|
### Install
|
|
207
254
|
|
|
@@ -223,7 +270,7 @@ uv run adloop init
|
|
|
223
270
|
|
|
224
271
|
### What `adloop init` does
|
|
225
272
|
|
|
226
|
-
The wizard
|
|
273
|
+
The wizard walks you through:
|
|
227
274
|
|
|
228
275
|
1. **Google Cloud setup** — creates a project, enables the three APIs, generates an OAuth client (see [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) below for the exact steps the wizard refers you to)
|
|
229
276
|
2. **Developer token** — from your Google Ads MCC ([API Center](https://ads.google.com/aw/apicenter))
|
|
@@ -233,8 +280,6 @@ The wizard defaults to the "bring your own Google Cloud project" path while veri
|
|
|
233
280
|
6. **Safety defaults** — budget cap and dry-run preference
|
|
234
281
|
7. **Editor config snippets** — prints MCP configuration for both Cursor and Claude Code
|
|
235
282
|
|
|
236
|
-
The wizard does still offer AdLoop's built-in credentials as a non-default option for existing users whose tokens predate the cap. Picking that option for a brand-new Google account will fail at the consent screen — the wizard warns you about this before you choose.
|
|
237
|
-
|
|
238
283
|
### Requirements
|
|
239
284
|
|
|
240
285
|
- Python 3.11+
|
|
@@ -243,7 +288,7 @@ The wizard does still offer AdLoop's built-in credentials as a non-default optio
|
|
|
243
288
|
|
|
244
289
|
### Google Ads Developer Token
|
|
245
290
|
|
|
246
|
-
A developer token is **always required
|
|
291
|
+
A developer token is **always required**. Your OAuth client handles Google sign-in; the developer token is a separate key that grants API access to your Google Ads data.
|
|
247
292
|
|
|
248
293
|
1. **Create an MCC** (free) at [ads.google.com/home/tools/manager-accounts](https://ads.google.com/home/tools/manager-accounts/) if you don't have one. Link your regular Google Ads account to it.
|
|
249
294
|
2. In the MCC, go to **Tools & Settings → API Center**
|
|
@@ -265,7 +310,7 @@ Running on a server without a browser (VMs, Docker, SSH)? The wizard automatical
|
|
|
265
310
|
|
|
266
311
|
### Custom Google Cloud Project Setup
|
|
267
312
|
|
|
268
|
-
|
|
313
|
+
The wizard refers to these steps — do them in your browser before running `adloop init` (or while it waits at the OAuth prompt).
|
|
269
314
|
|
|
270
315
|
#### Step 1 — Google Cloud Project
|
|
271
316
|
|
|
@@ -347,6 +392,7 @@ Ask your AI assistant things like:
|
|
|
347
392
|
- *"Draft a new responsive search ad for my main campaign."*
|
|
348
393
|
- *"Which landing pages get paid traffic but don't convert?"*
|
|
349
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?"*
|
|
350
396
|
- *"What keywords should I target for [product]? Find ideas and estimate the budget."*
|
|
351
397
|
- *"How much budget would I need for these keywords in Germany?"*
|
|
352
398
|
- *"Create a new search campaign for [product feature] with a €20/day budget."*
|
|
@@ -358,7 +404,7 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
|
|
|
358
404
|
| Section | Key | Default | Description |
|
|
359
405
|
|---------|-----|---------|-------------|
|
|
360
406
|
| `google` | `project_id` | *(empty)* | Google Cloud project ID (only needed with custom credentials) |
|
|
361
|
-
| `google` | `credentials_path` | *(empty
|
|
407
|
+
| `google` | `credentials_path` | *(empty)* | Path to OAuth client JSON or service account key. Empty = `~/.adloop/credentials.json`, else Application Default Credentials. |
|
|
362
408
|
| `google` | `token_path` | `~/.adloop/token.json` | Where to store the OAuth token (auto-created) |
|
|
363
409
|
| `ga4` | `property_id` | — | Your GA4 property ID (auto-discovered by `adloop init`) |
|
|
364
410
|
| `ads` | `developer_token` | — | Your Google Ads API developer token |
|
|
@@ -373,11 +419,11 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
|
|
|
373
419
|
```
|
|
374
420
|
src/adloop/
|
|
375
421
|
├── __init__.py # Entry point — routes 'adloop init' to wizard, otherwise starts MCP server
|
|
376
|
-
├── server.py # FastMCP server —
|
|
422
|
+
├── server.py # FastMCP server — 61 tool registrations with safety annotations
|
|
377
423
|
├── config.py # Config loader (~/.adloop/config.yaml)
|
|
378
|
-
├── auth.py # OAuth 2.0 flow (
|
|
424
|
+
├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts; GA4 / Ads / GTM scopes
|
|
379
425
|
├── cli.py # Interactive 'adloop init' setup wizard
|
|
380
|
-
├── crossref.py # Cross-reference tools (GA4 + Ads combined analysis)
|
|
426
|
+
├── crossref.py # Cross-reference tools (GA4 + Ads + GTM combined analysis)
|
|
381
427
|
├── tracking.py # Tracking validation + code generation tools
|
|
382
428
|
├── ga4/
|
|
383
429
|
│ ├── client.py # GA4 Data + Admin API clients
|
|
@@ -390,6 +436,9 @@ src/adloop/
|
|
|
390
436
|
│ ├── pmax.py # Performance Max tools — campaign/asset group performance, asset labels, top combinations
|
|
391
437
|
│ ├── write.py # Draft campaign, RSA, keywords; pause, enable, remove, confirm
|
|
392
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
|
|
393
442
|
└── safety/
|
|
394
443
|
├── guards.py # Budget caps, bid limits, blocked operations, Broad Match safety
|
|
395
444
|
├── preview.py # Change plans and previews
|
|
@@ -411,9 +460,10 @@ What's been shipped and what's next:
|
|
|
411
460
|
- ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
|
|
412
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
|
|
413
462
|
- ~~PyPI package~~ ✓ — `pip install adloop`
|
|
414
|
-
- ~~
|
|
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)
|
|
415
464
|
- ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
|
|
416
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
|
|
417
467
|
- **Community launch** — HN, Indie Hackers, r/cursor, Twitter
|
|
418
468
|
- **Video walkthrough**
|
|
419
469
|
|
|
@@ -427,14 +477,14 @@ MIT — see [LICENSE](LICENSE).
|
|
|
427
477
|
|
|
428
478
|
## Privacy
|
|
429
479
|
|
|
430
|
-
|
|
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).
|
|
431
481
|
|
|
432
482
|
---
|
|
433
483
|
|
|
434
484
|
<div align="center">
|
|
435
485
|
|
|
436
|
-
**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).**
|
|
437
487
|
|
|
438
|
-
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)
|
|
439
489
|
|
|
440
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)** · 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
|
|
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 |
|
|
@@ -172,12 +219,11 @@ AdLoop manages real ad spend, so safety is not optional.
|
|
|
172
219
|
|
|
173
220
|
## Setup
|
|
174
221
|
|
|
175
|
-
>
|
|
176
|
-
> Google limits unverified OAuth apps to 100 users, and AdLoop has reached that cap. New users will see a **"This app is blocked"** error if they pick the built-in option in the wizard.
|
|
222
|
+
> **AdLoop uses your own (free) Google Cloud project for OAuth.** The `adloop init` wizard walks you through it — a one-time setup of about 5 minutes, with no shared user caps and no waiting on anyone's verification review. AdLoop does not ship built-in OAuth credentials.
|
|
177
223
|
>
|
|
178
|
-
> **
|
|
224
|
+
> **Prefer zero setup?** [**AdLoop Cloud**](https://getadloop.com) is the hosted version: connect Google in two clicks — no Cloud project, no developer token, EU-hosted.
|
|
179
225
|
>
|
|
180
|
-
> *(
|
|
226
|
+
> *(Upgrading from ≤0.9 with built-in credentials? Those sign-ins were retired in 0.10 — run `adloop init` once to switch to your own project.)*
|
|
181
227
|
|
|
182
228
|
### Install
|
|
183
229
|
|
|
@@ -199,7 +245,7 @@ uv run adloop init
|
|
|
199
245
|
|
|
200
246
|
### What `adloop init` does
|
|
201
247
|
|
|
202
|
-
The wizard
|
|
248
|
+
The wizard walks you through:
|
|
203
249
|
|
|
204
250
|
1. **Google Cloud setup** — creates a project, enables the three APIs, generates an OAuth client (see [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) below for the exact steps the wizard refers you to)
|
|
205
251
|
2. **Developer token** — from your Google Ads MCC ([API Center](https://ads.google.com/aw/apicenter))
|
|
@@ -209,8 +255,6 @@ The wizard defaults to the "bring your own Google Cloud project" path while veri
|
|
|
209
255
|
6. **Safety defaults** — budget cap and dry-run preference
|
|
210
256
|
7. **Editor config snippets** — prints MCP configuration for both Cursor and Claude Code
|
|
211
257
|
|
|
212
|
-
The wizard does still offer AdLoop's built-in credentials as a non-default option for existing users whose tokens predate the cap. Picking that option for a brand-new Google account will fail at the consent screen — the wizard warns you about this before you choose.
|
|
213
|
-
|
|
214
258
|
### Requirements
|
|
215
259
|
|
|
216
260
|
- Python 3.11+
|
|
@@ -219,7 +263,7 @@ The wizard does still offer AdLoop's built-in credentials as a non-default optio
|
|
|
219
263
|
|
|
220
264
|
### Google Ads Developer Token
|
|
221
265
|
|
|
222
|
-
A developer token is **always required
|
|
266
|
+
A developer token is **always required**. Your OAuth client handles Google sign-in; the developer token is a separate key that grants API access to your Google Ads data.
|
|
223
267
|
|
|
224
268
|
1. **Create an MCC** (free) at [ads.google.com/home/tools/manager-accounts](https://ads.google.com/home/tools/manager-accounts/) if you don't have one. Link your regular Google Ads account to it.
|
|
225
269
|
2. In the MCC, go to **Tools & Settings → API Center**
|
|
@@ -241,7 +285,7 @@ Running on a server without a browser (VMs, Docker, SSH)? The wizard automatical
|
|
|
241
285
|
|
|
242
286
|
### Custom Google Cloud Project Setup
|
|
243
287
|
|
|
244
|
-
|
|
288
|
+
The wizard refers to these steps — do them in your browser before running `adloop init` (or while it waits at the OAuth prompt).
|
|
245
289
|
|
|
246
290
|
#### Step 1 — Google Cloud Project
|
|
247
291
|
|
|
@@ -323,6 +367,7 @@ Ask your AI assistant things like:
|
|
|
323
367
|
- *"Draft a new responsive search ad for my main campaign."*
|
|
324
368
|
- *"Which landing pages get paid traffic but don't convert?"*
|
|
325
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?"*
|
|
326
371
|
- *"What keywords should I target for [product]? Find ideas and estimate the budget."*
|
|
327
372
|
- *"How much budget would I need for these keywords in Germany?"*
|
|
328
373
|
- *"Create a new search campaign for [product feature] with a €20/day budget."*
|
|
@@ -334,7 +379,7 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
|
|
|
334
379
|
| Section | Key | Default | Description |
|
|
335
380
|
|---------|-----|---------|-------------|
|
|
336
381
|
| `google` | `project_id` | *(empty)* | Google Cloud project ID (only needed with custom credentials) |
|
|
337
|
-
| `google` | `credentials_path` | *(empty
|
|
382
|
+
| `google` | `credentials_path` | *(empty)* | Path to OAuth client JSON or service account key. Empty = `~/.adloop/credentials.json`, else Application Default Credentials. |
|
|
338
383
|
| `google` | `token_path` | `~/.adloop/token.json` | Where to store the OAuth token (auto-created) |
|
|
339
384
|
| `ga4` | `property_id` | — | Your GA4 property ID (auto-discovered by `adloop init`) |
|
|
340
385
|
| `ads` | `developer_token` | — | Your Google Ads API developer token |
|
|
@@ -349,11 +394,11 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
|
|
|
349
394
|
```
|
|
350
395
|
src/adloop/
|
|
351
396
|
├── __init__.py # Entry point — routes 'adloop init' to wizard, otherwise starts MCP server
|
|
352
|
-
├── server.py # FastMCP server —
|
|
397
|
+
├── server.py # FastMCP server — 61 tool registrations with safety annotations
|
|
353
398
|
├── config.py # Config loader (~/.adloop/config.yaml)
|
|
354
|
-
├── auth.py # OAuth 2.0 flow (
|
|
399
|
+
├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts; GA4 / Ads / GTM scopes
|
|
355
400
|
├── cli.py # Interactive 'adloop init' setup wizard
|
|
356
|
-
├── crossref.py # Cross-reference tools (GA4 + Ads combined analysis)
|
|
401
|
+
├── crossref.py # Cross-reference tools (GA4 + Ads + GTM combined analysis)
|
|
357
402
|
├── tracking.py # Tracking validation + code generation tools
|
|
358
403
|
├── ga4/
|
|
359
404
|
│ ├── client.py # GA4 Data + Admin API clients
|
|
@@ -366,6 +411,9 @@ src/adloop/
|
|
|
366
411
|
│ ├── pmax.py # Performance Max tools — campaign/asset group performance, asset labels, top combinations
|
|
367
412
|
│ ├── write.py # Draft campaign, RSA, keywords; pause, enable, remove, confirm
|
|
368
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
|
|
369
417
|
└── safety/
|
|
370
418
|
├── guards.py # Budget caps, bid limits, blocked operations, Broad Match safety
|
|
371
419
|
├── preview.py # Change plans and previews
|
|
@@ -387,9 +435,10 @@ What's been shipped and what's next:
|
|
|
387
435
|
- ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
|
|
388
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
|
|
389
437
|
- ~~PyPI package~~ ✓ — `pip install adloop`
|
|
390
|
-
- ~~
|
|
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)
|
|
391
439
|
- ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
|
|
392
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
|
|
393
442
|
- **Community launch** — HN, Indie Hackers, r/cursor, Twitter
|
|
394
443
|
- **Video walkthrough**
|
|
395
444
|
|
|
@@ -403,14 +452,14 @@ MIT — see [LICENSE](LICENSE).
|
|
|
403
452
|
|
|
404
453
|
## Privacy
|
|
405
454
|
|
|
406
|
-
|
|
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).
|
|
407
456
|
|
|
408
457
|
---
|
|
409
458
|
|
|
410
459
|
<div align="center">
|
|
411
460
|
|
|
412
|
-
**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).**
|
|
413
462
|
|
|
414
|
-
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)
|
|
415
464
|
|
|
416
465
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "adloop"
|
|
3
|
-
version = "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
|
-
|
|
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:
|
|
@@ -42,6 +46,15 @@ def main() -> None:
|
|
|
42
46
|
sys.exit(130)
|
|
43
47
|
return
|
|
44
48
|
|
|
49
|
+
# Process-global side effects (signal handlers, heartbeat thread, and
|
|
50
|
+
# the stdio cancellation-race monkeypatch) are deliberately installed
|
|
51
|
+
# here — in the stdio entry point — rather than at adloop.server import
|
|
52
|
+
# time, so embedding the server in an ASGI app stays side-effect-free.
|
|
53
|
+
from adloop import _mcp_patches, diagnostics
|
|
54
|
+
|
|
55
|
+
diagnostics.install()
|
|
56
|
+
_mcp_patches.install()
|
|
57
|
+
|
|
45
58
|
from adloop.server import mcp
|
|
46
59
|
|
|
47
60
|
mcp.run()
|
|
@@ -89,7 +89,29 @@ _GAQL_ERROR_HINTS = {
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def _parse_gaql_error(exc: Exception) -> str:
|
|
92
|
-
"""Extract a human-readable message from Google Ads gRPC errors.
|
|
92
|
+
"""Extract a human-readable message from Google Ads gRPC errors.
|
|
93
|
+
|
|
94
|
+
Prefers the structured ``failure.errors[]`` a GoogleAdsException
|
|
95
|
+
carries — Google's own message names the exact field/clause at
|
|
96
|
+
fault (e.g. PROHIBITED_FIELD_IN_SELECT_CLAUSE with the field name),
|
|
97
|
+
which is far more actionable than a generic hint. Known error codes
|
|
98
|
+
still get the hint appended as a suffix.
|
|
99
|
+
"""
|
|
100
|
+
failure = getattr(exc, "failure", None)
|
|
101
|
+
errors = getattr(failure, "errors", None) if failure is not None else None
|
|
102
|
+
if errors:
|
|
103
|
+
parts = []
|
|
104
|
+
for err in errors:
|
|
105
|
+
code = str(getattr(err, "error_code", "") or "").strip()
|
|
106
|
+
message = str(getattr(err, "message", "") or "").strip()
|
|
107
|
+
parts.append(" — ".join(p for p in (code, message) if p))
|
|
108
|
+
detail = " | ".join(p for p in parts if p)
|
|
109
|
+
if detail:
|
|
110
|
+
for known, hint in _GAQL_ERROR_HINTS.items():
|
|
111
|
+
if known in detail:
|
|
112
|
+
return f"{detail} (hint: {hint})"
|
|
113
|
+
return detail
|
|
114
|
+
|
|
93
115
|
raw = str(exc)
|
|
94
116
|
for code, hint in _GAQL_ERROR_HINTS.items():
|
|
95
117
|
if code in raw:
|
|
@@ -137,6 +159,21 @@ def _to_python(obj: object) -> object:
|
|
|
137
159
|
# AdTextAsset and similar message types
|
|
138
160
|
if hasattr(obj, "text") and isinstance(getattr(obj, "text", None), str):
|
|
139
161
|
return obj.text
|
|
162
|
+
# Nested proto-plus messages (targeting settings, criteria, ...) —
|
|
163
|
+
# serialize to a dict instead of collapsing to their str() repr.
|
|
164
|
+
try:
|
|
165
|
+
import proto
|
|
166
|
+
|
|
167
|
+
if isinstance(obj, proto.Message):
|
|
168
|
+
return type(obj).to_dict(
|
|
169
|
+
obj, preserving_proto_field_name=True, use_integers_for_enums=False
|
|
170
|
+
)
|
|
171
|
+
except ImportError:
|
|
172
|
+
pass
|
|
173
|
+
if hasattr(obj, "DESCRIPTOR"):
|
|
174
|
+
from google.protobuf.json_format import MessageToDict
|
|
175
|
+
|
|
176
|
+
return MessageToDict(obj, preserving_proto_field_name=True)
|
|
140
177
|
return str(obj)
|
|
141
178
|
|
|
142
179
|
|