AdLoop

Google Ads, Reddit Ads, GA4, Tag Manager and Search Console reports and edits from an AI client

By Daniel Klose (individual) Python Remote (hosted) · Streamable HTTPLocal · stdio CRM and salesData and analytics

59 popularity score ★ 296 ↓ 2.5k/mo

Connect your client

Remote (hosted)

Add in one click

Opens the app, which asks before it adds the server.

Or add it by command or config

Claude Codeterminal; then run /mcp, pick AdLoop and choose Authenticate
claude mcp add --transport http adloop https://mcp.getadloop.com/mcp
Cursor.cursor/mcp.json or ~/.cursor/mcp.json, without headers to sign in with OAuth
{
  "mcpServers": {
    "adloop": {
      "url": "https://mcp.getadloop.com/mcp"
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "adloop": {
      "type": "http",
      "url": "https://mcp.getadloop.com/mcp"
    }
  }
}
Codexterminal; then codex mcp login adloop
codex mcp add adloop --url https://mcp.getadloop.com/mcp
Claude (desktop and web)claude.ai: Customize → Connectors → Add custom connector
https://mcp.getadloop.com/mcp
ChatGPTcustom plugin with OAuth, developer mode on
https://mcp.getadloop.com/mcp
Local (PyPI)

Add in one click

Opens the app, which asks before it adds the server.

Or add it by command or config

FirstTerminal
pip install adloop
adloop init
Claude CodeTerminal
claude mcp add adloop -- python -m adloop
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "adloop": {
      "command": "python",
      "args": [
        "-m",
        "adloop"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "adloop": {
      "command": "python",
      "args": [
        "-m",
        "adloop"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "adloop": {
      "type": "stdio",
      "command": "python",
      "args": [
        "-m",
        "adloop"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.adloop]
command = "python"
args = ["-m", "adloop"]
Gemini CLITerminal
gemini mcp add --scope user adloop python -- -m adloop
Local (from source)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

FirstTerminal
git clone https://github.com/kLOsk/adloop.git
cd adloop
uv sync
uv run adloop init
Claude CodeTerminal
claude mcp add adloop -- '<ABSOLUTE_PATH_TO_CLONE>/.venv/bin/python' -m adloop
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "adloop": {
      "command": "<ABSOLUTE_PATH_TO_CLONE>/.venv/bin/python",
      "args": [
        "-m",
        "adloop"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "adloop": {
      "command": "<ABSOLUTE_PATH_TO_CLONE>/.venv/bin/python",
      "args": [
        "-m",
        "adloop"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "adloop": {
      "type": "stdio",
      "command": "<ABSOLUTE_PATH_TO_CLONE>/.venv/bin/python",
      "args": [
        "-m",
        "adloop"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.adloop]
command = "<ABSOLUTE_PATH_TO_CLONE>/.venv/bin/python"
args = ["-m", "adloop"]
Gemini CLITerminal
gemini mcp add --scope user adloop '<ABSOLUTE_PATH_TO_CLONE>/.venv/bin/python' -- -m adloop

Settings 2

ADLOOP_TOOLSETS Optional: Comma-separated toolsets to expose: ads, ads:read, ads:write, ads:plan, ga4, tracking, gtm, gsc, web, merchant, reddit. Unset exposes the full catalog.
ADLOOP_CONFIG Optional: Path to the config file holding accounts and safety settings. Default ~/.adloop/config.yaml.

What it does

AdLoop connects an AI client to the accounts a paid-ads marketer works in every day. It reads Google Ads, GA4, Tag Manager, Search Console, Merchant Center and PageSpeed Insights, and it can also manage Reddit Ads. The useful part is the joins. You can line up Ads clicks with GA4 sessions and conversions per campaign, find landing pages that take paid traffic but never convert, or compare the event names in your site's code with your GTM tags and with what GA4 actually receives. EU consent gaps are allowed for, so the usual shortfall from people rejecting cookies does not show up as broken tracking.

Edits never happen in one step. A draft tool returns a preview with a plan id, and confirm_and_apply carries it out. That call is a dry run unless dry_run=false is passed, and even then config.py ships with safety.require_dry_run set to true, so a fresh self-hosted install refuses real writes until you turn that off. Other checks in the code: new campaigns are created paused, daily budgets above safety.max_daily_budget (50 by default) are refused, and bid raises are capped at 100 percent by max_bid_increase_pct. Each operation goes to ~/.adloop/audit.log. In clients that render MCP Apps an approval card appears, and the model's own confirm_and_apply(dry_run=false) is turned away so only your click applies the change.

Version 0.37.0 registers 155 tools in server.py. Two of them, approve_change and store_image_upload, are only visible to the approval and upload cards, which leaves 153 for the model. The README's tables group most but not all of them. Recent additions cover more Google Ads asset types: price, promotion, call and business name assets, edits to existing sitelinks, callouts and snippets, ad schedules, and GTM variables.

The same tools run in two places. This MIT package runs on your machine against your own Google Cloud project. AdLoop Cloud at https://mcp.getadloop.com/mcp is the maker's hosted service in Germany, with a free tier and paid plans.

What it needs

To run it yourselfPython 3.11+
Runs onmacOS, Windows, Linux
Google AdsAds API access granted to your own Google Cloud project, Explorer level or higher for live accounts
Google CloudFree tierYour own project with billing set up and an OAuth desktop client or service account key
Reddit AdsOptional. Needs your own Reddit developer app
AdLoop CloudFree tierOnly for the hosted endpoint. Free plan, paid plans for more volume and accounts

Self-hosted: save the OAuth client JSON or service account key as ~/.adloop/credentials.json, enable the Analytics Data, Analytics Admin and Google Ads APIs, then run adloop init once to sign in and write ~/.adloop/config.yaml. Since 9 September 2026 a developer token and a Manager Account are no longer needed. Tag Manager, Search Console and Merchant Center tools each need their own API enabled. Hosted: Codex needs codex mcp login adloop after adding the server, and ChatGPT needs a paid plan with developer mode on.

Limits

  • Not covered A Cloud project at the Test access level reaches Google Ads test accounts only. Explorer allows 2,880 operations a day, Basic 15,000
  • Limit Projects on a Google Cloud free trial or without billing are refused Explorer and Basic access
  • Read only Self-hosted writes stay dry runs while safety.require_dry_run is true, which is its default in config.py
  • Not covered GTM writes are off until gtm.write_enabled is set and you re-consent. Custom HTML tags also need gtm.allow_custom_html
  • Rate limit Reddit reporting is limited to 60 requests a minute. Reddit has no validate-only mode, so its dry run only re-reads and re-checks
  • Size cap upload_images takes PNG, JPEG or GIF up to 5 MB, and uploads are kept for 24 hours
  • Cost AdLoop Cloud Free allows 100 Google and Reddit Ads operations a day and 250 a month, with one ad account per platform and one GA4 property
  • Limit Sign-ins made with the bundled credentials of 0.9 and older no longer refresh. Run adloop init again with your own project

At the Test access level the Google Ads API reaches only test accounts; Explorer allows 2,880 operations a day, Basic 15,000. GTM writes stay off until gtm.write_enabled is set and the account re-consents; Custom HTML tags also need gtm.allow_custom_html. Reddit has no validate-only mode, so a Reddit dry run only re-reads and re-checks caps, and its reporting is limited to 60 requests a minute. Images from upload_images are kept 24 hours, up to 5 MB each. AdLoop Cloud's free plan caps Google and Reddit Ads calls at 100 a day, with one Ads account and one GA4 property. Sign-ins from versions 0.9 and older no longer work; run adloop init again.

Who it suits

For advertisers and agencies who run Google Ads next to GA4 and Tag Manager and want audits, reports and edits from Claude Code, Cursor or a similar client, with a preview before anything changes. Self-host when account data should stay on your machine and you can set up a Google Cloud project with billing. Choose AdLoop Cloud to skip that setup or to work from claude.ai or ChatGPT, which can only reach the hosted endpoint. It is a third-party project, not made by Google or Reddit. For read-only use, set ADLOOP_TOOLSETS to ads:read and ga4 so the write tools never reach the client.

Tools 60 of 153

29 only read, 27 change things, 4 can delete or overwrite. A ? marks an optional parameter.

health_check
Checks sign-in and API access and reports which tools are switched on
list_accounts
Lists the Google Ads accounts the sign-in can reachlimit?: integer
get_campaign_performance
Campaign cost, conversions, CPA, ROAS and impression share for a date rangecustomer_id?: stringdate_range_start?: stringdate_range_end?: stringcompact?: boolean
get_ad_performance
Ad copy with clicks, CTR and policy statuscustomer_id?: stringdate_range_start?: stringdate_range_end?: stringcompact?: boolean
get_keyword_performance
Keyword metrics with quality scorescustomer_id?: stringdate_range_start?: stringdate_range_end?: stringcompact?: boolean
get_search_terms
The search queries that triggered clickscustomer_id?: stringdate_range_start?: stringdate_range_end?: stringcompact?: boolean
get_recommendations
Google's own recommendations for the account with their expected effectcustomer_id?: stringrecommendation_types?: arraycampaign_id?: string
get_pmax_performance
Performance Max results and asset group strengthcustomer_id?: stringdate_range_start?: stringdate_range_end?: string
get_change_history
Who changed what in the Ads account and whencustomer_id?: stringdate_range_start?: stringdate_range_end?: stringcampaign_id?: stringresource_types?: arraylimit?: integer
run_gaql
Runs a Google Ads Query Language query of your ownquery: stringcustomer_id?: stringformat?: string
run_ga4_report
Custom GA4 report with filters, sorting, paging and a comparison perioddimensions?: arraymetrics?: arraydate_range_start?: stringdate_range_end?: stringproperty_id?: stringlimit?: integerdimension_filter?: arraymetric_filter?: arrayorder_by?: arrayoffset?: integercompare_start?: stringcompare_end?: string
run_realtime_report
Live GA4 numbers, handy right after a tracking deploydimensions?: arraymetrics?: arrayproperty_id?: string
get_tracking_events
GA4 event names and how often each fireddate_range_start?: stringdate_range_end?: stringproperty_id?: string
analyze_campaign_conversions
Matches Ads clicks to GA4 sessions and conversions per campaigndate_range_start?: stringdate_range_end?: stringcustomer_id?: stringproperty_id?: stringcampaign_name?: string
landing_page_analysis
Paid traffic, engagement and conversions per landing pagedate_range_start?: stringdate_range_end?: stringcustomer_id?: stringproperty_id?: string
attribution_check
Compares conversions reported by Ads with the matching GA4 eventsdate_range_start?: stringdate_range_end?: stringcustomer_id?: stringproperty_id?: stringconversion_events?: array
validate_tracking
Checks a list of expected event names against what GA4 recordsexpected_events: arrayproperty_id?: stringdate_range_start?: stringdate_range_end?: string
audit_event_coverage
Joins events from your code, GTM tags and GA4 fires and gives each a statusexpected_events: arraygtm_account_id?: stringgtm_container_id?: stringproperty_id?: stringdate_range_start?: stringdate_range_end?: string
generate_tracking_code
Writes the gtag snippet for a GA4 eventevent_name: stringevent_params?: objecttrigger?: stringproperty_id?: stringcheck_existing?: boolean
list_gtm_tags
Tags in a Tag Manager container and the triggers that fire themgtm_account_id?: stringgtm_container_id?: stringworkspace_id?: string
get_gtm_workspace_diff
Unpublished changes waiting in a GTM workspaceworkspace_id: stringgtm_account_id?: stringgtm_container_id?: string
draft_gtm_tag changes
Plans a new or edited GTM tag (needs gtm.write_enabled)name?: stringtag_type?: stringparameters?: arrayfiring_trigger_ids?: arrayblocking_trigger_ids?: arraypaused?: booleannotes?: stringtag_id?: stringworkspace_id?: stringgtm_account_id?: stringgtm_container_id?: string
draft_gtm_variable changes
Plans a new or edited GTM variable, such as a data layer key (needs gtm.write_enabled)name?: stringvariable_type?: stringdata_layer_key?: stringdefault_value?: stringparameters?: arraynotes?: stringvariable_id?: stringworkspace_id?: stringgtm_account_id?: stringgtm_container_id?: string
draft_publish_gtm_workspace deletes
Plans publishing a GTM workspace as a live versionversion_name?: stringversion_notes?: stringworkspace_id?: stringgtm_account_id?: stringgtm_container_id?: string
run_gsc_report
Search Console clicks, impressions, CTR and positionsite_url?: stringdimensions?: arraydate_range_start?: stringdate_range_end?: stringlimit?: integersearch_type?: stringdimension_filter_groups?: arraydata_state?: stringaggregation_type?: stringstart_row?: integer
analyze_page_speed
PageSpeed Insights scores and Core Web Vitals for one URLurl: stringstrategy?: string
get_merchant_feed_health
Merchant Center product approval counts and the main feed issuesaccount_id?: string
discover_keywords
Keyword Planner ideas from seed words or a page URLseed_keywords?: arrayurl?: stringgeo_target_id?: stringlanguage_id?: stringpage_size?: integercustomer_id?: stringinclude_monthly_volumes?: boolean
estimate_budget
Forecast of clicks, cost and conversions for a keyword setkeywords: arraydaily_budget?: numbergeo_target_id?: stringlanguage_id?: stringforecast_days?: integercustomer_id?: string
draft_campaign changes
Plans a paused Search campaign with budget, ad group and keywordscampaign_name: stringdaily_budget: numberbidding_strategy: stringgeo_target_ids: arraylanguage_ids: arraycustomer_id?: stringtarget_cpa?: numbertarget_roas?: numberchannel_type?: stringad_group_name?: stringkeywords?: arraysearch_partners_enabled?: booleandisplay_network_enabled?: booleandisplay_expansion_enabled?: booleanmax_cpc?: numberlocation_option?: stringnegative_location_option?: stringtracking_url_template?: stringfinal_url_suffix?: stringurl_custom_parameters?: array
draft_shopping_campaign changes
Plans a paused Standard Shopping campaign on a Merchant Center feedcampaign_name: stringdaily_budget: numbermerchant_id: stringproduct_bid: numbergeo_target_ids: arraybidding_strategy?: stringtarget_roas?: numbermax_cpc?: numbercampaign_priority?: stringfeed_label?: stringad_group_name?: stringsearch_partners_enabled?: booleancustomer_id?: string
update_campaign changes
Plans changes to bidding, budget, targeting or URL options of a campaigncampaign_id: stringcustomer_id?: stringbidding_strategy?: stringtarget_cpa?: numbertarget_roas?: numberdaily_budget?: numbergeo_target_ids?: arraylanguage_ids?: arraysearch_partners_enabled?: booleandisplay_network_enabled?: booleandisplay_expansion_enabled?: booleanmax_cpc?: numberlocation_option?: stringnegative_location_option?: stringtracking_url_template?: stringfinal_url_suffix?: stringurl_custom_parameters?: array
draft_responsive_search_ad changes
Plans a new responsive search adad_group_id: stringheadlines: arraydescriptions: arrayfinal_url: stringcustomer_id?: stringpath1?: stringpath2?: string
draft_keywords changes
Plans keyword additions to an ad groupad_group_id: stringkeywords: arraycustomer_id?: string
add_negative_keywords changes
Plans negative keywords on a campaigncampaign_id: stringkeywords: arraycustomer_id?: stringmatch_type?: string
draft_sitelinks changes
Plans sitelink assets for a campaigncampaign_id: stringsitelinks: arraycustomer_id?: string
update_sitelink changes
Plans an edit to the text, URL or descriptions of an existing sitelinkasset_id: stringlink_text?: stringfinal_url?: stringdescription1?: stringdescription2?: stringcustomer_id?: string
draft_callouts changes
Plans callout assets at account, campaign or ad group levelcallouts: arraycampaign_id?: stringad_group_id?: stringscope?: stringcustomer_id?: string
update_callout changes
Plans new text for an existing callout assetasset_id: stringcallout_text: stringcustomer_id?: string
draft_structured_snippets changes
Plans structured snippet assetssnippets: arraycampaign_id?: stringad_group_id?: stringscope?: stringcustomer_id?: string
update_structured_snippet changes
Plans new values or a new header for a structured snippetasset_id: stringvalues: arrayheader?: stringcustomer_id?: string
draft_call_asset changes
Plans a call asset with a phone number and optional hoursphone_number: stringcountry_code: stringcampaign_id?: stringad_group_id?: stringscope?: stringcall_conversion_action_id?: stringad_schedule?: arraycustomer_id?: string
update_call_asset changes
Plans changes to the number, conversion action or hours of a call assetasset_id: stringphone_number?: stringcountry_code?: stringcall_conversion_action_id?: stringcall_conversion_reporting_state?: stringad_schedule?: arrayclear_ad_schedule?: booleancustomer_id?: string
draft_promotion changes
Plans a promotion asset such as a percentage or amount offpromotion_target: stringfinal_url: stringlanguage_code: stringmoney_off?: numberpercent_off?: numbercampaign_id?: stringad_group_id?: stringscope?: stringcurrency_code?: stringpromotion_code?: stringorders_over_amount?: numberoccasion?: stringdiscount_modifier?: stringstart_date?: stringend_date?: stringredemption_start_date?: stringredemption_end_date?: stringad_schedule?: arraycustomer_id?: string
update_promotion changes
Plans edits to an existing promotion asset or clears some of its fieldsasset_id: stringpromotion_target?: stringfinal_url?: stringmoney_off?: numberpercent_off?: numbercurrency_code?: stringpromotion_code?: stringorders_over_amount?: numberoccasion?: stringdiscount_modifier?: stringlanguage_code?: stringstart_date?: stringend_date?: stringredemption_start_date?: stringredemption_end_date?: stringclear_fields?: arrayad_schedule?: arraycustomer_id?: string
draft_price_asset changes
Plans a price asset listing offers with pricesofferings: arrayprice_type: stringlanguage_code: stringcampaign_id?: stringad_group_id?: stringscope?: stringprice_qualifier?: stringcurrency_code?: stringcustomer_id?: string
draft_business_name_asset changes
Plans a business name asset for the account or a campaignbusiness_name: stringcampaign_id?: stringscope?: stringcustomer_id?: string
link_asset_to_customer changes
Plans attaching existing assets at account levellinks: arraycustomer_id?: string
add_ad_schedule changes
Plans ad schedule slots (days and hours) on a campaigncampaign_id: stringschedule: arraycustomer_id?: string
draft_key_event changes
Plans marking a GA4 event as a key eventevent_name: stringcounting_method?: stringproperty_id?: string
pause_entity changes
Plans pausing a campaign, ad group, ad, keyword or asset groupentity_type: stringentity_id: stringcustomer_id?: string
remove_entity deletes
Plans removing an Ads entity for goodentity_type: stringentity_id: stringcustomer_id?: string
confirm_and_apply deletes
Runs a planned change, as a dry run unless dry_run is falseplan_id: stringdry_run?: boolean
upload_images
Opens an upload card or link for image files to use in assets
list_reddit_accounts
Reddit businesses and ad accounts the sign-in can reach
get_reddit_performance
Reddit Ads spend, clicks, conversions and CPA with optional breakdownsad_account_id?: stringlevel?: stringdate_range_start?: stringdate_range_end?: stringbreakdown?: stringcompact?: boolean
draft_reddit_campaign changes
Plans a new Reddit Ads campaigncampaign_name: stringobjective: stringfunding_instrument_id: stringad_account_id?: stringcampaign_budget_optimization?: booleandaily_budget?: numberlifetime_budget?: numberbid_strategy?: stringbid_type?: stringbid_value?: numberoptimization_goal?: stringconversion_pixel_id?: stringspend_cap?: numberstart_time?: stringend_time?: stringschedule?: arrayview_through_conversion_type?: string
draft_reddit_ad changes
Plans a Reddit ad with its post, or promotes an existing postad_group_id: stringprofile_id?: stringheadline?: stringclick_url?: stringad_account_id?: stringad_name?: stringpost_type?: stringbody?: stringimage_url?: stringcall_to_action?: stringdisplay_url?: stringallow_comments?: booleanpost_id?: stringvideo_url?: stringthumbnail_url?: stringcarousel_cards?: array
update_reddit_campaign changes
Plans budget, bid, date or schedule changes to a Reddit campaigncampaign_id: stringad_account_id?: stringname?: stringdaily_budget?: numberlifetime_budget?: numberspend_cap?: numberbid_strategy?: stringbid_type?: stringbid_value?: numberstart_time?: stringend_time?: stringschedule?: array
remove_reddit_entity deletes
Plans archiving a Reddit campaign, ad group or ad, which cannot be undoneentity_type: stringentity_id: stringad_account_id?: string

Similar servers