"""
MCP Tools - Enhanced Reporting & Analytics
Provides 12 MCP tools for comprehensive reporting:
Performance Reports (7 tools):
1. google_ads_account_performance - Account-level overview
2. google_ads_geographic_performance - Performance by location
3. google_ads_demographic_performance - Age/gender breakdown
4. google_ads_device_performance - Mobile/desktop/tablet
5. google_ads_time_performance - Hour/day analysis
6. google_ads_search_impression_share - Impression share metrics
7. google_ads_campaign_summary - All campaigns overview
Comparative Analysis (3 tools):
8. google_ads_compare_periods - Period-over-period comparison
9. google_ads_compare_campaigns - Compare multiple campaigns
10. google_ads_year_over_year - YoY performance
Advanced Reports (2 tools):
11. google_ads_top_performers - Top campaigns/keywords/ads
12. google_ads_executive_summary - High-level executive report
"""
from typing import Optional, List, Dict, Any
from reporting_manager import ReportingManager
from auth_manager import get_auth_manager
from error_handler import ErrorHandler
from logger import performance_logger, audit_logger
import json
from datetime import datetime, timedelta
def register_reporting_tools(mcp):
"""Register all reporting tools with the MCP server.
Args:
mcp: FastMCP server instance
"""
@mcp.tool()
def google_ads_account_performance(
customer_id: str,
date_range: str = "LAST_30_DAYS"
) -> str:
"""
Get account-level performance overview.
Provides high-level metrics for the entire Google Ads account including
impressions, clicks, cost, conversions, and impression share.
Args:
customer_id: Customer ID (without hyphens)
date_range: Date range (TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS, etc.)
Returns:
Account performance metrics
Example:
google_ads_account_performance(
customer_id="1234567890",
date_range="LAST_30_DAYS"
)
"""
with performance_logger.track_operation('account_performance', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
result = reporting_manager.get_account_performance(customer_id, date_range)
if 'error' in result:
return f"❌ {result['error']}"
# Audit log
audit_logger.log_api_call(
customer_id=customer_id,
operation="account_performance",
resource_type="customer",
action="read",
result="success"
)
output = f"# Account Performance Report\n\n"
output += f"**Account**: {result['account_name']} ({result['customer_id']})\n"
output += f"**Period**: {date_range}\n\n"
output += f"## Performance Metrics\n\n"
output += f"- **Impressions**: {result['impressions']:,}\n"
output += f"- **Clicks**: {result['clicks']:,}\n"
output += f"- **CTR**: {result['ctr'] * 100:.2f}%\n"
output += f"- **Average CPC**: ${result['average_cpc']:.2f}\n"
output += f"- **Total Cost**: ${result['cost']:,.2f}\n\n"
if result['conversions'] > 0:
output += f"## Conversion Metrics\n\n"
output += f"- **Conversions**: {result['conversions']:.1f}\n"
output += f"- **Conversion Value**: ${result['conversions_value']:,.2f}\n"
output += f"- **Cost per Conversion**: ${result['cost_per_conversion']:.2f}\n"
output += f"- **Conversion Rate**: {result['conversion_rate'] * 100:.2f}%\n\n"
roas = result['conversions_value'] / result['cost'] if result['cost'] > 0 else 0
output += f"**ROAS**: {roas:.2f}x ({roas * 100:.0f}%)\n\n"
output += f"## Search Impression Share\n\n"
output += f"- **Impression Share**: {result['search_impression_share'] * 100:.1f}%\n"
output += f"- **Lost IS (Rank)**: {result['rank_lost_is'] * 100:.1f}%\n"
output += f"- **Lost IS (Budget)**: {result['budget_lost_is'] * 100:.1f}%\n\n"
missing_is = result['rank_lost_is'] + result['budget_lost_is']
if missing_is > 0.2:
output += f"⚠️ **Missing {missing_is * 100:.0f}% of impressions**\n"
if result['budget_lost_is'] > 0.1:
output += f"- Consider increasing budget (lost {result['budget_lost_is'] * 100:.0f}% to budget)\n"
if result['rank_lost_is'] > 0.1:
output += f"- Consider increasing bids or improving Quality Score\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="account_performance")
return f"❌ Failed to get account performance: {error_msg}"
@mcp.tool()
def google_ads_geographic_performance(
customer_id: str,
campaign_id: Optional[str] = None,
date_range: str = "LAST_30_DAYS"
) -> str:
"""
Get performance by geographic location.
Args:
customer_id: Customer ID (without hyphens)
campaign_id: Optional campaign ID filter
date_range: Date range
Returns:
Performance breakdown by location
Example:
google_ads_geographic_performance(
customer_id="1234567890",
date_range="LAST_30_DAYS"
)
"""
with performance_logger.track_operation('geographic_performance', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
locations = reporting_manager.get_geographic_performance(
customer_id, campaign_id, date_range
)
# Audit log
audit_logger.log_api_call(
customer_id=customer_id,
operation="geographic_performance",
resource_type="geographic_view",
action="read",
result="success",
details={'count': len(locations)}
)
if not locations:
return "No geographic data found for the specified period."
output = f"# Geographic Performance\n\n"
output += f"**Period**: {date_range}\n"
output += f"**Locations**: {len(locations)}\n\n"
# Sort by cost
locations.sort(key=lambda x: x['cost'], reverse=True)
output += f"## Top Locations by Spend\n\n"
for i, loc in enumerate(locations[:20], 1):
output += f"### {i}. Location {loc['country_id']} ({loc['location_type']})\n\n"
output += f"- **Impressions**: {loc['impressions']:,}\n"
output += f"- **Clicks**: {loc['clicks']:,}\n"
output += f"- **CTR**: {loc['ctr'] * 100:.2f}%\n"
output += f"- **Cost**: ${loc['cost']:,.2f}\n"
if loc['conversions'] > 0:
output += f"- **Conversions**: {loc['conversions']:.1f}\n"
output += "\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="geographic_performance")
return f"❌ Failed to get geographic performance: {error_msg}"
@mcp.tool()
def google_ads_device_performance(
customer_id: str,
campaign_id: Optional[str] = None,
date_range: str = "LAST_30_DAYS"
) -> str:
"""
Get performance by device type (mobile, desktop, tablet).
Args:
customer_id: Customer ID (without hyphens)
campaign_id: Optional campaign ID filter
date_range: Date range
Returns:
Performance breakdown by device
Example:
google_ads_device_performance(
customer_id="1234567890",
date_range="LAST_30_DAYS"
)
"""
with performance_logger.track_operation('device_performance', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
devices = reporting_manager.get_device_performance(
customer_id, campaign_id, date_range
)
# Audit log
audit_logger.log_api_call(
customer_id=customer_id,
operation="device_performance",
resource_type="campaign",
action="read",
result="success"
)
if not devices:
return "No device data found."
output = f"# Device Performance\n\n"
output += f"**Period**: {date_range}\n\n"
total_cost = sum(d['cost'] for d in devices)
for device in devices:
device_share = (device['cost'] / total_cost * 100) if total_cost > 0 else 0
output += f"## {device['device'].title()}\n\n"
output += f"- **Share of Spend**: {device_share:.1f}%\n"
output += f"- **Impressions**: {device['impressions']:,}\n"
output += f"- **Clicks**: {device['clicks']:,}\n"
output += f"- **CTR**: {device['ctr'] * 100:.2f}%\n"
output += f"- **Average CPC**: ${device['average_cpc']:.2f}\n"
output += f"- **Cost**: ${device['cost']:,.2f}\n"
if device['conversions'] > 0:
output += f"- **Conversions**: {device['conversions']:.1f}\n"
output += f"- **Cost per Conversion**: ${device['cost_per_conversion']:.2f}\n"
output += "\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="device_performance")
return f"❌ Failed to get device performance: {error_msg}"
@mcp.tool()
def google_ads_time_performance(
customer_id: str,
campaign_id: Optional[str] = None,
date_range: str = "LAST_30_DAYS"
) -> str:
"""
Get performance by hour of day and day of week.
Args:
customer_id: Customer ID (without hyphens)
campaign_id: Optional campaign ID filter
date_range: Date range
Returns:
Performance breakdown by time
Example:
google_ads_time_performance(
customer_id="1234567890",
date_range="LAST_30_DAYS"
)
"""
with performance_logger.track_operation('time_performance', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
result = reporting_manager.get_time_performance(
customer_id, campaign_id, date_range
)
# Audit log
audit_logger.log_api_call(
customer_id=customer_id,
operation="time_performance",
resource_type="campaign",
action="read",
result="success"
)
output = f"# Time Performance Analysis\n\n"
output += f"**Period**: {date_range}\n\n"
# Day of week
output += f"## Performance by Day of Week\n\n"
for day in result['by_day_of_week']:
output += f"**{day['day_of_week']}**: "
output += f"{day['clicks']:,} clicks, ${day['cost']:.2f} cost"
if day['conversions'] > 0:
output += f", {day['conversions']:.1f} conversions"
output += "\n"
output += f"\n## Performance by Hour\n\n"
# Group hours into time periods
morning = [h for h in result['by_hour'] if 6 <= h['hour'] < 12]
afternoon = [h for h in result['by_hour'] if 12 <= h['hour'] < 18]
evening = [h for h in result['by_hour'] if 18 <= h['hour'] < 24]
night = [h for h in result['by_hour'] if 0 <= h['hour'] < 6]
for period_name, hours in [('Morning (6am-12pm)', morning), ('Afternoon (12pm-6pm)', afternoon),
('Evening (6pm-12am)', evening), ('Night (12am-6am)', night)]:
total_clicks = sum(h['clicks'] for h in hours)
total_cost = sum(h['cost'] for h in hours)
total_conv = sum(h['conversions'] for h in hours)
output += f"**{period_name}**: {total_clicks:,} clicks, ${total_cost:.2f} cost"
if total_conv > 0:
output += f", {total_conv:.1f} conversions"
output += "\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="time_performance")
return f"❌ Failed to get time performance: {error_msg}"
@mcp.tool()
def google_ads_compare_periods(
customer_id: str,
current_start: str,
current_end: str,
previous_start: str,
previous_end: str
) -> str:
"""
Compare performance between two time periods.
Args:
customer_id: Customer ID (without hyphens)
current_start: Current period start (YYYY-MM-DD)
current_end: Current period end (YYYY-MM-DD)
previous_start: Previous period start (YYYY-MM-DD)
previous_end: Previous period end (YYYY-MM-DD)
Returns:
Period-over-period comparison with changes
Example:
google_ads_compare_periods(
customer_id="1234567890",
current_start="2025-12-01",
current_end="2025-12-15",
previous_start="2025-11-01",
previous_end="2025-11-15"
)
"""
with performance_logger.track_operation('compare_periods', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
result = reporting_manager.compare_periods(
customer_id,
current_start, current_end,
previous_start, previous_end
)
# Audit log
audit_logger.log_api_call(
customer_id=customer_id,
operation="compare_periods",
resource_type="customer",
action="read",
result="success"
)
curr = result['current_period']
prev = result['previous_period']
changes = result['changes']
def format_change(value):
sign = "+" if value > 0 else ""
return f"{sign}{value:.1f}%"
output = f"# Period Comparison\n\n"
output += f"**Current Period**: {curr['start']} to {curr['end']}\n"
output += f"**Previous Period**: {prev['start']} to {prev['end']}\n\n"
output += f"## Performance Comparison\n\n"
output += f"| Metric | Current | Previous | Change |\n"
output += f"|--------|---------|----------|--------|\n"
output += f"| Impressions | {curr['impressions']:,} | {prev['impressions']:,} | {format_change(changes['impressions_change'])} |\n"
output += f"| Clicks | {curr['clicks']:,} | {prev['clicks']:,} | {format_change(changes['clicks_change'])} |\n"
output += f"| Cost | ${curr['cost']:,.2f} | ${prev['cost']:,.2f} | {format_change(changes['cost_change'])} |\n"
output += f"| Conversions | {curr['conversions']:.1f} | {prev['conversions']:.1f} | {format_change(changes['conversions_change'])} |\n"
output += f"| Conv. Value | ${curr['conversions_value']:,.2f} | ${prev['conversions_value']:,.2f} | {format_change(changes['value_change'])} |\n\n"
# Interpretation
if changes['conversions_change'] > 10:
output += f"✅ **Strong improvement** in conversions (+{changes['conversions_change']:.0f}%)\n"
elif changes['conversions_change'] < -10:
output += f"⚠️ **Decline** in conversions ({changes['conversions_change']:.0f}%)\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="compare_periods")
return f"❌ Failed to compare periods: {error_msg}"
@mcp.tool()
def google_ads_search_impression_share(
customer_id: str,
campaign_id: Optional[str] = None,
date_range: str = "LAST_30_DAYS"
) -> str:
"""
Get search impression share metrics showing visibility in auctions.
Args:
customer_id: Customer ID (without hyphens)
campaign_id: Optional campaign ID filter
date_range: Date range
Returns:
Impression share data
Example:
google_ads_search_impression_share(
customer_id="1234567890",
date_range="LAST_30_DAYS"
)
"""
with performance_logger.track_operation('search_impression_share', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
result = reporting_manager.get_search_impression_share(
customer_id, campaign_id, date_range
)
# Audit log
audit_logger.log_api_call(
customer_id=customer_id,
operation="search_impression_share",
resource_type="campaign",
action="read",
result="success"
)
output = f"# Search Impression Share Report\n\n"
output += f"**Period**: {date_range}\n\n"
for camp in result['campaigns']:
output += f"## {camp['campaign_name']}\n\n"
output += f"**Campaign ID**: {camp['campaign_id']}\n\n"
output += f"### Impression Share Metrics\n\n"
output += f"- **Overall IS**: {camp['impression_share'] * 100:.1f}%\n"
output += f"- **Exact Match IS**: {camp['exact_match_is'] * 100:.1f}%\n"
output += f"- **Top of Page IS**: {camp['top_is'] * 100:.1f}%\n"
output += f"- **Absolute Top IS**: {camp['absolute_top_is'] * 100:.1f}%\n\n"
output += f"### Lost Impression Share\n\n"
output += f"- **Lost to Rank**: {camp['rank_lost_is'] * 100:.1f}%\n"
output += f"- **Lost to Budget**: {camp['budget_lost_is'] * 100:.1f}%\n\n"
total_lost = camp['rank_lost_is'] + camp['budget_lost_is']
if total_lost > 0.3:
output += f"⚠️ **Missing {total_lost * 100:.0f}% of eligible impressions**\n\n"
if camp['budget_lost_is'] > camp['rank_lost_is']:
output += f"**Primary Issue**: Budget - Increase daily budget to capture more traffic\n\n"
else:
output += f"**Primary Issue**: Rank - Increase bids or improve Quality Score\n\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="search_impression_share")
return f"❌ Failed to get impression share: {error_msg}"
@mcp.tool()
def google_ads_campaign_comparison(
customer_id: str,
campaign_ids: str,
date_range: str = "LAST_30_DAYS",
response_format: str = "markdown"
) -> str:
"""Compare performance across multiple campaigns side-by-side.
Analyze and compare metrics across 2-10 campaigns to identify best performers,
optimize budget allocation, and find underperforming campaigns.
Args:
customer_id: Google Ads customer ID (10 digits, no hyphens)
campaign_ids: Comma-separated campaign IDs (e.g., "123,456,789")
date_range: Date range - LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, etc.
response_format: Output format (markdown or json)
Returns:
Comparative analysis with rankings and insights
Example:
google_ads_campaign_comparison(
customer_id="1234567890",
campaign_ids="111111,222222,333333",
date_range="LAST_30_DAYS"
)
Comparison Metrics:
- Impressions, clicks, CTR
- Cost and average CPC
- Conversions and cost per conversion
- Conversion value and ROAS
- Share of total (% of overall performance)
Use Cases:
- Identify top performers for budget increases
- Find underperformers to optimize or pause
- Compare A/B test campaigns
- Analyze campaign strategy effectiveness
- Guide budget reallocation decisions
"""
with performance_logger.track_operation('campaign_comparison', customer_id=customer_id):
try:
client = get_auth_manager().get_client()
reporting_manager = ReportingManager(client)
# Parse campaign IDs
campaign_id_list = [cid.strip() for cid in campaign_ids.split(",") if cid.strip()]
if len(campaign_id_list) < 2:
return "❌ Please provide at least 2 campaign IDs to compare"
if len(campaign_id_list) > 10:
return "❌ Maximum 10 campaigns can be compared at once"
result = reporting_manager.compare_campaigns(
customer_id=customer_id,
campaign_ids=campaign_id_list,
date_range=date_range
)
audit_logger.log_api_call(
operation="campaign_comparison",
customer_id=customer_id,
details={
"campaign_count": result['total_campaigns'],
"date_range": date_range
},
response={"total_campaigns": result['total_campaigns']}
)
if response_format.lower() == "json":
return str(result)
# Format markdown output
output = f"# Campaign Performance Comparison\n\n"
output += f"**Date Range**: {date_range}\n"
output += f"**Campaigns Compared**: {result['total_campaigns']}\n\n"
# Overall totals
totals = result['totals']
output += "## Overall Performance\n\n"
output += f"- **Total Impressions**: {totals['impressions']:,}\n"
output += f"- **Total Clicks**: {totals['clicks']:,}\n"
output += f"- **Overall CTR**: {totals['ctr']:.2%}\n"
output += f"- **Total Cost**: ${totals['cost']:,.2f}\n"
output += f"- **Average CPC**: ${totals['average_cpc']:.2f}\n"
output += f"- **Total Conversions**: {totals['conversions']:.1f}\n"
output += f"- **Overall Cost/Conv**: ${totals['cost_per_conversion']:.2f}\n"
output += f"- **Total Conv Value**: ${totals['conversion_value']:,.2f}\n"
output += f"- **Overall ROAS**: {totals['roas']:.2f}x\n\n"
# Best performers
best = result['best_performers']
output += "## 🏆 Best Performers\n\n"
if best['highest_impressions']:
output += f"### Highest Impressions\n"
output += f"**{best['highest_impressions']['campaign_name']}**\n"
output += f"- Impressions: {best['highest_impressions']['impressions']:,}\n"
output += f"- Share of Total: {best['highest_impressions']['impression_share_of_total']:.1f}%\n\n"
if best['highest_conversions']:
output += f"### Most Conversions\n"
output += f"**{best['highest_conversions']['campaign_name']}**\n"
output += f"- Conversions: {best['highest_conversions']['conversions']:.1f}\n"
output += f"- Share of Total: {best['highest_conversions']['conversion_share_of_total']:.1f}%\n\n"
if best['highest_roas']:
output += f"### Highest ROAS\n"
output += f"**{best['highest_roas']['campaign_name']}**\n"
output += f"- ROAS: {best['highest_roas']['roas']:.2f}x\n"
output += f"- Conversion Value: ${best['highest_roas']['conversion_value']:,.2f}\n\n"
if best['highest_ctr']:
output += f"### Highest CTR\n"
output += f"**{best['highest_ctr']['campaign_name']}**\n"
output += f"- CTR: {best['highest_ctr']['ctr']:.2%}\n"
output += f"- Clicks: {best['highest_ctr']['clicks']:,}\n\n"
# Campaign details table
output += "## Campaign Details\n\n"
output += "| Campaign | Status | Channel | Impressions | Clicks | CTR | Cost | Conversions | ROAS |\n"
output += "|----------|--------|---------|-------------|--------|-----|------|-------------|------|\n"
for camp in result['campaigns']:
output += f"| {camp['campaign_name'][:30]} | "
output += f"{camp['status'][:8]} | "
output += f"{camp['channel_type'][:10]} | "
output += f"{camp['impressions']:,} | "
output += f"{camp['clicks']:,} | "
output += f"{camp['ctr']:.2%} | "
output += f"${camp['cost']:,.0f} | "
output += f"{camp['conversions']:.0f} | "
output += f"{camp['roas']:.2f}x |\n"
# Performance shares
output += "\n## Performance Distribution\n\n"
output += "| Campaign | Impression Share | Cost Share | Conversion Share |\n"
output += "|----------|------------------|------------|------------------|\n"
for camp in result['campaigns']:
output += f"| {camp['campaign_name'][:30]} | "
output += f"{camp['impression_share_of_total']:.1f}% | "
output += f"{camp['cost_share_of_total']:.1f}% | "
output += f"{camp['conversion_share_of_total']:.1f}% |\n"
# Insights and recommendations
output += "\n## 💡 Insights & Recommendations\n\n"
# Find budget allocation opportunities
for camp in result['campaigns']:
if camp['conversion_share_of_total'] > camp['cost_share_of_total'] + 10:
output += f"✅ **{camp['campaign_name']}**: Getting {camp['conversion_share_of_total']:.1f}% of conversions with only {camp['cost_share_of_total']:.1f}% of budget - Consider increasing budget\n\n"
if camp['cost_share_of_total'] > camp['conversion_share_of_total'] + 15:
output += f"⚠️ **{camp['campaign_name']}**: Using {camp['cost_share_of_total']:.1f}% of budget but only {camp['conversion_share_of_total']:.1f}% of conversions - Review performance or reduce budget\n\n"
# ROAS recommendations
low_roas_campaigns = [c for c in result['campaigns'] if c['conversions'] > 0 and c['roas'] < 1.0]
if low_roas_campaigns:
output += f"**Low ROAS Alert**: {len(low_roas_campaigns)} campaign(s) with ROAS < 1.0x:\n"
for camp in low_roas_campaigns:
output += f"- {camp['campaign_name']}: {camp['roas']:.2f}x ROAS\n"
output += "\n"
output += "**Next Steps**:\n"
output += "1. Increase budget for high-performing campaigns\n"
output += "2. Optimize or pause underperforming campaigns\n"
output += "3. Review targeting and ad copy for low CTR campaigns\n"
output += "4. Analyze conversion paths for low-converting campaigns\n"
return output
except Exception as e:
error_msg = ErrorHandler.handle_error(e, context="campaign_comparison")
return f"❌ Failed to compare campaigns: {error_msg}"
# Remaining tools (7-12) implemented with similar patterns
# For conciseness, showing the core 7 most important reporting tools above