feat: add Google Search Console integration (closes #18)#20
feat: add Google Search Console integration (closes #18)#20ZLeventer wants to merge 1 commit intokLOsk:mainfrom
Conversation
kLOsk
left a comment
There was a problem hiding this comment.
Thanks for filing this — GSC is a real and frequently-requested integration (it closes #18), and you've put together the basic plumbing well. However there's a major gating concern plus several blocking code issues, so this PR is on hold for now.
Major: this would set back our pending Google OAuth verification
We're currently in the middle of getting the AdLoop OAuth consent screen verified by Google. Adding a new OAuth scope (webmasters.readonly) to _ALL_SCOPES and _GA4_SCOPES mid-verification will at minimum require:
- Re-submission with updated scope justification
- An updated demo video showing the new scope's actual use
- A reset of the review timeline (typically extends it by weeks)
webmasters.readonly is a "sensitive" scope (not "restricted", so the bar is lower than the most expensive verifications), but adding any new scope during an active review effectively restarts that review. Given the impact on every existing user (their tokens would invalidate the moment we cut a release pre-verification), we can't merge this until verification lands.
The path forward we'd prefer:
- Complete the current verification with the existing scope set.
- After verification lands, merge GSC as a follow-up — Google's re-verification for adding a single sensitive scope to an already-verified app is typically much faster than first-time verification.
So this PR is paused regardless of the code review below. We'll revisit the moment our app is verified — really sorry about the timing, this isn't on you.
Blocking code issues (please address before we revisit post-verification)
- Duplicate import bug.
from adloop.gsc.client import get_gsc_clientis imported twice insiderun_gsc_report— once at the top of the function and again right beforeclient = get_gsc_client(config). The second one is dead code and should be removed. - Zero tests. The same review batch (#21, #22) ships with 13 and 10 new tests respectively. At minimum we'd want:
_resolve_datetests (ISO passthrough,today,yesterday,NdaysAgo, unknown passthrough).- A
run_gsc_reportbody-construction test using a fakegoogleapiclientclient. - The
site_urlprecedence test (param > config > error).
.cursor/rules/adloop.mdcis not updated. This file is canonical for tool orchestration. Without entries forlist_gsc_sitesandrun_gsc_reportplus an orchestration pattern ("When user asks about organic performance"), the LLM won't know these tools exist or when to use them — the PR effectively ships dead tools as far as agent behavior is concerned.adloop initwizard not updated. New users won't be prompted forgsc.site_urland will have to hand-edit~/.adloop/config.yaml. Inconsistent with how every other config field is handled.
Non-blocking but recommended
- Cross-reference value left on the table. The most valuable use of GSC is correlating it with paid + GA4 — e.g. "queries where I'm paying for paid clicks AND already ranking organically" (paid/organic cannibalisation analysis). A
crossref.pyaddition for this would be the killer feature; raw GSC reports are useful but the integration story is much stronger with a cross-ref tool. sc-domain:vshttps://site URL footgun. A small_normalize_site_urlhelper that detects bare domains and suggests the prefix would save users a confusing "site not found" error.- Re-auth UX. When this eventually lands, we'll need a CHANGELOG entry and ideally an
adloop init --reauthhelper, since adding the scope invalidates existing tokens. Literaltyping in server signature.search_type: strinserver.pyaccepts anything;Literal[...]would surface invalid values to MCP clients that introspect the tool schema.- Architecture nit:
gsc/client.pyis 5 lines wrappingbuild()— fine to fold intoreports.pyuntil there's a second consumer.
Thanks again for the work — this is a good first cut and we genuinely want to land it. Just need to wait for Google verification first, and the code-quality items above will need addressing when we pick it back up.
Summary
Adds Google Search Console (GSC) integration to AdLoop, closing #18. Users can now query organic search performance alongside their Google Ads and GA4 data in a single MCP session.
New tools
list_gsc_sitesrun_gsc_reportrun_gsc_reportsupports all GSC dimensions (query,page,country,device,date), search types (web,image,video,news,discover,googleNews), dimension filter groups, and relative date shorthands (7daysAgo,30daysAgo,today).Changes
src/adloop/gsc/(new package) —client.pywrapsgoogleapiclient.discovery.build("searchconsole", "v1")using the existingget_ga4_credentialsauth flow;reports.pyimplementslist_gsc_sitesandrun_gsc_reportsrc/adloop/auth.py— addedwebmasters.readonlyscope to_ALL_SCOPESand_GA4_SCOPESso GSC auth is covered by the existing single OAuth tokensrc/adloop/config.py— addedGscConfig(site_url)dataclass and wired it intoAdLoopConfigandload_configsrc/adloop/server.py— registeredlist_gsc_sitesandrun_gsc_reportas@mcp.tool(annotations=_READONLY)tools in a new GSC sectionpyproject.toml— addedgoogle-api-python-client>=2.0.0dependencyconfig.yaml.example— addedgsc.site_urlwith inline docsAuth note
GSC uses the same OAuth token as GA4 — no separate login step. The
webmasters.readonlyscope is added to the existing token request, so existing users will be prompted once to re-authorize on next use.Usage example