Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ec55ba6
Add preflight.sh for GAds-MCP setup checks
alexrich700 Mar 21, 2026
9537386
Add GAds-MCP installation script
alexrich700 Mar 21, 2026
8a2a1df
Refactor Python version check in preflight script
alexrich700 Mar 21, 2026
e91d553
Update Python version check in install script
alexrich700 Mar 21, 2026
7c6054b
Add draft_ad_group tool for creating ad groups in existing campaigns
claude Mar 21, 2026
0eb72b9
Add pre-flight checks to draft_ad_group: campaign type, duplicate nam…
claude Mar 21, 2026
5180487
Fix comment numbering in preflight checks and correct tool count to 29
claude Mar 21, 2026
7d7ccfe
Fix null match_type handling and surface preflight failures as warnings
claude Mar 21, 2026
bf3e7e1
Merge pull request #1 from alexrich700/claude/add-multiple-ad-groups-…
alexrich700 Mar 21, 2026
a156a81
Add automatic Final URL Suffix (UTM tracking) to search campaign crea…
claude Mar 21, 2026
2a0370a
Merge pull request #2 from alexrich700/claude/check-ads-api-url-prefi…
alexrich700 Mar 21, 2026
73a448f
Add draft_rsa_replacement tool for editing existing RSA ad copy
claude Mar 21, 2026
42a6df5
Default to removing old ad in draft_rsa_replacement
claude Mar 21, 2026
093e84c
Merge pull request #3 from alexrich700/claude/evaluate-rsa-editing-GozE9
alexrich700 Mar 21, 2026
c0bd4f5
Add 9 new Google Ads insights tools for performance analysis
claude Mar 27, 2026
e26151d
Clean up get_change_history date logic and expand analyze-performance…
claude Mar 27, 2026
e9b1830
Fix change_history end-of-day timestamp and clamp limit to API max
claude Mar 27, 2026
b40442b
Merge pull request #4 from alexrich700/claude/research-google-ads-api…
alexrich700 Mar 27, 2026
b724ff3
Add MCP tool enhancements for ppc-search-checkin skill
claude Mar 27, 2026
61af265
Add campaign_id to search terms, discrepancy_pct to campaign conversions
claude Mar 27, 2026
28d97cc
Fix review issues: campaign_id validation, conversion_rate edge case,…
claude Mar 27, 2026
efae5e9
Merge pull request #5 from alexrich700/claude/research-google-ads-api…
alexrich700 Mar 27, 2026
8868b66
Remove "local service" phrasing from tool docstrings to prevent LSA c…
claude Mar 27, 2026
de10c44
Merge pull request #6 from alexrich700/claude/remove-lsa-references-v…
alexrich700 Mar 27, 2026
6432574
Fix trailing slash mismatch in landing_page_analysis URL matching
claude Mar 27, 2026
587ee47
Merge pull request #7 from alexrich700/claude/investigate-trailing-sl…
alexrich700 Mar 27, 2026
d6bf5c9
Fix GA4 tool schema types: replace anyOf unions with direct type fields
claude Mar 27, 2026
49fbe8a
Merge pull request #8 from alexrich700/claude/fix-ga4-schema-types-JXjst
alexrich700 Mar 27, 2026
900a74a
Add headline/description pinning support for Responsive Search Ads
claude Mar 27, 2026
cbb8207
Fix critical enum accessor bug and add pinning test coverage
claude Mar 27, 2026
3fe8bb1
Coerce non-string text values in _normalize_assets to prevent TypeError
claude Mar 27, 2026
fa4c78b
Merge pull request #9 from alexrich700/claude/research-ads-api-pinnin…
alexrich700 Mar 27, 2026
ca4530b
Add Performance Max read tools and analyze_pmax_performance cross-ref
claude May 7, 2026
e85e24f
Self-review fixes for Phase 1 PMax — docstring accuracy, GA4 failure UX
claude May 7, 2026
0f6b0ab
Set ga4_paid=None on GA4 failure (Codex P2)
claude May 7, 2026
aa9ef41
Merge pull request #10 from alexrich700/claude/research-google-ads-ap…
alexrich700 May 7, 2026
2779ede
Fix PMax for Google Ads API v24 and add PMax write + label tools
claude May 7, 2026
25fe39d
Self-review cleanup: dead code, naming, op order, brand_guidelines gate
claude May 7, 2026
2d31ae9
Address PR #11 review: BUSINESS_NAME minimum + image/logo maximum checks
claude May 8, 2026
303dc34
Merge pull request #11 from alexrich700/claude/fix-google-ads-pmax-DAFbk
alexrich700 May 8, 2026
3d28654
Fix validate_only TypeError that broke every PMax + label write path
claude May 8, 2026
3e6569e
Merge pull request #12 from alexrich700/claude/fix-pmax-label-issues-…
alexrich700 May 12, 2026
63987ff
Add draft_image_asset so PMax creation no longer requires the Ads UI
claude May 12, 2026
d16f7df
Address Round 3 Cowork findings: silent-dupe, Brand Guidelines, signa…
claude May 12, 2026
719e72a
Verify image bytes by sha256, not just file_size, at apply time
claude May 12, 2026
9161319
Merge pull request #13 from alexrich700/claude/review-pmax-approvals-…
alexrich700 May 12, 2026
46beba3
Merge upstream/main into fork
Wade-Randel Jul 27, 2026
b3cdbc6
fix(merge): reconcile apply layer to upstream + green the suite
Wade-Randel Jul 27, 2026
fd57e17
fix(write): re-graft final_url_suffix (UTM) onto upstream apply layer
Wade-Randel Jul 27, 2026
c345606
chore(merge): clean up post-merge follow-ups
Wade-Randel Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .claude/commands/analyze-performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,32 @@ Analyze Google Ads and GA4 performance: $ARGUMENTS
## 1. Pull data (AdLoop MCP)

- `get_campaign_performance` — relevant date range (default: last 30 days)
- `get_impression_share` — visibility and lost opportunity analysis
- `get_bid_strategy_status` — check learning status and strategy health
- `analyze_campaign_conversions` — cross-referenced Ads + GA4 data with GDPR gap detection
- If specific campaigns mentioned, filter by name
- If keywords are relevant, also pull `get_keyword_performance` and `get_search_terms`
- If budget concerns, pull `get_budget_pacing` for month-to-date pacing

**Context tip:** for the account-wide pass, call the performance tools with `compact=true` — you get totals, top-10 rows, and pre-computed offender lists instead of every row. Only switch to full mode when drilling into a specific campaign/keyword. In harnesses that support subagents, this whole data-pull step can be delegated to a subagent that returns just the summary.

## 2. Analyze

- Spend, Clicks, Conversions, CPA, CTR per campaign
- Impression share: search IS, budget-lost IS, rank-lost IS — identify visibility gaps
- Bid strategy status: any campaigns in learning phase? Appropriate strategy type?
- Paid vs organic comparison (from non_paid_channels)
- GDPR gap (clicks vs sessions ratio — 2:1 to 5:1 is normal in EU)
- Flag: zero conversions with significant spend, CPA > 3x target, QS < 5, wasteful search terms
- Flag: zero conversions with significant spend, CPA > 3x target, QS < 5, wasteful search terms, high budget-lost IS

If conversion issues found: run `attribution_check`
If landing page problems suspected: run `landing_page_analysis`
If quality scores are low: run `get_quality_score_details` for component breakdowns
If device performance varies: run `get_device_performance` to compare mobile vs desktop
If performance changed unexpectedly: run `get_change_history` to correlate with account changes
If geographic waste suspected: run `get_location_performance` to identify underperforming areas
If timing patterns matter: run `get_ad_schedule_performance` for hour/day analysis
If competitive context needed: run `get_auction_insights` (requires allowlisted account)

## 3. Present results

Expand Down
215 changes: 195 additions & 20 deletions .claude/rules/adloop.md

Large diffs are not rendered by default.

206 changes: 190 additions & 16 deletions .cursor/rules/adloop.mdc

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ src/adloop/
├── __init__.py # Entry point — routes 'adloop init' vs MCP server
├── server.py # FastMCP server — all tool registrations + toolset tags
├── config.py # Config loader (~/.adloop/config.yaml)
├── auth.py # OAuth 2.0 (user-supplied credentials, headless fallback) + service accounts
├── auth.py # OAuth 2.0 (user-supplied credentials, headless fallback) + service accounts + token refresh
├── cli.py # Interactive setup wizard (own Google Cloud project + auto-discovery)
├── crossref.py # Cross-reference tools (GA4 + Ads combined)
├── crossref.py # Cross-reference tools (GA4 + Ads, incl. analyze_pmax_performance)
├── tracking.py # Tracking validation + code generation
├── ga4/ # GA4 Data + Admin API (reports, realtime, events)
├── ads/ # Google Ads API (read, write, GAQL, forecasting, PMax, recommendations)
├── ads/ # Google Ads API (read, write, GAQL, forecasting, pmax_read, pmax_write, labels, recommendations)
└── safety/ # Guards, previews, audit logging
```

Expand All @@ -34,13 +34,13 @@ All tool usage rules, safety protocols, orchestration patterns, GAQL reference,

**Read and follow `.claude/rules/adloop.md` for all AdLoop MCP tool orchestration.**

That file is the complete guide for combining AdLoop's tools. It covers:
That file is the complete guide for combining AdLoop's tools (Search + Performance Max read & write + Image upload + Labels + Recommendations). It covers:
- Tool inventory with parameters and when to use each
- 9 safety rules (budget caps, dry-run defaults, Broad Match prevention, pre-write validation)
- 16 orchestration patterns (performance review, PMax analysis, recommendations, ad creation, tracking diagnosis, etc.)
- GAQL quick reference with syntax, common queries, and gotchas
- 16 orchestration patterns (performance review, PMax analysis & diagnostics, recommendations, ad creation, tracking diagnosis, etc.)
- GAQL quick reference with syntax, common queries, and gotchas (incl. asset_group, asset_group_asset)
- GDPR consent awareness for EU markets
- Ad copy character limits and marketing best practices
- Ad copy character limits and marketing best practices (incl. PMax-specific notes)

## Documentation Site

Expand Down
329 changes: 329 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
#!/bin/bash
# ==============================================================================
# GAds-MCP Installer
# Rossman Media - Google Ads MCP Setup
#
# This script installs GAds-MCP (AdLoop) and connects it to Claude.
# Run the pre-flight check first to make sure your machine is ready.
#
# Usage: curl -sSL https://raw.githubusercontent.com/alexrich700/GAds-MCP/main/scripts/install.sh | bash
# ==============================================================================

set -e

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'

REPO_URL="https://github.com/alexrich700/GAds-MCP.git"
INSTALL_DIR="$HOME/.gads-mcp"
CONFIG_DIR="$HOME/.adloop"
CLAUDE_CONFIG_DIR="$HOME/Library/Application Support/Claude"
CLAUDE_CONFIG_FILE="$CLAUDE_CONFIG_DIR/claude_desktop_config.json"

echo ""
echo "==========================================="
echo " GAds-MCP Installer"
echo " Rossman Media"
echo "==========================================="
echo ""

# -------------------------------------------
# 1. Check prerequisites
# -------------------------------------------
echo -e "${BOLD}Step 1/6: Checking prerequisites...${NC}"

# Find Python 3.11+ (check versioned commands, Homebrew paths, then generic python3)
PYTHON_CMD=""
for cmd in python3.13 python3.12 python3.11; do
if command -v "$cmd" &> /dev/null; then
ver=$("$cmd" --version 2>&1 | awk '{print $2}')
minor=$(echo "$ver" | cut -d. -f2)
if [[ "$minor" -ge 11 ]]; then
PYTHON_CMD="$cmd"
break
fi
fi
done
if [[ -z "$PYTHON_CMD" ]]; then
for brew_cmd in /opt/homebrew/bin/python3.13 /opt/homebrew/bin/python3.12 /opt/homebrew/bin/python3.11 /usr/local/bin/python3.13 /usr/local/bin/python3.12 /usr/local/bin/python3.11; do
if [[ -x "$brew_cmd" ]]; then
ver=$("$brew_cmd" --version 2>&1 | awk '{print $2}')
minor=$(echo "$ver" | cut -d. -f2)
if [[ "$minor" -ge 11 ]]; then
PYTHON_CMD="$brew_cmd"
break
fi
fi
done
fi
if [[ -z "$PYTHON_CMD" ]]; then
if command -v python3 &> /dev/null; then
ver=$(python3 --version 2>&1 | awk '{print $2}')
minor=$(echo "$ver" | cut -d. -f2)
if [[ "$minor" -ge 11 ]]; then
PYTHON_CMD="python3"
fi
fi
fi
if [[ -z "$PYTHON_CMD" ]]; then
echo -e "${RED}Error: Python 3.11+ not found. Run the pre-flight check first.${NC}"
echo -e " Tried: python3.12, python3.11, /opt/homebrew/bin/python3.12, python3"
exit 1
fi

PY_VERSION=$("$PYTHON_CMD" --version 2>&1 | awk '{print $2}')
echo -e " ${GREEN}Python $PY_VERSION (using: $PYTHON_CMD)${NC}"

# Git
if ! command -v git &> /dev/null; then
echo -e "${RED}Error: Git not found. Run the pre-flight check first.${NC}"
exit 1
fi
echo -e " ${GREEN}Git OK${NC}"
echo ""

# -------------------------------------------
# 2. Install uv if needed
# -------------------------------------------
echo -e "${BOLD}Step 2/6: Setting up package manager...${NC}"

if ! command -v uv &> /dev/null; then
echo " Installing uv..."
curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null

# Source the env so uv is available in this session
if [[ -f "$HOME/.local/bin/env" ]]; then
source "$HOME/.local/bin/env"
fi
# Also add to path directly in case the source above doesn't work
export PATH="$HOME/.local/bin:$PATH"

if command -v uv &> /dev/null; then
echo -e " ${GREEN}uv installed successfully${NC}"
else
echo -e "${RED}Error: uv installation failed. Try manually: curl -LsSf https://astral.sh/uv/install.sh | sh${NC}"
exit 1
fi
else
echo -e " ${GREEN}uv already installed${NC}"
fi
echo ""

# -------------------------------------------
# 3. Clone or update the repo
# -------------------------------------------
echo -e "${BOLD}Step 3/6: Getting GAds-MCP...${NC}"

if [[ -d "$INSTALL_DIR" ]]; then
echo " Found existing installation, updating..."
cd "$INSTALL_DIR"
git pull --quiet origin main
echo -e " ${GREEN}Updated to latest version${NC}"
else
echo " Cloning from GitHub..."
git clone --quiet "$REPO_URL" "$INSTALL_DIR"
echo -e " ${GREEN}Downloaded${NC}"
fi

cd "$INSTALL_DIR"
echo ""

# -------------------------------------------
# 4. Install Python dependencies
# -------------------------------------------
echo -e "${BOLD}Step 4/6: Installing dependencies...${NC}"

uv sync --python "$PYTHON_CMD" --quiet 2>/dev/null || uv sync --python "$PYTHON_CMD"
echo -e " ${GREEN}Dependencies installed${NC}"
echo ""

# -------------------------------------------
# 5. Run adloop init (OAuth + config)
# -------------------------------------------
echo -e "${BOLD}Step 5/6: Setting up Google Ads connection...${NC}"
echo ""
echo -e " ${YELLOW}This will open your browser for Google sign-in.${NC}"
echo -e " ${YELLOW}Sign in with your Google account that has access to the MCC.${NC}"
echo ""
echo -e " ${BLUE}You'll need the following info (Alex can provide these):${NC}"
echo " - Google Cloud Project ID"
echo " - Google Ads Developer Token"
echo " - OAuth Client ID and Client Secret"
echo ""
read -p " Ready? Press Enter to continue (or Ctrl+C to exit)... "
echo ""

# Run the init wizard
uv run adloop init

echo ""
echo -e " ${GREEN}Google Ads connection configured${NC}"
echo ""

# -------------------------------------------
# 6. Configure Claude MCP
# -------------------------------------------
echo -e "${BOLD}Step 6/6: Connecting to Claude...${NC}"

# Get the full path to the Python in the venv
PYTHON_PATH="$INSTALL_DIR/.venv/bin/python"

if [[ ! -f "$PYTHON_PATH" ]]; then
# Fallback: find the python in the venv
PYTHON_PATH=$(find "$INSTALL_DIR/.venv" -name "python3" -type f 2>/dev/null | head -1)
fi

if [[ -z "$PYTHON_PATH" || ! -f "$PYTHON_PATH" ]]; then
echo -e "${RED}Error: Could not find Python in the virtual environment.${NC}"
echo " Please contact Alex for help."
exit 1
fi

# The MCP server entry we need to add
MCP_ENTRY=$(cat <<EOF
{
"mcpServers": {
"gads-mcp": {
"command": "$PYTHON_PATH",
"args": ["-m", "adloop"]
}
}
}
EOF
)

# --- Claude Desktop / Cowork config ---
CLAUDE_CONFIGURED=false

if [[ "$OSTYPE" == "darwin"* ]]; then
if [[ -d "$CLAUDE_CONFIG_DIR" ]] || [[ -d "/Applications/Claude.app" ]]; then
echo " Configuring Claude Desktop / Cowork..."

# Create config dir if it doesn't exist
mkdir -p "$CLAUDE_CONFIG_DIR"

if [[ -f "$CLAUDE_CONFIG_FILE" ]]; then
# Config file exists, check if gads-mcp is already there
if grep -q "gads-mcp" "$CLAUDE_CONFIG_FILE" 2>/dev/null; then
echo -e " ${GREEN}Claude Desktop already configured${NC}"
CLAUDE_CONFIGURED=true
else
# Need to merge. Use python for safe JSON manipulation.
python3 << PYEOF
import json
import sys
import shutil

config_file = "$CLAUDE_CONFIG_FILE"
python_path = "$PYTHON_PATH"

try:
with open(config_file, 'r') as f:
config = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
config = {}

# Create backup
shutil.copy2(config_file, config_file + ".backup")

# Add or update mcpServers
if 'mcpServers' not in config:
config['mcpServers'] = {}

config['mcpServers']['gads-mcp'] = {
"command": python_path,
"args": ["-m", "adloop"]
}

with open(config_file, 'w') as f:
json.dump(config, f, indent=2)

print(" Config updated (backup saved as claude_desktop_config.json.backup)")
PYEOF
echo -e " ${GREEN}Claude Desktop configured${NC}"
CLAUDE_CONFIGURED=true
fi
else
# No config file yet, create one
echo "$MCP_ENTRY" > "$CLAUDE_CONFIG_FILE"
echo -e " ${GREEN}Claude Desktop config created${NC}"
CLAUDE_CONFIGURED=true
fi
fi
fi

# --- Claude Code config (project-level .mcp.json) ---
echo ""
echo " For Claude Code, the MCP is configured per-project."
echo " When you open a project in Claude Code, create a .mcp.json file"
echo " in the project root with this content:"
echo ""
echo -e " ${BLUE}$(cat <<EOF
{
"mcpServers": {
"gads-mcp": {
"command": "$PYTHON_PATH",
"args": ["-m", "adloop"]
}
}
}
EOF
)${NC}"
echo ""

# Also save this to a file they can easily copy
MCP_JSON_FILE="$INSTALL_DIR/mcp-config-snippet.json"
cat > "$MCP_JSON_FILE" <<EOF
{
"mcpServers": {
"gads-mcp": {
"command": "$PYTHON_PATH",
"args": ["-m", "adloop"]
}
}
}
EOF

echo " This snippet is also saved to: $MCP_JSON_FILE"
echo " You can copy it anytime with: cat $MCP_JSON_FILE"
echo ""

# -------------------------------------------
# Done!
# -------------------------------------------
echo "==========================================="
echo ""
echo -e " ${GREEN}${BOLD}Setup complete!${NC}"
echo ""

if [[ "$CLAUDE_CONFIGURED" == true ]]; then
echo -e " ${GREEN}Claude Desktop / Cowork:${NC} Configured"
echo " Restart Claude Desktop for changes to take effect."
echo ""
fi

echo -e " ${BOLD}To verify it's working:${NC}"
echo " 1. Open Claude Desktop (or restart it if it was open)"
echo " 2. Start a new conversation"
echo ' 3. Ask: "List my Google Ads accounts"'
echo " 4. Claude should call the gads-mcp tool and show your accounts"
echo ""
echo -e " ${BOLD}To update later:${NC}"
echo " cd $INSTALL_DIR && git pull && uv sync"
echo ""
echo -e " ${BOLD}If something breaks:${NC}"
echo " Screenshot the error and send it to Alex."
echo ""
echo -e " ${BOLD}Config locations:${NC}"
echo " GAds-MCP install: $INSTALL_DIR"
echo " AdLoop config: $CONFIG_DIR/config.yaml"
echo " Claude config: $CLAUDE_CONFIG_FILE"
echo " MCP snippet: $MCP_JSON_FILE"
echo ""
echo "==========================================="
echo ""
Loading