Skip to content

feat: add Google Search Console integration (closes #18)#20

Open
ZLeventer wants to merge 1 commit intokLOsk:mainfrom
ZLeventer:feat/gsc-integration
Open

feat: add Google Search Console integration (closes #18)#20
ZLeventer wants to merge 1 commit intokLOsk:mainfrom
ZLeventer:feat/gsc-integration

Conversation

@ZLeventer
Copy link
Copy Markdown

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

Tool Description
list_gsc_sites Lists all GSC properties the authenticated user can access
run_gsc_report Runs a search analytics report returning clicks, impressions, CTR, and position

run_gsc_report supports 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.py wraps googleapiclient.discovery.build("searchconsole", "v1") using the existing get_ga4_credentials auth flow; reports.py implements list_gsc_sites and run_gsc_report
  • src/adloop/auth.py — added webmasters.readonly scope to _ALL_SCOPES and _GA4_SCOPES so GSC auth is covered by the existing single OAuth token
  • src/adloop/config.py — added GscConfig(site_url) dataclass and wired it into AdLoopConfig and load_config
  • src/adloop/server.py — registered list_gsc_sites and run_gsc_report as @mcp.tool(annotations=_READONLY) tools in a new GSC section
  • pyproject.toml — added google-api-python-client>=2.0.0 dependency
  • config.yaml.example — added gsc.site_url with inline docs

Auth note

GSC uses the same OAuth token as GA4 — no separate login step. The webmasters.readonly scope is added to the existing token request, so existing users will be prompted once to re-authorize on next use.

Usage example

List my Google Search Console sites
Show top 20 queries by clicks for my site for the last 30 days
What pages get the most impressions but lowest CTR on my site?

Copy link
Copy Markdown
Owner

@kLOsk kLOsk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Complete the current verification with the existing scope set.
  2. 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)

  1. Duplicate import bug. from adloop.gsc.client import get_gsc_client is imported twice inside run_gsc_report — once at the top of the function and again right before client = get_gsc_client(config). The second one is dead code and should be removed.
  2. Zero tests. The same review batch (#21, #22) ships with 13 and 10 new tests respectively. At minimum we'd want:
    • _resolve_date tests (ISO passthrough, today, yesterday, NdaysAgo, unknown passthrough).
    • A run_gsc_report body-construction test using a fake googleapiclient client.
    • The site_url precedence test (param > config > error).
  3. .cursor/rules/adloop.mdc is not updated. This file is canonical for tool orchestration. Without entries for list_gsc_sites and run_gsc_report plus 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.
  4. adloop init wizard not updated. New users won't be prompted for gsc.site_url and 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.py addition 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: vs https:// site URL footgun. A small _normalize_site_url helper 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 --reauth helper, since adding the scope invalidates existing tokens.
  • Literal typing in server signature. search_type: str in server.py accepts anything; Literal[...] would surface invalid values to MCP clients that introspect the tool schema.
  • Architecture nit: gsc/client.py is 5 lines wrapping build() — fine to fold into reports.py until 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants