loading…
Search for a command to run...
loading…
Exposes Google Ads and Meta Marketing performance data, campaign settings, and change history to Claude (Cowork) for live daily-dashboard workflows.
Exposes Google Ads and Meta Marketing performance data, campaign settings, and change history to Claude (Cowork) for live daily-dashboard workflows.
Local MCP server exposing Google Ads + Meta Marketing performance data, campaign settings, and change history to Claude (Cowork) for live daily-dashboard workflows.
Three tools registered with MCP:
| Tool | Purpose |
|---|---|
get_google_ads_report(date_range, breakdown) |
Performance + diagnostics + 56-day series + WoW + 8-week DoW comparisons + campaign settings + change history |
get_meta_ads_report(date_range, breakdown) |
Same shape as Google. Conversions filtered to META_CONVERSION_EVENT_NAME |
get_campaign_settings(platform) |
Settings + change history for google / meta / both |
Architecture: pull ad×day raw once per platform per hour, cache to parquet, derive every aggregation in pandas. No API call per breakdown.
uv package manager: brew install uv| Credential | Where to get it |
|---|---|
GOOGLE_ADS_DEVELOPER_TOKEN |
Google Ads UI → Tools → API Center |
GOOGLE_ADS_CLIENT_ID / CLIENT_SECRET |
https://console.cloud.google.com → OAuth 2.0 client (Desktop app) |
GOOGLE_ADS_REFRESH_TOKEN |
Run python -m google.ads.googleads.examples.authentication.generate_user_credentials after installing google-ads |
GOOGLE_ADS_CUSTOMER_IDS |
Comma-separated, no dashes. Find in Google Ads UI top-right |
GOOGLE_ADS_LOGIN_CUSTOMER_ID |
MCC manager account ID, no dashes |
META_APP_ID / META_APP_SECRET |
https://developers.facebook.com → My Apps → Settings → Basic |
META_ACCESS_TOKEN |
https://business.facebook.com → Business Settings → System Users → Generate New Token (long-lived, with ads_read) |
META_AD_ACCOUNT_ID |
Meta Ads Manager → top-left account picker. Format: act_XXXXXXXXX |
cd ~/marketing-ds/ads-mcp-server
uv sync --extra dev
uv creates .venv/ and installs all deps pinned in pyproject.toml.
Two options:
Option A — point at existing .env (recommended if you already have keys in ~/marketing-ds/decision_science/.env):
export ADS_MCP_ENV_FILE=/Users/jmacaggi/marketing-ds/decision_science/.env
Option B — local .env:
cp .env.example .env
# fill in the blanks
Required keys are listed in .env.example with comments explaining each.
Locally for testing:
uv run ads-mcp-server
The server speaks MCP over stdio — Cowork (or any MCP client) spawns it on demand.
Add this block to ~/.claude/claude_desktop_config.json (create if missing):
{
"mcpServers": {
"ads": {
"command": "uv",
"args": [
"--directory",
"/Users/jmacaggi/marketing-ds/ads-mcp-server",
"run",
"ads-mcp-server"
],
"env": {
"ADS_MCP_ENV_FILE": "/Users/jmacaggi/marketing-ds/decision_science/.env"
}
}
}
}
Restart Claude/Cowork. Tools get_google_ads_report, get_meta_ads_report, get_campaign_settings should appear.
No background daemon required — Cowork starts/stops the process per session.
Cache validity rule: a cache is fresh if it contains yesterday's date. Refreshes once per day. The first user query of the day triggers the refresh — and a 56-day pull on a large account can take 1-3 minutes.
To avoid that wait, schedule a pre-warm at 6am via macOS launchd:
# Install
cp /Users/jmacaggi/marketing-ds/ads-mcp-server/launchd/com.jmacaggi.adsmcp.prewarm.plist \
~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.jmacaggi.adsmcp.prewarm.plist
# Verify it's scheduled
launchctl list | grep adsmcp
# Trigger immediately (test)
launchctl start com.jmacaggi.adsmcp.prewarm
# Logs
tail -f ~/marketing-ds/ads-mcp-server/logs/prewarm.stdout.log
tail -f ~/marketing-ds/ads-mcp-server/logs/$(date +%Y-%m-%d).log
What it does each morning at 6am:
cache/google_lastrefresh.txt = todayBy the time you open Cowork, Google data is hot. Tool calls return in <1s.
To uninstall:
launchctl unload ~/Library/LaunchAgents/com.jmacaggi.adsmcp.prewarm.plist
rm ~/Library/LaunchAgents/com.jmacaggi.adsmcp.prewarm.plist
Meta tools (get_meta_ads_report, get_campaign_settings(platform="meta"|"both")) are structurally complete but not yet verified end-to-end.
What works:
Service temporarily unavailable / subcode 1504044 "result too large" error)level=campaign for the 56-day series, level=ad for yesterday-only (avoids 5-minute pagination)effective_status IN [ACTIVE, PAUSED] (avoids paginating thousands of archived ad sets)What blocked us:
code 17, subcode 2446079: "User request limit reached"). Cooldown is typically 10-60 minutes.To re-test tomorrow morning (after quota resets):
# Remove --skip-meta from the launchd plist to enable Meta in pre-warm
sed -i '' '/<string>--skip-meta<\/string>/d' \
~/Library/LaunchAgents/com.jmacaggi.adsmcp.prewarm.plist
launchctl unload ~/Library/LaunchAgents/com.jmacaggi.adsmcp.prewarm.plist
launchctl load ~/Library/LaunchAgents/com.jmacaggi.adsmcp.prewarm.plist
# Or run manually
ADS_MCP_ENV_FILE=/Users/jmacaggi/marketing-ds/decision_science/.env \
uv run python scripts/prewarm.py
If Meta keeps rate-limiting, fallback options (not yet implemented):
async=True) for the perf pulleffective_status filter (ACTIVE only, drop paused)If you prefer a long-running background process (optional, not required for Cowork): use nohup uv run ads-mcp-server > /tmp/ads-mcp.log 2>&1 & or a launchd plist.
Google Ads UI exports CSV reports without API quota limits. Drop a CSV into cache/external/ to override the API pull:
cache/external/google_2026-05-07.csv
cache/external/meta_2026-05-07.csv
If a matching CSV exists AND is newer than the parquet cache, the server loads it instead of calling the API. The response sets metadata.data_source = "csv_override" so Cowork knows.
CSV column schema must match the parquet (see src/ads_mcp_server/google_ads.py and meta_ads.py for column names: date, campaign_id, campaign_name, ad_id, ad_name, spend, impressions, clicks, conversions, ...).
uv run pytest -v
All tests are mock-based — no network calls. Covers:
classify_campaign Brand/Non-Brand/Otherdiagnose 5-state classifieractions[] filter for MetaEvery API call and error is logged to logs/YYYY-MM-DD.log (one file per day).
google-ads install fails: ensure Python 3.13 (uv python pin 3.13) and pip install grpcio works on your system. On Apple Silicon: arch -arm64 uv sync.~/Library/Logs/Claude/mcp*.log for spawn errors. Confirm uv is in PATH for the GUI process (you may need a full path: which uv).cache/*.parquet to force fresh pull.src/ads_mcp_server/
server.py # MCP entry + tool handlers
config.py # env loading, validates creds
google_ads.py # 3 GAQL queries: perf, settings, change_event
meta_ads.py # Insights + AdSet pull
snapshots.py # Meta snapshot diff (Meta has no reliable change API)
cache.py # parquet + CSV override
aggregate.py # all pandas math
classify.py # Brand/NB/Other
diagnose.py # 5-state diagnosis
date_ranges.py # window resolution + 8wk DoW
retry.py # exponential backoff
logging_setup.py # daily file logs
schema.py # response shape constants
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"ads-mcp-server": {
"command": "npx",
"args": []
}
}
}