feat(connectors/google-ads): first-class Google Ads primitives + MCP/HTTP-SSE + Temporal#18
Merged
Conversation
…HTTP-SSE + Temporal Performance Ingest source #1 for the Thompson Sampling loop. Mirrors connectors/shopify exactly (primitives/, mcp/, temporal/; HTTP/SSE + bearer auth; Temporal idempotency). Four primitives: 1. ingestPerformance() — GAQL campaign/ad_group/ad metrics → one JSONL/day of typed PerformanceObservation (roas = conversions_value/cost). Dedup by (date, campaign_id, ad_group_id, ad_id). Shopify join via shoploop.gads_campaign_id. 2. MCP (7 tools on :8788) — list_campaigns, get_campaign, get_performance (daily/weekly), get_search_terms, get_change_history; pause/resume gated behind SHOPLOOP_GADS_WRITE_ENABLED. 3. recordPerformanceForBandit() — enqueues observations to BANDIT_QUEUE_PATH; Thompson Sampling consumer stubbed with a clear TODO. 4. Temporal — IngestGoogleAdsPerformanceWorkflow (hourly, last 25h overlap) + PauseUnderperformingCampaignWorkflow (no-op behind write flag). Idempotency id gads:<customerId>:<workflow>:<hour_bucket>. CLI worker --once for dev. Cost: ~$0/mo incremental (Basic Access free, JSONL <$1, co-located on Shopify t4g.small). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ninan-versioning
force-pushed
the
jared-shoploop/google-ads-connector
branch
from
June 26, 2026 06:00
2946797 to
0dc96dc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
@shoploop/connector-google-ads— the first Performance Ingest source, closing the right half of the architecture loop:Mirrors
connectors/shopify(PR #15) exactly: sameprimitives/·mcp/·temporal/layout, same HTTP/SSE transport, same bearer auth (SHOPLOOP_MCP_TOKEN), same single-internal-core discipline, same Temporal idempotency convention. MCP-shaped and agent-callable — not a thin REST wrapper. Usesgoogle-ads-api(Google Ads API v17, OAuth2 refresh-token flow, env-driven creds).Four primitives
ingestPerformance({ customerId, since, outDir }). GAQL across campaign/ad_group/ad (impressions, clicks, conversions, cost_micros, ctr, average_cpc, derived roas = conversions_value / cost) → one JSONL/day at<outDir>/<customerId>/YYYY-MM-DD.jsonlmatchingcontracts/performance_observation.v1.json. Dedupes by(date, campaign_id, ad_group_id, ad_id). Joinscampaign.id ↔ Shopify product.idvia theshoploop.gads_campaign_idmetafield.list_campaigns,get_campaign,get_performance(daily/weekly rollups),get_search_terms(negative-keyword harvesting),get_change_history; write toolspause_campaign/resume_campaigngated behindSHOPLOOP_GADS_WRITE_ENABLED=true(default off, refused otherwise).recordPerformanceForBandit(observation)enqueues toBANDIT_QUEUE_PATH. Thompson Sampling update is stubbed with a clear TODO; the typedPerformanceObservationis the producer/consumer contract so a real bandit drops in without changing the producer side.IngestGoogleAdsPerformanceWorkflow(customerId)(hourly, queries last 25h overlap, flushes observations to the bandit queue) +PauseUnderperformingCampaignWorkflow(customerId, campaignId, reason)(typed entry point, no-op behind the write flag). Worker on task queueshoploop-google-ads; idempotency idgads:<customerId>:<workflow>:<hour_bucket>. CLIworker --once --customer <id>runs the hourly workflow on-demand for dev.CLI binaries:
shoploop-google-ads ingest|mcp|worker.Decision gate (per AGENTS.md)
brand_rules.json; ingest + signal, no commodity ad-buying.t4g.small.Testing
npm run typecheck— clean.npm test— 23 passing across 3 files:primitives.test.ts— ingest emits 7 JSONL files / 56 observations (8/day), contract shape + derived roas, overlap dedup, Shopify join, rollups (daily + Monday-weekly), bandit enqueue, join metafield write.mcp.test.ts— in-proc over MCP transport: all 7 tools listed, each read tool exercised, write tools refused when the flag is off and succeed when on.workflow.test.ts— Temporal hour-bucket idempotency (duplicate hourly fires collapse to one execution under REJECT_DUPLICATE; next hour starts fresh).npm run build— emitsdist/(CLI bin →dist/cli.js).Varitea dogfood acceptance
shoploop-google-ads ingest --customer 5128873230 --since 2026-06-12 --out ./var/shoploop/perf→ JSONL emitted per day under5128873230/.get_performancereturns the 7-day daily rollup (and a Monday-keyed weekly rollup) with derived ROAS.Test plan
🤖 Generated with Claude Code