ga4-manager-mcp
v4.1.2
Published
MCP server for organic growth: Google Analytics 4, Search Console, AdSense, on-page SEO audits and DataForSEO, built for AI agents
Maintainers
Readme
GA4 Manager MCP Server
AI-powered GA4 and Google Search Console management through Model Context Protocol
A Model Context Protocol (MCP) server that exposes GA4 Manager CLI commands as 13 structured tools for AI assistants. Configure Google Analytics 4 properties, manage Search Console, and query analytics data using natural language with Claude Code, Claude Desktop, and other MCP clients.
What is This?
This MCP server bridges AI assistants like Claude Code with the GA4 Manager CLI, enabling:
- Natural language analytics configuration - "Set up conversion tracking for purchases"
- Automated Search Console management - "Submit my sitemap and check indexing status"
- Data-driven insights - "Show me top search queries for the last 30 days"
- Workflow automation - "Validate config, preview setup, then apply if it looks good"
What is MCP?
Model Context Protocol (MCP) is an open protocol that lets AI assistants interact with external tools and data sources. Think of it as an API designed for AI agents.
How it works:
- AI assistant (Claude Code) receives user request
- MCP client calls tool on MCP server (this project)
- MCP server executes GA4 CLI command
- Structured JSON response returned to AI assistant
- AI assistant uses data to help user
Features
- ✅ 13 Production-Ready Tools - Complete GA4 and GSC operation coverage
- ✅ Comprehensive Test Suite - All tools covered by unit and integration tests
- ✅ Structured JSON Output - All CLI responses parsed to machine-readable format
- ✅ Intelligent Parsing - Auto-detects and handles JSON, tables, CSV, markdown
- ✅ Dry-Run Support - Preview changes before applying (safe operations)
- ✅ Quota Tracking - Monitor Google Search Console API usage
- ✅ Error Handling - Actionable error messages with suggestions
- ✅ Multi-Client Support - Works with Claude Desktop, Claude CLI, VS Code, Cursor, Cline
Quick Start
Installation
From npm
claude mcp add ga4-manager \
--env GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json \
--env GOOGLE_CLOUD_PROJECT=your-gcp-project-id \
-- npx -y ga4-manager-mcpNative tools (GA4 reports, Search Console comparisons, AdSense, SEO audits, DataForSEO) work with just this. CLI-backed tools (ga4_setup, gsc_health, gsc_decay…) also need the ga4 binary: download it from GitHub Releases and put it on your PATH or set GA4_BINARY_PATH. Destructive tools stay disabled until you add --env GA4M_ALLOW_DESTRUCTIVE=1.
From source
# 1. Clone repository
git clone https://github.com/garbarok/ga4-manager.git
cd ga4-manager
# 2. Build CLI binary
make build
# 3. Build MCP server
cd mcp
npm install
npm run buildConfiguration
Choose your client and follow the setup:
Claude CLI (Recommended)
claude mcp add \
--name ga4-manager \
--transport stdio \
--command "node" \
--args "/absolute/path/to/ga4-manager/mcp/dist/index.js" \
--env "GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json" \
--env "GOOGLE_CLOUD_PROJECT=your-gcp-project-id" \
--env "GA4_BINARY_PATH=/absolute/path/to/ga4-manager/ga4" \
--env "GA4_DEFAULT_PROPERTY_ID=123456789"Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"ga4-manager": {
"command": "node",
"args": ["/absolute/path/to/ga4-manager/mcp/dist/index.js"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/credentials.json",
"GOOGLE_CLOUD_PROJECT": "your-gcp-project-id",
"GA4_BINARY_PATH": "/absolute/path/to/ga4-manager/ga4",
"GA4_DEFAULT_PROPERTY_ID": "123456789"
}
}
}
}Other Clients
See CONFIGURATION.md for setup guides:
- VS Code (via MCP extension)
- Cursor
- Cline
- Custom integrations
Verify Installation
Claude Desktop:
- Restart Claude Desktop
- Click 🔌 icon in bottom-left
- Should show "ga4-manager" with 29 tools
Claude CLI:
claude mcp list
# Should show: ga4-manager (29 tools)VS Code/Cursor:
- Check MCP status indicator in status bar
Available Tools (29)
Tool Categories
Growth planning (1 tool) - Start here for SEO / monetization plans
site_growth_brief- One compact brief per site: GSC + GA4 traffic + AdSense-in-GA4 revenue KPIs with deltas, top pages by revenue, and the top 10 diagnostic findings ranked by estimated clicks
GA4 Analytics (8 tools) - Configuration, management and traffic
ga4_setup- Create conversions, dimensions, metricsga4_report- View current configurationga4_cleanup- Remove unused resourcesga4_link_list- List existing external-service linksga4_link_create- Create a Search Console/BigQuery/Channels linkga4_link_remove- Remove (unlink) a BigQuery/Channels linkga4_validate- Configuration validationga4_traffic_report- Sessions, engagement, page views and ad revenue (when AdSense is linked) by page, landing page, channel, source, country, device or date
Search Console (14 tools) - SEO and indexing
gsc_sitemaps_list- List all sitemapsgsc_sitemaps_submit- Submit new sitemapgsc_sitemaps_delete- Remove sitemapgsc_sitemaps_get- Get sitemap detailsgsc_inspect_url- Check URL indexing statusgsc_analytics_run- Search performance datagsc_monitor_urls- Batch URL monitoringgsc_index_coverage- Index coverage reportgsc_cannibalization- Detect keyword cannibalization across URLsgsc_opportunities- Surface ranking/CTR improvement opportunities (per query, or per page withgranularity: "page")gsc_ctr_anomaly- Flag pages with anomalous CTR for their positiongsc_decay- Flag pages that slipped in position and lost clicks vs the prior windowgsc_health- Search Console health overviewgsc_url_hygiene- URLs with impressions that should not be indexed (fragments, asset routes, malformed paths, query duplicates)gsc_hreflang- hreflang pair integrity and cross-language ranking
Diagnostics & SEO (4 tools) - Traffic analysis and page auditing
gsc_traffic_compare- Diff GSC traffic between two date ranges per URL, or before/after a change date (around) with an optionalpagesfilterga4_consent_health- Consent banner grant/deny rates and health scoreseo_page_audit- On-page SEO audit with optional Core Web Vitals;render_js: truefetches the post-JavaScript DOM via Cloudflare Browser Rendering for JS-rendered/SPA pagesseo_audit_batch- Batch on-page SEO audit over a sitemap or URL list; supportsrender_js, include/exclude regex filters and SiteOne crawl discovery (discover.siteone)
AdSense (2 tools) - Publisher earnings reporting
adsense_accounts_list- List accessible AdSense publisher accountsadsense_report- Earnings / page views / impressions / clicks / RPM by date, domain, country, ad unit
Keyword research (3 tools) - DataForSEO / ValueSERP, opt-in third-party credentials
keyword_volume- Search volume, CPC and competition for a list of keywords (DataForSEO)keyword_serp_snapshot- Who currently ranks for a keyword, for content-gap/competitive research (DataForSEO)keyword_rank_check- A domain's live Google rank for a keyword today, distinct from GSC's historical average position (ValueSERP)
Competitive & AI-visibility research (9 tools) - DataForSEO, opt-in; every result reports cost_usd
dataforseo_balance- Remaining DataForSEO balance (free)llm_mentions- Whether Google AI answers / ChatGPT mention or cite a domain for given queries, and who they cite insteadkeyword_gap- Keywords a competitor ranks for that you don't, with volume and difficultyranked_keywords- Every keyword and position for any domain, competitors includedkeyword_ideas- Keyword ideas with volume, difficulty (KD) and search intentgoogle_trends- Interest over time (0–100) for up to 5 terms: rising/falling, seasonality, peak datebacklinks_overview- Domain rank, backlinks and referring domains, side by sidelink_gap- Referring domains that link to competitors but not to you (outreach targets)dataforseo_request- Gated escape hatch: any DataForSEO v3 endpoint by path; billed per call, off unlessDATAFORSEO_ALLOW_RAW=1
Indexing (1 tool) - IndexNow, no account needed (just a key file on your site)
indexnow_submit- Notify Bing, Yandex, Seznam, Naver, Yep and others (not Google) about new or changed URLs.dry_rundefaults to true; the key file is verified before submitting
Tool Operation Types
Read-Only (Safe):
ga4_report,ga4_validate,ga4_link_list,gsc_sitemaps_list,gsc_sitemaps_get,gsc_inspect_url,gsc_analytics_run,gsc_monitor_urls,gsc_index_coverage,gsc_cannibalization,gsc_opportunities,gsc_ctr_anomaly,gsc_health,gsc_traffic_compare,ga4_consent_health,seo_page_audit,seo_audit_batch,adsense_accounts_list,adsense_report,site_growth_brief,ga4_traffic_report,gsc_url_hygiene,gsc_hreflang,keyword_volume,keyword_serp_snapshot,keyword_rank_check,dataforseo_balance,llm_mentions,keyword_gap,ranked_keywords,keyword_ideas,google_trends,backlinks_overview,link_gap,dataforseo_requestga4_report,ga4_validate,ga4_link_list,gsc_sitemaps_list,gsc_sitemaps_get,gsc_inspect_url,gsc_analytics_run,gsc_monitor_urls,gsc_index_coverage,gsc_cannibalization,gsc_opportunities,gsc_ctr_anomaly,gsc_decay,gsc_health,gsc_traffic_compare,ga4_consent_health,seo_page_audit,seo_audit_batch,adsense_accounts_list,adsense_report,site_growth_brief,ga4_traffic_report,gsc_url_hygiene,gsc_hreflang,keyword_volume,keyword_serp_snapshot,keyword_rank_check,dataforseo_balance,llm_mentions,keyword_gap,ranked_keywords,keyword_ideas,google_trends,backlinks_overview,link_gap
Modifying (Use with caution):
indexnow_submit(dry-run by default),ga4_setup,ga4_cleanup,ga4_link_create,ga4_link_remove,gsc_sitemaps_submit,gsc_sitemaps_delete
Always use dry-run first for modifying operations!
Tool Documentation
GA4 Tools
ga4_setup - Setup GA4/GSC Configuration
Creates conversions, dimensions, metrics, and Search Console configuration from YAML file.
Input Schema:
{
config_path?: string; // Path to YAML config file
project_name?: string; // Project name (alternative to config_path)
all?: boolean; // Setup all configs in configs/ directory
dry_run?: boolean; // Preview without applying (recommended)
}Example Request:
{
"config_path": "configs/example-site.yaml",
"dry_run": true
}Example Response:
{
"success": true,
"operation": "setup",
"dry_run": true,
"project": {
"name": "Example Site",
"property_id": "123456789"
},
"results": {
"ga4": {
"conversions_created": 5,
"conversions_skipped": 2,
"dimensions_created": 8,
"metrics_created": 3
},
"gsc": {
"sitemaps_submitted": 2,
"urls_monitored": 10
}
},
"execution_time_ms": 2345,
"message": "Dry-run complete. No changes applied."
}Use Cases:
- Initial GA4 property setup
- Deploying analytics to new environment
- Bulk configuration changes
ga4_report - View Configuration
Lists existing conversions, dimensions, and metrics for a GA4 property.
Input Schema:
{
config_path?: string; // Path to YAML config
project_name?: string; // Project name
all?: boolean; // Report on all configs
}Example Request:
{
"config_path": "configs/example-site.yaml"
}Example Response:
{
"success": true,
"operation": "report",
"project": {
"name": "Example Site",
"property_id": "123456789"
},
"report": {
"conversions": [
{
"name": "purchase",
"counting_method": "ONCE_PER_EVENT",
"status": "active"
}
],
"dimensions": [
{
"parameter": "user_tier",
"display_name": "User Tier",
"scope": "USER",
"status": "active"
}
],
"metrics": [
{
"parameter": "cart_value",
"display_name": "Cart Value",
"unit": "CURRENCY",
"scope": "EVENT"
}
]
}
}Use Cases:
- Audit existing configuration
- Verify setup completed successfully
- Document current state
ga4_cleanup - Remove Unused Resources
Archives unused conversions, dimensions, and metrics to free up quota.
Input Schema:
{
config_path?: string; // Path to YAML config
project_name?: string; // Project name
all?: boolean; // Cleanup all configs
type?: string; // "conversions" | "dimensions" | "metrics" | "all"
dry_run?: boolean; // Preview deletions (recommended)
yes?: boolean; // Auto-confirm (use with caution)
}Example Request:
{
"config_path": "configs/example-site.yaml",
"type": "all",
"dry_run": true
}Example Response:
{
"success": true,
"operation": "cleanup",
"dry_run": true,
"items_to_remove": {
"conversions": ["old_event_1", "deprecated_conversion"],
"dimensions": ["unused_param"],
"metrics": []
},
"quota_freed": {
"conversions": 2,
"dimensions": 1,
"metrics": 0
},
"warning": "Archived parameters cannot be reused. Consider new names (e.g., user_type_v2)."
}Important:
- Archived dimension/metric parameters are permanently reserved (GA4 limitation)
- Always use
dry_run: truefirst - Consider using new parameter names instead of archiving
Use Cases:
- Free up quota for new items
- Remove deprecated tracking
- Clean up test configurations
External Service Integration (ga4_link_list / ga4_link_create / ga4_link_remove)
Linking is split into three tools so that read, write, and delete operations
are distinct (this is required for the Claude connector directory — a single
tool may not mix safe and unsafe operations). All three dispatch to the same
ga4 link CLI command.
ga4_link_list - List Links (read-only)
Lists existing BigQuery export links and custom Channel Groups. (Search Console
links can't be listed via the GA4 Admin API and are reported as
manual_check_required.)
{
project_name: string; // Required: Project name
}Example Request:
{
"project_name": "example-site"
}ga4_link_create - Create a Link (modifying)
Creates a BigQuery export link, sets up default Channel Groups, or returns a Search Console setup guide.
{
project_name: string; // Required: Project name
service: string; // Required: "search-console" | "bigquery" | "channels"
url?: string; // Site URL (required for search-console)
gcp_project?: string; // GCP project (required for bigquery)
dataset?: string; // BigQuery dataset (required for bigquery)
}Example Request (Link Search Console):
{
"project_name": "example-site",
"service": "search-console",
"url": "https://example.com"
}ga4_link_remove - Remove a Link (destructive)
Unlinks an existing BigQuery export or Channel Group. (Search Console links cannot be removed via the Admin API.)
{
project_name: string; // Required: Project name
service: string; // Required: "bigquery" | "channels"
}Example Request:
{
"project_name": "example-site",
"service": "bigquery"
}Use Cases:
- Connect GA4 to Search Console
- Setup BigQuery export
- Configure custom channel groups
ga4_validate - Configuration Validation
Validates YAML configuration files against GA4 tier limits and naming rules.
Input Schema:
{
config_file?: string; // Path to config file
all?: boolean; // Validate all configs in configs/
verbose?: boolean; // Detailed validation output
}Example Request:
{
"config_file": "configs/example-site.yaml",
"verbose": true
}Example Response:
{
"success": true,
"operation": "validate",
"file": "configs/example-site.yaml",
"validation": {
"valid": true,
"errors": [],
"warnings": [
"Property tier 'standard' allows max 30 conversions (you have 5)"
],
"details": {
"conversions": 5,
"dimensions": 8,
"metrics": 3,
"tier": "standard",
"limits": {
"conversions": 30,
"dimensions": 50,
"metrics": 50
}
}
}
}Use Cases:
- Verify config before setup
- Check quota usage
- Identify configuration errors
Google Search Console Tools
gsc_sitemaps_list - List Sitemaps
Lists all sitemaps submitted for a Search Console property.
Input Schema:
{
site: string; // Required: "sc-domain:example.com" or "https://example.com/"
}Example Response:
{
"success": true,
"operation": "sitemaps_list",
"site": "sc-domain:example.com",
"sitemaps": [
{
"path": "https://example.com/sitemap.xml",
"type": "sitemapsIndex",
"submitted": "2024-12-01T10:00:00Z",
"last_downloaded": "2024-12-27T15:30:00Z",
"is_pending": false,
"is_sitemaps_index": true,
"contents": {
"submitted": 1500,
"indexed": 1450
}
}
],
"total": 1
}gsc_sitemaps_submit - Submit Sitemap
Submits a new sitemap URL to Search Console for crawling.
Input Schema:
{
site: string; // Required: Site URL
url: string; // Required: Sitemap URL
}Example Request:
{
"site": "sc-domain:example.com",
"url": "https://example.com/sitemap.xml"
}Example Response:
{
"success": true,
"operation": "sitemap_submit",
"site": "sc-domain:example.com",
"sitemap_url": "https://example.com/sitemap.xml",
"message": "Sitemap submitted successfully"
}gsc_sitemaps_delete - Delete Sitemap
Removes a sitemap from Search Console (does not delete the actual file).
Input Schema:
{
site: string; // Required: Site URL
url: string; // Required: Sitemap URL to remove
}gsc_sitemaps_get - Get Sitemap Details
Retrieves detailed information about a specific sitemap including errors and content breakdown.
Input Schema:
{
site: string; // Required: Site URL
url: string; // Required: Sitemap URL
}Example Response:
{
"success": true,
"operation": "sitemap_get",
"sitemap": {
"path": "https://example.com/sitemap.xml",
"type": "sitemapsIndex",
"submitted": "2024-12-01T10:00:00Z",
"last_downloaded": "2024-12-27T15:30:00Z",
"errors": 0,
"warnings": 2,
"contents": {
"web_pages": {
"submitted": 1000,
"indexed": 950
},
"images": {
"submitted": 500,
"indexed": 500
}
}
}
}gsc_inspect_url - URL Indexing Inspection
Inspects a URL's indexing status, mobile usability, rich results, and coverage state.
Input Schema:
{
site: string; // Required: "sc-domain:example.com" (domain property recommended)
url: string; // Required: Full URL to inspect
}Example Request:
{
"site": "sc-domain:example.com",
"url": "https://example.com/products/widget"
}Example Response:
{
"success": true,
"operation": "inspect_url",
"url": "https://example.com/products/widget",
"inspection_result": {
"index_status": {
"verdict": "PASS",
"coverage_state": "Submitted and indexed",
"crawled_as": "DESKTOP",
"last_crawl_time": "2024-12-27T10:30:00Z",
"page_fetch_state": "SUCCESSFUL",
"robots_txt_state": "ALLOWED",
"indexing_state": "INDEXING_ALLOWED"
},
"mobile_usability": {
"verdict": "PASS",
"issues": []
},
"rich_results": {
"verdict": "PASS",
"detected_items": [
{
"type": "Product",
"name": "Widget",
"issues": []
}
]
},
"amp": {
"verdict": "NOT_APPLICABLE"
}
},
"quota": {
"used": 1,
"limit": 2000,
"remaining": 1999,
"percentage": 0.05
}
}Rate Limits:
- 2,000 inspections per day per property
- 600 inspections per minute per property
Use Cases:
- Check why page isn't indexed
- Verify mobile usability
- Validate rich results markup
- Monitor critical pages
gsc_analytics_run - Search Analytics Report
Queries Search Console for performance data: queries, pages, impressions, clicks, CTR, and position.
Input Schema:
{
site?: string; // Site URL (or from config)
config?: string; // Config file path (alternative)
days?: number; // Period: 1-180 days (default: 30)
dimensions?: string; // Comma-separated: "query,page,country,device"
limit?: number; // Max rows: 1-25000 (default: 100)
format?: string; // Output: "json" | "csv" | "table" | "markdown"
dry_run?: boolean; // Preview query parameters
}Example Request:
{
"site": "sc-domain:example.com",
"days": 30,
"dimensions": "query,page",
"limit": 50,
"format": "json"
}Example Response:
{
"success": true,
"operation": "gsc_analytics",
"site_url": "sc-domain:example.com",
"period": "2024-11-27 to 2024-12-27",
"total_rows": 50,
"aggregates": {
"total_clicks": 1250,
"total_impressions": 15000,
"average_ctr": 0.0833,
"average_position": 8.5
},
"rows": [
{
"keys": ["best analytics tool", "https://example.com/"],
"clicks": 45,
"impressions": 500,
"ctr": 0.09,
"position": 3.2
},
{
"keys": ["ga4 setup guide", "https://example.com/blog/ga4-guide"],
"clicks": 38,
"impressions": 420,
"ctr": 0.09,
"position": 4.1
}
],
"quota": {
"used": 1,
"limit": 2000,
"remaining": 1999
}
}Available Dimensions:
query- Search querypage- Landing page URLcountry- User country (ISO)device- DESKTOP, MOBILE, TABLETsearchAppearance- How result appeareddate- Individual dates
Limits:
- Max 3 dimensions per query
- Data typically 2-3 days behind
- 25,000 rows maximum
Use Cases:
- Identify top-performing content
- Find ranking opportunities
- Track keyword performance
- Analyze traffic patterns
gsc_monitor_urls - Batch URL Monitoring
Monitors multiple priority URLs for indexing issues using configuration file or direct URL array.
Input Schema (Config-based):
{
config: string; // Path to YAML config with priority_urls
format?: string; // "json" | "table" | "markdown"
dry_run?: boolean; // Preview URLs without inspecting
}Input Schema (Direct URLs):
{
site: string; // Site URL
urls: string[]; // Array of URLs to inspect (max 50)
dry_run?: boolean; // Preview without API calls
}Example Request (Direct):
{
"site": "sc-domain:example.com",
"urls": [
"https://example.com/",
"https://example.com/products/",
"https://example.com/blog/"
]
}Example Response:
{
"success": true,
"operation": "monitor_urls",
"site": "sc-domain:example.com",
"summary": {
"total": 3,
"indexed": 3,
"not_indexed": 0,
"with_issues": 0
},
"results": [
{
"url": "https://example.com/",
"label": "Homepage",
"verdict": "PASS",
"coverage_state": "Submitted and indexed",
"mobile_usability": "PASS",
"last_crawl": "2024-12-27T10:00:00Z",
"issues": []
}
],
"quota": {
"used": 3,
"limit": 2000,
"remaining": 1997
}
}Use Cases:
- Monitor critical pages daily
- Detect indexing issues early
- Track mobile usability
- Audit site after deployment
gsc_index_coverage - Index Coverage Report
Generates index coverage analysis showing indexed, excluded, and error pages based on Search Analytics impressions data.
Input Schema:
{
site?: string; // Site URL (or from config)
config?: string; // Config file path
days?: number; // Analysis period: 1-180 (default: 90)
limit?: number; // Sample size: 1-1000 (default: 1000)
format?: string; // "json" | "table" | "markdown"
}Example Request:
{
"site": "sc-domain:example.com",
"days": 90,
"limit": 1000,
"format": "json"
}Example Response:
{
"success": true,
"operation": "index_coverage",
"site": "sc-domain:example.com",
"period": "2024-09-28 to 2024-12-27",
"analysis": {
"total_pages": 1000,
"indexed": 850,
"low_impressions": 100,
"no_impressions": 50
},
"classification": {
"healthy": 850,
"needs_review": 100,
"excluded": 50
},
"top_pages": [
{
"url": "https://example.com/",
"impressions": 5000,
"clicks": 450,
"status": "indexed"
}
],
"recommendations": [
"50 pages with no impressions may need content improvement or removal",
"100 pages with low impressions could benefit from SEO optimization"
]
}How It Works:
- Queries Search Analytics for page impressions
- Classifies pages: ≥10 impressions = indexed, 1-9 = low, 0 = none
- Samples up to 1,000 pages for performance
- Estimates overall index health
Use Cases:
- Monitor index health trends
- Identify indexing issues
- Track coverage after site updates
- Estimate indexed page count
Diagnostics & SEO Tools
gsc_traffic_compare - Compare GSC Traffic Between Date Ranges
Diffs Google Search Console search analytics between two date ranges per URL to surface the biggest drops and gains.
Before/after mode: instead of the two periods, pass around: { date, window_days?, gap_days? } (window_days 7-90, default 28; gap_days 0-14, default 0) to compare the window ending the day before date with the window starting at date + gap_days. Use a GA4 annotation date as date for a SEOTesting-style test. If the after window would end within 3 days of today, both windows are shortened equally with a warning. Add pages: string[] (exact URL match after normalize) to target the changed pages; with around or pages the response includes a before_after block with total clicks, impressions, CTR and position and their deltas. Passing both around and period_a/period_b, or neither, is an error.
Input Schema:
{
site: string; // Required: "sc-domain:example.com" or URL-prefix
period_a_start: string; // Required: ISO date "YYYY-MM-DD"
period_a_end: string; // Required: ISO date "YYYY-MM-DD"
period_b_start: string; // Required: ISO date "YYYY-MM-DD"
period_b_end: string; // Required: ISO date "YYYY-MM-DD"
fetch_limit?: number; // Max URLs to fetch per period (default 500)
output_limit?: number; // Max rows in output (default 50)
sort_by?: "clicks" | "impressions" | "delta"; // Sort mode (default "clicks")
min_clicks_a?: number; // Minimum clicks in period A to include URL
url_normalization?: "none" | "minimal" | "aggressive";
}Example Response:
{
"success": true,
"site": "sc-domain:example.com",
"period_a": { "start": "2024-12-01", "end": "2024-12-14" },
"period_b": { "start": "2024-11-17", "end": "2024-11-30" },
"summary": { "urls_analyzed": 120, "total_click_delta": -340 },
"rows": [
{
"url": "https://example.com/pricing",
"clicks_a": 45, "clicks_b": 120,
"delta_clicks": -75, "delta_pct": -62.5
}
]
}ga4_consent_health - Consent Banner Health Report
Reports consent banner grant/deny rates and a composite health score from consent_granted / consent_denied custom events in GA4.
Input Schema:
{
property_id: string; // Required: GA4 property ID (e.g. "123456789")
days?: number; // Lookback window in days (default 30)
}Example Response:
{
"success": true,
"property_id": "123456789",
"period_days": 30,
"consent_rate_pct": 72.4,
"consent_visibility_pct": 89.1,
"health_score": 64.5,
"granted": 14821,
"denied": 5641,
"note": "Uses consent_granted/consent_denied custom events. Requires Consent Mode v2 event instrumentation."
}seo_page_audit - On-Page SEO Audit
Single-URL on-page SEO audit: title/description length and pixel-width checks, canonical severity escalation, JSON-LD extraction, redirect chain tracing, robots.txt respect, and optional Core Web Vitals via PageSpeed Insights.
Input Schema:
{
url: string; // Required: page URL to audit
include_cwv?: boolean; // Include Core Web Vitals via PSI (default false)
psi_api_key?: string; // PageSpeed Insights API key (required for CWV)
strategy?: "mobile" | "desktop"; // PSI strategy (default "mobile")
}Example Response:
{
"success": true,
"url": "https://example.com/pricing",
"issues": [
{ "type": "title_too_long", "severity": "warning", "value": "62 chars / 498px" },
{ "type": "canonical_mismatch", "severity": "error", "canonical": "https://example.com/pricing/" }
],
"meta": {
"title": "Pricing - Example",
"description": "Choose the right plan.",
"canonical": "https://example.com/pricing/"
},
"redirects": [],
"schema_types": ["Product", "BreadcrumbList"]
}AdSense Tools
Publisher-side reporting via the AdSense Management API v2. These tools answer "how much do the ads on my own site earn" — the opposite of the advertiser-side Google Ads API. They are read-only and need no developer token or Manager account: just the adsense.readonly OAuth scope on the same credentials used for GA4/GSC (see PERMISSIONS.md).
Scope: Add
https://www.googleapis.com/auth/adsense.readonlyto your credential. For an ADC user credential, re-rungcloud auth application-default login --scopes=...,https://www.googleapis.com/auth/adsense.readonly. Note: a personal AdSense account cannot be read with a service-account key — use ADC user credentials.
adsense_accounts_list - List AdSense Accounts
Lists the AdSense publisher accounts the authenticated identity can access. Call this first — the returned name (accounts/pub-XXXXXXXXXXXXXXXX) is the account argument every other AdSense tool needs. No parameters.
Example Response:
{
"success": true,
"accounts": [
{ "name": "accounts/pub-1234567890123456", "displayName": "example.com", "state": "READY", "timeZone": { "id": "America/Los_Angeles" } }
]
}adsense_report - Generate AdSense Report
Generates an earnings report for one account, returning each row as a header → value map plus totals.
Input Schema:
{
account: string; // Required: "accounts/pub-..." from adsense_accounts_list
date_range?: // Preset window (default "LAST_7_DAYS")
| "CUSTOM" | "TODAY" | "YESTERDAY" | "MONTH_TO_DATE" | "YEAR_TO_DATE"
| "LAST_7_DAYS" | "LAST_30_DAYS";
// Anything longer (e.g. last 3 months): use "CUSTOM".
// The AdSense API rejects LAST_MONTH / LAST_3_MONTHS /
// LAST_6_MONTHS / LAST_12_MONTHS / LAST_YEAR.
start_date?: string; // YYYY-MM-DD, required when date_range = "CUSTOM"
end_date?: string; // YYYY-MM-DD, required when date_range = "CUSTOM"
metrics?: string[]; // default ESTIMATED_EARNINGS, PAGE_VIEWS, IMPRESSIONS, CLICKS, IMPRESSIONS_RPM
dimensions?: string[]; // default ["DATE"]; e.g. DOMAIN_NAME, COUNTRY_NAME, AD_UNIT_NAME
order_by?: string[]; // sort before limit; requested metric/dimension, "-" = desc,
// e.g. ["-ESTIMATED_EARNINGS"] for top earners first
currency_code?: string; // ISO-4217, e.g. "USD" (defaults to account currency)
limit?: number; // max rows (default 100, max 1000)
}Example Response:
{
"success": true,
"account": "accounts/pub-1234567890123456",
"date_range": "LAST_7_DAYS",
"start_date": "2026-06-19",
"end_date": "2026-06-25",
"headers": ["DATE", "ESTIMATED_EARNINGS", "PAGE_VIEWS", "IMPRESSIONS", "CLICKS", "IMPRESSIONS_RPM"],
"rows": [
{ "DATE": "2026-06-19", "ESTIMATED_EARNINGS": "1.23", "PAGE_VIEWS": "842", "IMPRESSIONS": "910", "CLICKS": "7", "IMPRESSIONS_RPM": "1.35" }
],
"totals": { "ESTIMATED_EARNINGS": "9.81", "PAGE_VIEWS": "5894" },
"warnings": []
}Growth, Traffic & Index-Quality Tools
site_growth_brief - Per-Site Growth Brief
The starting point for an SEO or monetization plan. For one config (or every configs/*.yaml with all: true, examples excluded) it joins Search Console, GA4 traffic and AdSense-in-GA4 revenue into a compact brief (≤ ~4 KB per site):
kpis— GSC clicks, impressions, CTR, position; GA4 sessions, engagement rate; ad revenue and revenue per 1k sessionsdeltas— % change vs the preceding window of the same lengthtop_pages— up to 10 pages joined on path (GSC clicks/impressions, sessions, ad revenue), ranked by revenue then clicksfindings— top 10 from page-level opportunities, CTR anomalies, URL-hygiene warnings and (whenhreflang_pairsare configured) hreflang warnings, ranked by estimated clicks at stakedata_gaps— sources that were not configured or failed; the brief still returns the rest
{ config?: string; all?: boolean; days?: number /* 7–240, default 28 */ }Revenue is GA4-attributed (totalAdRevenue), so it needs AdSense linked to the GA4 property; use adsense_report for payout figures. The brief never calls URL Inspection or the AdSense API.
ga4_traffic_report - GA4 Traffic & Ad Revenue
GA4 Data API report by up to two of pagePath, landingPage, sessionDefaultChannelGroup, sessionSource, country, deviceCategory, date. Every row carries sessions, engaged_sessions, engagement_rate, average_session_duration_s, page_views, ad_revenue, ad_impressions, ad_clicks and revenue_per_1k_sessions, plus totals. The warning ad_revenue_unavailable means no AdSense impressions were attributed (not linked, or no ads served).
{
property_id?: string; // "123456789" or "properties/123456789"
config?: string; // alternative: uses the config's ga4.property_id
days?: number; // 1–365, default 28, ending yesterday
dimensions?: string[]; // default ["pagePath"]
order_by?: "sessions" | "page_views" | "ad_revenue" | "engagement_rate"; // desc, default sessions
limit?: number; // 1–1000, default 50
}gsc_opportunities with granularity: "page"
Page mode ranks whole pages instead of query × page rows. It catches high-impression pages whose traffic is spread over anonymized long-tail queries, where no single query looks like an opportunity. The expected CTR is the higher of the bucket's site median (≥3 peer pages above min_impressions) and the industry baseline, so weak neighbours never excuse a page. Each result adds top_queries (up to 5) and anonymized_share (0–1). Costs 2 Search Analytics calls.
gsc_url_hygiene - Index-Polluting URLs
Classifies every URL with impressions (default 90 days) into at most one of malformed_path, fragment, asset_route, query_duplicate, utility_page (info). Each result has a one-line fix. Exit 2 / results non-empty on any warning.
gsc_hreflang - hreflang Integrity & Cross-Language Ranking
Uses search_console.hreflang_pairs from the config, or discovers pairs by fetching the top max_pages pages (default 50) and reading their <link rel="alternate" hreflang> tags. Reports missing_return_link, missing_self_reference, wrong_target, missing_x_default (info), and cross_language_ranking (a query in one pair language getting ≥ min_impressions on the other language's page). Fetches only the configured site's host, as ga4-manager/<version>, max 4 at a time.
search_console:
site_url: "sc-domain:example.com"
hreflang_pairs:
- en: "https://www.example.com/calculator"
es: "https://www.example.com/es/calculadora"Configuration Files
YAML Config Structure
All tools support YAML configuration files for project settings:
project:
name: Example Site
description: Production analytics configuration
version: 1.0.0
# GA4 Configuration
ga4:
property_id: "123456789"
tier: standard # or "360"
conversions:
- name: purchase
counting_method: ONCE_PER_EVENT
description: User completed purchase
- name: signup
counting_method: ONCE_PER_SESSION
description: User created account
dimensions:
- parameter: user_tier
display_name: User Tier
description: User subscription level
scope: USER
- parameter: product_category
display_name: Product Category
scope: EVENT
metrics:
- parameter: cart_value
display_name: Cart Value
description: Total cart value
unit: CURRENCY
scope: EVENT
# Search Console Configuration
search_console:
site_url: "https://example.com"
sitemaps:
- url: "https://example.com/sitemap.xml"
priority: true
monitoring:
priority_urls:
- url: "https://example.com/"
label: "Homepage"
- url: "https://example.com/products/"
label: "Products Landing Page"Example Templates
Ready-to-use templates in examples/:
minimal.yaml- Basic setup for learningblog.yaml- Content sites (5 conversions, 5 dimensions, 3 metrics)ecommerce.yaml- Online stores (7 conversions, 6 dimensions, 4 metrics)saas.yaml- Web applications (6 conversions, 5 dimensions, 3 metrics)
See examples/README.md for template documentation.
Usage Workflows
Initial Setup Workflow
// 1. Validate configuration
await ga4_validate({
config_file: "configs/example-site.yaml",
verbose: true
});
// 2. Preview setup (dry-run)
await ga4_setup({
config_path: "configs/example-site.yaml",
dry_run: true
});
// 3. Review output, then apply
await ga4_setup({
config_path: "configs/example-site.yaml",
dry_run: false
});
// 4. Verify with report
await ga4_report({
config_path: "configs/example-site.yaml"
});
// 5. Submit sitemaps
await gsc_sitemaps_submit({
site: "sc-domain:example.com",
url: "https://example.com/sitemap.xml"
});Analytics Analysis Workflow
// 1. Check index coverage
const coverage = await gsc_index_coverage({
site: "sc-domain:example.com",
days: 90,
limit: 1000
});
// 2. Get search performance
const analytics = await gsc_analytics_run({
site: "sc-domain:example.com",
days: 30,
dimensions: "query,page",
limit: 100
});
// 3. Inspect top pages
for (const page of analytics.rows.slice(0, 5)) {
await gsc_inspect_url({
site: "sc-domain:example.com",
url: page.keys[1] // page URL
});
}Monitoring Workflow
// 1. Monitor critical URLs
const monitoring = await gsc_monitor_urls({
site: "sc-domain:example.com",
urls: [
"https://example.com/",
"https://example.com/products/",
"https://example.com/blog/"
]
});
// 2. If issues found, inspect details
if (monitoring.summary.with_issues > 0) {
for (const result of monitoring.results) {
if (result.issues.length > 0) {
console.log(`Issues on ${result.url}:`, result.issues);
}
}
}Best Practices for AI Assistants
1. Always Use Dry-Run First
Before any modifying operation (setup, cleanup, delete), preview changes:
// ❌ DON'T do this first
await ga4_setup({ config_path: "config.yaml" });
// ✅ DO this instead
await ga4_setup({ config_path: "config.yaml", dry_run: true });
// Review output, then apply:
await ga4_setup({ config_path: "config.yaml", dry_run: false });2. Validate Configurations
Always validate before setup:
await ga4_validate({ config_file: "config.yaml", verbose: true });3. Monitor Quota Usage
Check quota.remaining in responses:
const result = await gsc_inspect_url({ ... });
if (result.quota.remaining < 100) {
console.warn("Low GSC quota remaining:", result.quota.remaining);
}4. Use Specific Dimensions
Request only needed dimensions to reduce response size:
// ✅ Good - specific dimensions
await gsc_analytics_run({
dimensions: "query,page",
limit: 50
});
// ❌ Avoid - too many dimensions
await gsc_analytics_run({
dimensions: "query,page,country,device,date",
limit: 25000 // Also too many rows
});5. Handle Errors Gracefully
All errors include actionable suggestions:
try {
await ga4_setup({ config_path: "config.yaml" });
} catch (error) {
console.error(error.code); // e.g., "AUTH_ERROR"
console.error(error.message); // Human-readable message
console.error(error.suggestion); // How to fix
}6. Provide Context to Users
When presenting results, explain what the data means:
const analytics = await gsc_analytics_run({ ... });
// ❌ Don't just dump raw data
console.log(analytics);
// ✅ Explain the insights
console.log(`Top query: "${analytics.rows[0].keys[0]}" with ${analytics.rows[0].clicks} clicks`);
console.log(`Average CTR: ${(analytics.aggregates.average_ctr * 100).toFixed(2)}%`);Error Handling
Error Response Structure
All errors follow this format:
{
"code": "AUTH_ERROR",
"message": "Missing Google credentials",
"details": {
"suggestion": "Set GOOGLE_APPLICATION_CREDENTIALS environment variable",
"stderr": "Error: could not find credentials file",
"exit_code": 1
}
}Common Error Codes
| Code | Meaning | Solution |
|------|---------|----------|
| AUTH_ERROR | Missing/invalid credentials | Set GOOGLE_APPLICATION_CREDENTIALS |
| VALIDATION_ERROR | Invalid input parameters | Check parameter types and values |
| API_ERROR | Google API error | Check quota, permissions, property ID |
| CLI_EXECUTION_FAILED | CLI command failed | Check GA4_BINARY_PATH, build binary |
| TIMEOUT | Operation timed out | Increase timeout, check network |
| FILE_NOT_FOUND | Config file missing | Verify file path is absolute |
Quota Errors
Search Console has strict quotas:
{
"code": "API_ERROR",
"message": "GSC quota exceeded",
"details": {
"quota": {
"used": 2000,
"limit": 2000,
"remaining": 0
},
"suggestion": "Wait 24 hours for quota reset"
}
}Rate Limits
Google Search Console
- Daily limit: 2,000 queries per site
- Per-minute limit: 600 queries per site
- Shared across: All GSC operations (inspect, analytics, sitemaps, coverage)
Google Analytics 4 Admin API
- Requests: 50 per second per project
- No daily limit (but stay within reasonable use)
MCP Server
- No built-in rate limiting (relies on CLI rate limiting)
- CLI has configurable rate limiter (default: 10 RPS)
Development
Build
npm run build # Compile TypeScript to dist/
npm run dev # Run with tsx (no compile)Testing
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:run # Single run (CI)Linting
npm run lint # ESLint checkArchitecture
mcp/
├── src/
│ ├── index.ts # MCP server entry
│ ├── cli/
│ │ ├── executor.ts # Subprocess execution
│ │ └── parser.ts # Output parsing
│ ├── tools/ # 16 tool implementations
│ │ ├── ga4-setup.ts
│ │ ├── ga4-report.ts
│ │ ├── ga4-cleanup.ts
│ │ ├── ga4-link.ts
│ │ ├── ga4-validate.ts
│ │ ├── gsc-sitemaps.ts # 4 sitemap tools
│ │ ├── gsc-inspect.ts
│ │ ├── gsc-analytics.ts
│ │ ├── gsc-monitor.ts
│ │ └── gsc-coverage.ts
│ ├── utils/
│ │ ├── ansi-strip.ts # Terminal color removal
│ │ └── errors.ts # Error mapping
│ └── types/
│ ├── cli.ts # CLI response types
│ └── mcp.ts # MCP schemas
└── tests/ # comprehensive testsTroubleshooting
Server Not Connecting
Symptom: Claude Desktop doesn't show ga4-manager
Solution:
- Verify
nodein PATH:which node - Check config uses absolute paths (not relative)
- Test manually:
node /path/to/mcp/dist/index.js - Check Claude Desktop logs for errors
Binary Not Found
Symptom: Error: spawn ga4 ENOENT
Solution:
- Set
GA4_BINARY_PATHenvironment variable - Build binary:
cd .. && make build - Verify:
ls -la /path/to/ga4
Authentication Errors
Symptom: Missing Google credentials
Solution:
- Set
GOOGLE_APPLICATION_CREDENTIALSin MCP config - Verify file exists:
ls -la /path/to/credentials.json - Check credentials have required OAuth scopes:
https://www.googleapis.com/auth/analytics.edithttps://www.googleapis.com/auth/analytics.readonlyhttps://www.googleapis.com/auth/webmasters
Quota Exceeded
Symptom: GSC quota exceeded
Solution:
- GSC daily limit: 2,000 queries
- Wait 24 hours for reset
- Monitor
quota.remainingin responses - Use
dry_run: truefor testing
Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| GOOGLE_APPLICATION_CREDENTIALS | Yes | Path to service account JSON |
| GOOGLE_CLOUD_PROJECT | Yes | GCP project ID |
| GA4_BINARY_PATH | Yes | Path to ga4 binary |
| GA4_DEFAULT_PROPERTY_ID | No | Default property ID (optional) |
| GSC_DEFAULT_SITE | No | Default GSC site URL (optional) |
Additional Documentation
- CONFIGURATION.md - Multi-client setup guides
- CHANGELOG.md - Version history
- examples/README.md - Template documentation
- ../CLAUDE.md - CLI documentation for Claude Code
- ../README.md - Main project README
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: See SECURITY.md for vulnerability reporting
License
MIT - See LICENSE for details
Version History
See CHANGELOG.md for complete version history and release notes.
Current Version: 2.2.0
