Skip to main content
Glama
johnoconnor0

Google Ads MCP Server

by johnoconnor0
mcp_tools_keywords.py40.4 kB
""" MCP Tools for Keyword Management Keyword management tools for Google Ads MCP Server. """ from mcp.server.fastmcp import FastMCP from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any from enum import Enum import json from auth_manager import get_auth_manager from error_handler import ErrorHandler from logger import performance_logger, audit_logger, get_logger from cache_manager import get_cache_manager, ResourceType from keyword_manager import ( KeywordManager, KeywordConfig, KeywordMatchType, KeywordStatus ) logger = get_logger(__name__) def register_keyword_tools(mcp: FastMCP): """Register keyword management tools with MCP server.""" # ============================================================================ # Keyword Addition # ============================================================================ @mcp.tool() def google_ads_add_keywords( customer_id: str, ad_group_id: str, keywords: List[Dict[str, Any]], cpc_bid: Optional[float] = None ) -> str: """ Add keywords to an ad group. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID keywords: List of keyword dicts with 'text' and 'match_type' (EXACT, PHRASE, BROAD) cpc_bid: Optional default CPC bid for all keywords in currency units Returns: Success message with keyword count Example: keywords = [ {"text": "running shoes", "match_type": "PHRASE"}, {"text": "nike shoes", "match_type": "EXACT"}, {"text": "athletic footwear", "match_type": "BROAD"} ] """ with performance_logger.track_operation('add_keywords', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) # Convert to KeywordConfig objects cpc_bid_micros = int(cpc_bid * 1_000_000) if cpc_bid else None keyword_configs = [ KeywordConfig( text=kw['text'], match_type=KeywordMatchType[kw['match_type'].upper()], ad_group_id=ad_group_id, cpc_bid_micros=cpc_bid_micros, status=KeywordStatus[kw.get('status', 'ENABLED').upper()] ) for kw in keywords ] # Add keywords result = keyword_manager.add_keywords(customer_id, keyword_configs) # Audit log audit_logger.log_api_call( customer_id=customer_id, operation="add_keywords", resource_type="keyword", action="create", result="success", details={ 'ad_group_id': ad_group_id, 'keyword_count': len(keywords), 'cpc_bid': cpc_bid } ) # Invalidate cache get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD) output = f"✅ Keywords added successfully!\n\n" output += f"**Keywords Added**: {result['keywords_added']}\n" output += f"**Ad Group ID**: {ad_group_id}\n" if cpc_bid: output += f"**Default CPC Bid**: ${cpc_bid:.2f}\n" output += "\n**Added Keywords**:\n" for kw in keywords[:10]: # Show first 10 output += f"- {kw['text']} ({kw['match_type']})\n" if len(keywords) > 10: output += f"... and {len(keywords) - 10} more\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="add_keywords") return f"❌ Failed to add keywords: {error_msg}" @mcp.tool() def google_ads_add_negative_keywords( customer_id: str, ad_group_id: str, keywords: List[Dict[str, str]] ) -> str: """ Add negative keywords to an ad group. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID keywords: List of keyword dicts with 'text' and 'match_type' Returns: Success message Example: keywords = [ {"text": "cheap", "match_type": "BROAD"}, {"text": "free", "match_type": "BROAD"} ] """ with performance_logger.track_operation('add_negative_keywords', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) result = keyword_manager.add_negative_keywords( customer_id, ad_group_id, keywords ) # Audit log audit_logger.log_api_call( customer_id=customer_id, operation="add_negative_keywords", resource_type="keyword", action="create", result="success", details={ 'ad_group_id': ad_group_id, 'negative_keyword_count': len(keywords) } ) # Invalidate cache get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD) output = f"✅ Negative keywords added successfully!\n\n" output += f"**Negative Keywords Added**: {result['negative_keywords_added']}\n\n" output += "**Added Negative Keywords**:\n" for kw in keywords[:10]: output += f"- {kw['text']} ({kw['match_type']})\n" if len(keywords) > 10: output += f"... and {len(keywords) - 10} more\n" output += "\nThese keywords will prevent your ads from showing when searched." return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="add_negative_keywords") return f"❌ Failed to add negative keywords: {error_msg}" # ============================================================================ # Keyword Updates # ============================================================================ @mcp.tool() def google_ads_update_keyword_bid( customer_id: str, ad_group_id: str, criterion_id: str, cpc_bid: float ) -> str: """ Update keyword CPC bid. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID criterion_id: Keyword criterion ID cpc_bid: New CPC bid in currency units (e.g., 1.50 for $1.50) Returns: Success message """ with performance_logger.track_operation('update_keyword_bid', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) cpc_bid_micros = int(cpc_bid * 1_000_000) result = keyword_manager.update_keyword_bid( customer_id, ad_group_id, criterion_id, cpc_bid_micros ) # Audit log audit_logger.log_api_call( customer_id=customer_id, operation="update_keyword_bid", resource_type="keyword", resource_id=criterion_id, action="update", result="success", details={'new_cpc_bid': cpc_bid} ) # Invalidate cache get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD) return ( f"✅ Keyword bid updated successfully!\n\n" f"**Criterion ID**: {criterion_id}\n" f"**New CPC Bid**: ${result['new_cpc_bid']:.2f}\n\n" f"The new bid will take effect immediately." ) except Exception as e: error_msg = ErrorHandler.handle_error(e, context="update_keyword_bid") return f"❌ Failed to update keyword bid: {error_msg}" @mcp.tool() def google_ads_update_keyword_status( customer_id: str, ad_group_id: str, criterion_id: str, status: str ) -> str: """ Update keyword status (enable, pause, or remove). Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID criterion_id: Keyword criterion ID status: New status (ENABLED, PAUSED, or REMOVED) Returns: Success message """ with performance_logger.track_operation('update_keyword_status', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) status_upper = status.upper() result = keyword_manager.update_keyword_status( customer_id, ad_group_id, criterion_id, KeywordStatus[status_upper] ) # Audit log audit_logger.log_api_call( customer_id=customer_id, operation="update_keyword_status", resource_type="keyword", resource_id=criterion_id, action="update", result="success", details={'new_status': status_upper} ) # Invalidate cache get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD) status_messages = { "ENABLED": "Keyword is now active and will trigger ads.", "PAUSED": "Keyword is now paused and will not trigger ads.", "REMOVED": "Keyword has been removed." } return ( f"✅ Keyword status updated to {status_upper}\n\n" f"**Criterion ID**: {criterion_id}\n\n" f"{status_messages.get(status_upper, 'Status updated successfully.')}" ) except Exception as e: error_msg = ErrorHandler.handle_error(e, context="update_keyword_status") return f"❌ Failed to update keyword status: {error_msg}" # ============================================================================ # Keyword Information # ============================================================================ @mcp.tool() def google_ads_get_keyword_performance( customer_id: str, ad_group_id: Optional[str] = None, date_range: str = "LAST_30_DAYS" ) -> str: """ Get keyword performance metrics. Args: customer_id: Customer ID (without hyphens) ad_group_id: Optional ad group ID to filter by date_range: Date range (TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS, etc.) Returns: Keyword performance report """ with performance_logger.track_operation('get_keyword_performance', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) keywords = keyword_manager.get_keyword_performance( customer_id, ad_group_id=ad_group_id, date_range=date_range ) if not keywords: return "No keyword data found for the specified criteria." output = f"# Keyword Performance ({date_range})\n\n" output += f"**Total Keywords**: {len(keywords)}\n\n" # Show top 20 by cost for kw in keywords[:20]: output += f"## {kw['keyword_text']} ({kw['match_type']})\n" output += f"- **Status**: {kw['status']}\n" output += f"- **Campaign**: {kw['campaign']['name']}\n" output += f"- **Ad Group**: {kw['ad_group']['name']}\n" if kw['cpc_bid']: output += f"- **CPC Bid**: ${kw['cpc_bid']:.2f}\n" if kw['quality_score']: output += f"- **Quality Score**: {kw['quality_score']}/10\n" metrics = kw['metrics'] output += f"- **Cost**: ${metrics['cost']:,.2f}\n" output += f"- **Clicks**: {metrics['clicks']:,}\n" output += f"- **Impressions**: {metrics['impressions']:,}\n" output += f"- **CTR**: {metrics['ctr']:.2f}%\n" output += f"- **Avg CPC**: ${metrics['average_cpc']:.2f}\n" output += f"- **Conversions**: {metrics['conversions']:.2f}\n" if metrics['cost_per_conversion'] > 0: output += f"- **Cost/Conv**: ${metrics['cost_per_conversion']:.2f}\n" output += "\n" if len(keywords) > 20: output += f"... and {len(keywords) - 20} more keywords\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="get_keyword_performance") return f"❌ Failed to get keyword performance: {error_msg}" @mcp.tool() def google_ads_list_keywords( customer_id: str, ad_group_id: str ) -> str: """ List all keywords in an ad group. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID Returns: List of keywords """ with performance_logger.track_operation('list_keywords', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) keywords = keyword_manager.list_keywords(customer_id, ad_group_id) if not keywords: return f"No keywords found in ad group {ad_group_id}" # Separate positive and negative keywords positive_kws = [kw for kw in keywords if not kw['negative']] negative_kws = [kw for kw in keywords if kw['negative']] output = f"# Keywords in Ad Group {ad_group_id}\n\n" if positive_kws: output += f"## Positive Keywords ({len(positive_kws)})\n\n" for kw in positive_kws: output += f"- **{kw['keyword_text']}** ({kw['match_type']})\n" output += f" - Status: {kw['status']}\n" if kw['cpc_bid']: output += f" - CPC Bid: ${kw['cpc_bid']:.2f}\n" output += f" - ID: {kw['criterion_id']}\n\n" if negative_kws: output += f"## Negative Keywords ({len(negative_kws)})\n\n" for kw in negative_kws: output += f"- **{kw['keyword_text']}** ({kw['match_type']})\n" output += f" - ID: {kw['criterion_id']}\n\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="list_keywords") return f"❌ Failed to list keywords: {error_msg}" @mcp.tool() def google_ads_get_keyword_quality_score( customer_id: str, ad_group_id: str, criterion_id: str ) -> str: """ Get detailed quality score information for a keyword. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID criterion_id: Keyword criterion ID Returns: Quality score details """ with performance_logger.track_operation('get_keyword_quality_score', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) quality_data = keyword_manager.get_keyword_quality_score( customer_id, ad_group_id, criterion_id ) if not quality_data: return f"❌ Keyword {criterion_id} not found" output = f"# Quality Score: {quality_data['keyword_text']}\n\n" output += f"**Match Type**: {quality_data['match_type']}\n\n" if quality_data['quality_score']: output += f"## Overall Quality Score: {quality_data['quality_score']}/10\n\n" else: output += "## Overall Quality Score: Not yet available\n\n" output += "## Quality Score Components\n\n" output += f"- **Expected CTR**: {quality_data['expected_ctr']}\n" output += f"- **Ad Relevance (Creative Quality)**: {quality_data['creative_quality']}\n" output += f"- **Landing Page Experience**: {quality_data['landing_page_experience']}\n\n" output += "### What This Means\n\n" output += "Quality Score is rated on a scale of 1-10:\n" output += "- 7-10: Above Average\n" output += "- 4-6: Average\n" output += "- 1-3: Below Average\n\n" output += "Each component is rated as:\n" output += "- ABOVE_AVERAGE: Better than most advertisers\n" output += "- AVERAGE: Similar to most advertisers\n" output += "- BELOW_AVERAGE: Lower than most advertisers\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="get_keyword_quality_score") return f"❌ Failed to get quality score: {error_msg}" # ============================================================================ # Search Terms # ============================================================================ @mcp.tool() def google_ads_get_search_terms_for_keyword( customer_id: str, ad_group_id: str, criterion_id: Optional[str] = None, date_range: str = "LAST_30_DAYS" ) -> str: """ Get search terms that triggered ads for keywords in an ad group. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID criterion_id: Optional specific keyword criterion ID date_range: Date range (TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS, etc.) Returns: Search terms report with performance data """ with performance_logger.track_operation('get_search_terms', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) search_terms = keyword_manager.get_search_terms_for_keyword( customer_id, ad_group_id, criterion_id=criterion_id, date_range=date_range ) if not search_terms: return "No search term data found for the specified criteria." output = f"# Search Terms Report ({date_range})\n\n" output += f"**Total Search Terms**: {len(search_terms)}\n\n" # Show top 30 by impressions for st in search_terms[:30]: output += f"## \"{st['search_term']}\"\n" output += f"- **Triggered By Keyword**: {st['keyword_text']}\n" output += f"- **Status**: {st['status']}\n" output += f"- **Impressions**: {st['impressions']:,}\n" output += f"- **Clicks**: {st['clicks']:,}\n" output += f"- **CTR**: {st['ctr']:.2f}%\n" output += f"- **Cost**: ${st['cost']:,.2f}\n" output += f"- **Conversions**: {st['conversions']:.2f}\n\n" if len(search_terms) > 30: output += f"... and {len(search_terms) - 30} more search terms\n\n" output += "**Tip**: Review search terms to identify:\n" output += "- New keyword opportunities (high-performing terms to add as keywords)\n" output += "- Negative keywords (irrelevant terms to exclude)\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="get_search_terms") return f"❌ Failed to get search terms: {error_msg}" # ============================================================================ # Bulk Operations # ============================================================================ @mcp.tool() def google_ads_bulk_add_keywords( customer_id: str, ad_group_id: str, keyword_texts: List[str], match_type: str = "PHRASE", cpc_bid: Optional[float] = None ) -> str: """ Bulk add multiple keywords with the same match type. Args: customer_id: Customer ID (without hyphens) ad_group_id: Ad group ID keyword_texts: List of keyword text strings match_type: Match type for all keywords (EXACT, PHRASE, or BROAD, default: PHRASE) cpc_bid: Optional CPC bid for all keywords in currency units Returns: Success message Example: keyword_texts = ["running shoes", "athletic shoes", "sport shoes"] """ with performance_logger.track_operation('bulk_add_keywords', customer_id=customer_id): try: # Build keywords list keywords = [ {"text": text, "match_type": match_type} for text in keyword_texts ] # Use existing add_keywords function client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) cpc_bid_micros = int(cpc_bid * 1_000_000) if cpc_bid else None keyword_configs = [ KeywordConfig( text=kw['text'], match_type=KeywordMatchType[match_type.upper()], ad_group_id=ad_group_id, cpc_bid_micros=cpc_bid_micros ) for kw in keywords ] result = keyword_manager.add_keywords(customer_id, keyword_configs) # Audit log audit_logger.log_api_call( customer_id=customer_id, operation="bulk_add_keywords", resource_type="keyword", action="create", result="success", details={ 'ad_group_id': ad_group_id, 'keyword_count': len(keyword_texts), 'match_type': match_type, 'cpc_bid': cpc_bid } ) # Invalidate cache get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD) output = f"✅ Bulk keywords added successfully!\n\n" output += f"**Keywords Added**: {result['keywords_added']}\n" output += f"**Match Type**: {match_type}\n" if cpc_bid: output += f"**CPC Bid**: ${cpc_bid:.2f}\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="bulk_add_keywords") return f"❌ Failed to bulk add keywords: {error_msg}" @mcp.tool() def google_ads_bulk_update_keyword_bids( customer_id: str, bid_updates: List[Dict[str, Any]] ) -> str: """ Update bids for multiple keywords at once. Args: customer_id: Customer ID (without hyphens) bid_updates: List of dicts with 'ad_group_id', 'criterion_id', 'cpc_bid' Returns: Success message Example: bid_updates = [ {"ad_group_id": "123", "criterion_id": "456", "cpc_bid": 2.50}, {"ad_group_id": "123", "criterion_id": "789", "cpc_bid": 3.00} ] """ with performance_logger.track_operation('bulk_update_keyword_bids', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) # Convert cpc_bid to micros updates_with_micros = [ { 'ad_group_id': update['ad_group_id'], 'criterion_id': update['criterion_id'], 'cpc_bid_micros': int(update['cpc_bid'] * 1_000_000) } for update in bid_updates ] result = keyword_manager.bulk_update_keyword_bids( customer_id, updates_with_micros ) # Audit log audit_logger.log_api_call( customer_id=customer_id, operation="bulk_update_keyword_bids", resource_type="keyword", action="update", result="success", details={'keyword_count': len(bid_updates)} ) # Invalidate cache get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD) output = f"✅ Bulk bid update completed!\n\n" output += f"**Keywords Updated**: {result['keywords_updated']}\n\n" output += f"{result['message']}" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="bulk_update_keyword_bids") return f"❌ Failed to bulk update keyword bids: {error_msg}" # ============================================================================ # Traffic Estimation # ============================================================================ @mcp.tool() def google_ads_estimate_keyword_traffic( customer_id: str, keywords: List[str], location_ids: Optional[List[str]] = None ) -> str: """ Get traffic estimates for keywords. Args: customer_id: Customer ID (without hyphens) keywords: List of keyword texts to estimate location_ids: Optional location IDs for targeting (e.g., ["2840"] for United States) Returns: Traffic estimates Note: This is a placeholder. Full implementation requires Keyword Plan API setup. """ with performance_logger.track_operation('estimate_keyword_traffic', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) result = keyword_manager.estimate_keyword_traffic( customer_id, keywords, location_ids=location_ids ) output = f"# Keyword Traffic Estimation\n\n" output += f"**Keywords Analyzed**: {result['keywords_analyzed']}\n\n" output += f"{result['message']}\n\n" output += f"**Note**: {result['note']}\n\n" output += "To enable full traffic estimation:\n" output += "1. Set up Keyword Planner in your Google Ads account\n" output += "2. Create a Keyword Plan via the API\n" output += "3. Use KeywordPlanIdeaService for detailed forecasts\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="estimate_keyword_traffic") return f"❌ Failed to estimate keyword traffic: {error_msg}" @mcp.tool() def google_ads_keyword_ideas( customer_id: str, seed_keywords: str = "", page_url: str = "", location_ids: str = "2840", language_id: str = "1000", keyword_plan_network: str = "GOOGLE_SEARCH", response_format: str = "markdown" ) -> str: """Get keyword ideas from Google Ads Keyword Planner. Generate keyword suggestions based on seed keywords or a webpage URL. Includes search volume, competition level, and bid estimates. Args: customer_id: Google Ads customer ID (10 digits, no hyphens) seed_keywords: Comma-separated seed keywords (e.g., "running shoes,nike") page_url: Optional URL to extract keywords from location_ids: Comma-separated location criterion IDs (default: 2840 = US) language_id: Language criterion ID (default: 1000 = English) keyword_plan_network: Network - GOOGLE_SEARCH, GOOGLE_SEARCH_AND_PARTNERS, or YOUTUBE response_format: Output format (markdown or json) Returns: Keyword ideas with metrics (search volume, competition, bids) Example: google_ads_keyword_ideas( customer_id="1234567890", seed_keywords="running shoes,athletic footwear", location_ids="2840", # US language_id="1000" # English ) Common Location IDs: - 2840: United States - 2826: United Kingdom - 2124: Canada - 2036: Australia Competition Levels: - LOW: Easy to rank for - MEDIUM: Moderate competition - HIGH: Very competitive """ with performance_logger.track_operation('get_keyword_ideas', customer_id=customer_id): try: client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) # Parse inputs seed_kws = [kw.strip() for kw in seed_keywords.split(",") if kw.strip()] if seed_keywords else None location_list = [loc.strip() for loc in location_ids.split(",") if loc.strip()] result = keyword_manager.get_keyword_ideas( customer_id=customer_id, seed_keywords=seed_kws, page_url=page_url if page_url else None, location_ids=location_list, language_id=language_id, keyword_plan_network=keyword_plan_network ) audit_logger.log_api_call( operation="get_keyword_ideas", customer_id=customer_id, details={ "seed_keywords": seed_kws, "page_url": page_url, "total_ideas": result['total_ideas'] }, response={"total_ideas": result['total_ideas']} ) if response_format.lower() == "json": return str(result) # Format markdown output output = f"# Keyword Ideas\n\n" output += f"**Total Ideas**: {result['total_ideas']}\n" if seed_kws: output += f"**Seed Keywords**: {', '.join(seed_kws)}\n" if page_url: output += f"**Page URL**: {page_url}\n" output += f"**Locations**: {', '.join(result['locations'])}\n" output += f"**Language**: {result['language']}\n" output += f"**Network**: {keyword_plan_network}\n\n" output += "## Top Keyword Ideas\n\n" output += "| Keyword | Avg Monthly Searches | Competition | Competition Index | Low Bid | High Bid |\n" output += "|---------|---------------------|-------------|------------------|---------|----------|\n" # Sort by search volume and show top 50 sorted_ideas = sorted( result['keyword_ideas'], key=lambda x: x['avg_monthly_searches'], reverse=True )[:50] for idea in sorted_ideas: output += f"| {idea['keyword_text']} | " output += f"{idea['avg_monthly_searches']:,} | " output += f"{idea['competition']} | " output += f"{idea['competition_index']}/100 | " output += f"${idea['low_top_of_page_bid']:.2f} | " output += f"${idea['high_top_of_page_bid']:.2f} |\n" if len(result['keyword_ideas']) > 50: output += f"\n*Showing top 50 of {result['total_ideas']} total keyword ideas*\n" output += "\n## Competition Guide\n" output += "- **LOW**: Easy to rank for, less competitive\n" output += "- **MEDIUM**: Moderate competition\n" output += "- **HIGH**: Very competitive, higher bids needed\n" output += "- **Competition Index**: 0-100 scale (higher = more competitive)\n\n" output += "## Bid Estimates\n" output += "- **Low Bid**: Lower end of top-of-page bid range\n" output += "- **High Bid**: Upper end of top-of-page bid range\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="get_keyword_ideas") return f"❌ Failed to get keyword ideas: {error_msg}" @mcp.tool() def google_ads_keyword_forecast( customer_id: str, keywords_json: str, location_ids: str = "2840", language_id: str = "1000", cpc_bid: float = 1.0, date_interval: str = "NEXT_MONTH", response_format: str = "markdown" ) -> str: """Forecast traffic metrics for specific keywords. Get projected impressions, clicks, and costs for keywords over a future time period. Args: customer_id: Google Ads customer ID (10 digits, no hyphens) keywords_json: JSON array of keywords with text and match_type Example: [{"text": "running shoes", "match_type": "BROAD"}] location_ids: Comma-separated location criterion IDs (default: 2840 = US) language_id: Language criterion ID (default: 1000 = English) cpc_bid: CPC bid amount for forecast (default: 1.0) date_interval: Forecast period - NEXT_WEEK, NEXT_MONTH, or NEXT_QUARTER response_format: Output format (markdown or json) Returns: Traffic forecast with projected metrics Example: google_ads_keyword_forecast( customer_id="1234567890", keywords_json='[{"text": "running shoes", "match_type": "BROAD"}, {"text": "nike shoes", "match_type": "PHRASE"}]', cpc_bid=2.5, date_interval="NEXT_MONTH" ) Match Types: - BROAD: Matches variations and related searches - PHRASE: Matches phrase and close variants - EXACT: Matches exact keyword only Date Intervals: - NEXT_WEEK: 7-day forecast - NEXT_MONTH: 30-day forecast - NEXT_QUARTER: 90-day forecast """ with performance_logger.track_operation('forecast_keyword_metrics', customer_id=customer_id): try: import json client = get_auth_manager().get_client() keyword_manager = KeywordManager(client) # Parse keywords JSON try: keywords = json.loads(keywords_json) except json.JSONDecodeError as e: return f"❌ Invalid JSON format for keywords: {str(e)}" # Parse locations location_list = [loc.strip() for loc in location_ids.split(",") if loc.strip()] # Convert CPC to micros cpc_bid_micros = int(cpc_bid * 1_000_000) result = keyword_manager.forecast_keyword_metrics( customer_id=customer_id, keywords=keywords, location_ids=location_list, language_id=language_id, cpc_bid_micros=cpc_bid_micros, date_interval=date_interval ) audit_logger.log_api_call( operation="forecast_keyword_metrics", customer_id=customer_id, details={ "keywords_count": len(keywords), "cpc_bid": cpc_bid, "date_interval": date_interval }, response={"keywords_forecasted": result['keywords_forecasted']} ) if response_format.lower() == "json": return str(result) # Format markdown output output = f"# Keyword Traffic Forecast\n\n" output += f"**Keywords Forecasted**: {result['keywords_forecasted']}\n" output += f"**Forecast Period**: {result['forecast_period']}\n" if result['cpc_bid']: output += f"**CPC Bid**: ${result['cpc_bid']:.2f}\n" output += f"**Locations**: {', '.join(location_list)}\n" output += f"**Language**: {language_id}\n\n" output += "## Keywords Being Forecasted\n\n" for i, kw in enumerate(keywords, 1): output += f"{i}. **{kw['text']}** ({kw.get('match_type', 'BROAD')})\n" output += "\n## Forecast Metrics\n\n" output += f"**Note**: {result['note']}\n\n" output += "Expected metrics structure:\n" for metric, value in result['forecast_metrics'].items(): output += f"- **{metric.replace('_', ' ').title()}**: {value}\n" output += "\n## About Forecasts\n" output += "Keyword forecasts require creating a temporary keyword plan which:\n" output += "1. Creates a keyword plan structure\n" output += "2. Adds campaigns, ad groups, and keywords\n" output += "3. Generates traffic projections\n" output += "4. Cleans up temporary resources\n\n" output += "Forecast metrics include:\n" output += "- **Impressions**: Projected ad views\n" output += "- **Clicks**: Projected clicks\n" output += "- **Cost**: Projected spend\n" output += "- **CTR**: Click-through rate\n" output += "- **Average CPC**: Average cost per click\n" return output except Exception as e: error_msg = ErrorHandler.handle_error(e, context="forecast_keyword_metrics") return f"❌ Failed to forecast keyword metrics: {error_msg}" logger.info("Keyword management tools registered")

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnoconnor0/google-ads-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server