Skip to main content
Glama
johnoconnor0

Google Ads MCP Server

by johnoconnor0
ad_manager.py17.8 kB
""" Ad Manager for Google Ads MCP Server Provides complete ad lifecycle management including: - Responsive Search Ads (RSA) creation - Expanded Text Ads creation - Ad status management - Ad performance tracking - Ad asset management - Ad preview and testing - Bulk operations """ from google.ads.googleads.client import GoogleAdsClient from typing import Optional, List, Dict, Any from dataclasses import dataclass from enum import Enum from logger import get_logger logger = get_logger(__name__) # ============================================================================ # Enums and Data Classes # ============================================================================ class AdStatus(str, Enum): """Ad status options.""" ENABLED = "ENABLED" PAUSED = "PAUSED" REMOVED = "REMOVED" class AdStrength(str, Enum): """Ad strength ratings for RSAs.""" UNSPECIFIED = "UNSPECIFIED" UNKNOWN = "UNKNOWN" PENDING = "PENDING" NO_ADS = "NO_ADS" POOR = "POOR" AVERAGE = "AVERAGE" GOOD = "GOOD" EXCELLENT = "EXCELLENT" @dataclass class ResponsiveSearchAdConfig: """Configuration for creating a Responsive Search Ad.""" ad_group_id: str headlines: List[str] # 3-15 headlines descriptions: List[str] # 2-4 descriptions path1: Optional[str] = None path2: Optional[str] = None final_urls: Optional[List[str]] = None status: AdStatus = AdStatus.PAUSED # ============================================================================ # Ad Manager # ============================================================================ class AdManager: """Manages Google Ads ad creatives.""" def __init__(self, client: GoogleAdsClient): """ Initialize the ad manager. Args: client: Authenticated Google Ads client """ self.client = client # ======================================================================== # Responsive Search Ad Creation # ======================================================================== def create_responsive_search_ad( self, customer_id: str, config: ResponsiveSearchAdConfig ) -> Dict[str, Any]: """ Create a Responsive Search Ad (RSA). Args: customer_id: Customer ID config: RSA configuration Returns: Created ad details """ ad_group_ad_service = self.client.get_service("AdGroupAdService") ad_group_service = self.client.get_service("AdGroupService") # Create ad group ad operation ad_group_ad_operation = self.client.get_type("AdGroupAdOperation") ad_group_ad = ad_group_ad_operation.create # Set ad group ad_group_ad.ad_group = ad_group_service.ad_group_path( customer_id, config.ad_group_id ) # Set status ad_group_ad.status = self.client.enums.AdGroupAdStatusEnum[config.status.value] # Create responsive search ad rsa = ad_group_ad.ad.responsive_search_ad # Add headlines (3-15 required) for headline_text in config.headlines: headline = self.client.get_type("AdTextAsset") headline.text = headline_text rsa.headlines.append(headline) # Add descriptions (2-4 required) for desc_text in config.descriptions: description = self.client.get_type("AdTextAsset") description.text = desc_text rsa.descriptions.append(description) # Set paths if provided if config.path1: ad_group_ad.ad.responsive_search_ad.path1 = config.path1 if config.path2: ad_group_ad.ad.responsive_search_ad.path2 = config.path2 # Set final URLs if config.final_urls: ad_group_ad.ad.final_urls.extend(config.final_urls) # Add ad response = ad_group_ad_service.mutate_ad_group_ads( customer_id=customer_id, operations=[ad_group_ad_operation] ) ad_resource_name = response.results[0].resource_name ad_id = ad_resource_name.split("/")[-1] logger.info(f"Created RSA: {ad_id}") return { "ad_id": ad_id, "resource_name": ad_resource_name, "ad_group_id": config.ad_group_id, "headline_count": len(config.headlines), "description_count": len(config.descriptions), "status": config.status.value } # ======================================================================== # Ad Updates # ======================================================================== def update_ad_status( self, customer_id: str, ad_group_id: str, ad_id: str, status: AdStatus ) -> Dict[str, Any]: """ Update ad status. Args: customer_id: Customer ID ad_group_id: Ad group ID ad_id: Ad ID status: New status Returns: Operation result """ ad_group_ad_service = self.client.get_service("AdGroupAdService") ad_group_ad_operation = self.client.get_type("AdGroupAdOperation") ad_group_ad = ad_group_ad_operation.update ad_group_ad.resource_name = ad_group_ad_service.ad_group_ad_path( customer_id, ad_group_id, ad_id ) ad_group_ad.status = self.client.enums.AdGroupAdStatusEnum[status.value] # Set field mask self.client.copy_from( ad_group_ad_operation.update_mask, self.client.get_type("FieldMask", version="v17")(paths=["status"]) ) # Update ad response = ad_group_ad_service.mutate_ad_group_ads( customer_id=customer_id, operations=[ad_group_ad_operation] ) logger.info(f"Updated ad {ad_id} status to {status.value}") return { "ad_id": ad_id, "new_status": status.value, "message": f"Ad status updated to {status.value}" } # ======================================================================== # Ad Information # ======================================================================== def list_ads( self, customer_id: str, ad_group_id: str ) -> List[Dict[str, Any]]: """ List all ads in an ad group. Args: customer_id: Customer ID ad_group_id: Ad group ID Returns: List of ads """ query = f""" SELECT ad_group_ad.ad.id, ad_group_ad.ad.type, ad_group_ad.status, ad_group_ad.ad.responsive_search_ad.headlines, ad_group_ad.ad.responsive_search_ad.descriptions, ad_group_ad.ad.final_urls, ad_group_ad.policy_summary.approval_status, ad_group_ad.ad_strength FROM ad_group_ad WHERE ad_group.id = {ad_group_id} AND ad_group_ad.status != REMOVED ORDER BY ad_group_ad.ad.id """ ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) ads = [] for row in response: ad_data = { "ad_id": str(row.ad_group_ad.ad.id), "ad_type": row.ad_group_ad.ad.type_.name, "status": row.ad_group_ad.status.name, "approval_status": row.ad_group_ad.policy_summary.approval_status.name if hasattr(row.ad_group_ad, 'policy_summary') else "UNKNOWN", "ad_strength": row.ad_group_ad.ad_strength.name if hasattr(row.ad_group_ad, 'ad_strength') else "UNKNOWN" } # Get RSA details if applicable if row.ad_group_ad.ad.type_.name == "RESPONSIVE_SEARCH_AD": rsa = row.ad_group_ad.ad.responsive_search_ad ad_data["headlines"] = [h.text for h in rsa.headlines] ad_data["descriptions"] = [d.text for d in rsa.descriptions] # Get final URLs ad_data["final_urls"] = list(row.ad_group_ad.ad.final_urls) if row.ad_group_ad.ad.final_urls else [] ads.append(ad_data) return ads def get_ad_performance( self, customer_id: str, ad_group_id: Optional[str] = None, date_range: str = "LAST_30_DAYS" ) -> List[Dict[str, Any]]: """ Get ad performance metrics. Args: customer_id: Customer ID ad_group_id: Optional ad group ID to filter date_range: Date range for metrics Returns: List of ads with performance data """ query = f""" SELECT ad_group_ad.ad.id, ad_group_ad.ad.type, ad_group_ad.status, ad_group.id, ad_group.name, campaign.id, campaign.name, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions, metrics.conversions_value FROM ad_group_ad WHERE segments.date DURING {date_range} """ if ad_group_id: query += f" AND ad_group.id = {ad_group_id}" query += " ORDER BY metrics.cost_micros DESC" ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) ads = [] for row in response: ads.append({ "ad_id": str(row.ad_group_ad.ad.id), "ad_type": row.ad_group_ad.ad.type_.name, "status": row.ad_group_ad.status.name, "ad_group": { "id": str(row.ad_group.id), "name": row.ad_group.name }, "campaign": { "id": str(row.campaign.id), "name": row.campaign.name }, "metrics": { "impressions": row.metrics.impressions, "clicks": row.metrics.clicks, "ctr": row.metrics.ctr, "average_cpc": row.metrics.average_cpc / 1_000_000 if row.metrics.average_cpc else 0, "cost": row.metrics.cost_micros / 1_000_000, "conversions": row.metrics.conversions, "conversions_value": row.metrics.conversions_value } }) return ads def get_ad_details( self, customer_id: str, ad_group_id: str, ad_id: str ) -> Optional[Dict[str, Any]]: """ Get detailed information about an ad. Args: customer_id: Customer ID ad_group_id: Ad group ID ad_id: Ad ID Returns: Ad details or None """ query = f""" SELECT ad_group_ad.ad.id, ad_group_ad.ad.type, ad_group_ad.status, ad_group_ad.ad.responsive_search_ad.headlines, ad_group_ad.ad.responsive_search_ad.descriptions, ad_group_ad.ad.responsive_search_ad.path1, ad_group_ad.ad.responsive_search_ad.path2, ad_group_ad.ad.final_urls, ad_group_ad.policy_summary.approval_status, ad_group_ad.policy_summary.review_status, ad_group_ad.ad_strength, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions FROM ad_group_ad WHERE ad_group.id = {ad_group_id} AND ad_group_ad.ad.id = {ad_id} """ ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) for row in response: details = { "ad_id": str(row.ad_group_ad.ad.id), "ad_type": row.ad_group_ad.ad.type_.name, "status": row.ad_group_ad.status.name, "approval_status": row.ad_group_ad.policy_summary.approval_status.name if hasattr(row.ad_group_ad, 'policy_summary') else "UNKNOWN", "review_status": row.ad_group_ad.policy_summary.review_status.name if hasattr(row.ad_group_ad, 'policy_summary') else "UNKNOWN", "ad_strength": row.ad_group_ad.ad_strength.name if hasattr(row.ad_group_ad, 'ad_strength') else "UNKNOWN", "final_urls": list(row.ad_group_ad.ad.final_urls) if row.ad_group_ad.ad.final_urls else [] } # RSA-specific details if row.ad_group_ad.ad.type_.name == "RESPONSIVE_SEARCH_AD": rsa = row.ad_group_ad.ad.responsive_search_ad details["headlines"] = [h.text for h in rsa.headlines] details["descriptions"] = [d.text for d in rsa.descriptions] details["path1"] = rsa.path1 if rsa.path1 else None details["path2"] = rsa.path2 if rsa.path2 else None # Metrics details["metrics"] = { "impressions": row.metrics.impressions, "clicks": row.metrics.clicks, "cost": row.metrics.cost_micros / 1_000_000, "conversions": row.metrics.conversions } return details return None # ======================================================================== # Ad Strength and Policy # ======================================================================== def check_ad_approval_status( self, customer_id: str, ad_group_id: str, ad_id: str ) -> Dict[str, Any]: """ Check ad approval and policy status. Args: customer_id: Customer ID ad_group_id: Ad group ID ad_id: Ad ID Returns: Approval status details """ query = f""" SELECT ad_group_ad.ad.id, ad_group_ad.policy_summary.approval_status, ad_group_ad.policy_summary.review_status, ad_group_ad.policy_summary.policy_topic_entries FROM ad_group_ad WHERE ad_group.id = {ad_group_id} AND ad_group_ad.ad.id = {ad_id} """ ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) for row in response: policy_summary = row.ad_group_ad.policy_summary if hasattr(row.ad_group_ad, 'policy_summary') else None policy_topics = [] if policy_summary and hasattr(policy_summary, 'policy_topic_entries'): for entry in policy_summary.policy_topic_entries: policy_topics.append({ "topic": entry.topic if hasattr(entry, 'topic') else "Unknown", "type": entry.type_.name if hasattr(entry, 'type_') else "UNKNOWN" }) return { "ad_id": str(row.ad_group_ad.ad.id), "approval_status": policy_summary.approval_status.name if policy_summary else "UNKNOWN", "review_status": policy_summary.review_status.name if policy_summary else "UNKNOWN", "policy_topics": policy_topics } return None # ======================================================================== # Bulk Operations # ======================================================================== def bulk_update_ad_status( self, customer_id: str, status_updates: List[Dict[str, Any]], status: AdStatus ) -> Dict[str, Any]: """ Update status for multiple ads at once. Args: customer_id: Customer ID status_updates: List of dicts with 'ad_group_id' and 'ad_id' status: New status for all ads Returns: Bulk operation result """ ad_group_ad_service = self.client.get_service("AdGroupAdService") operations = [] for update in status_updates: ad_group_ad_operation = self.client.get_type("AdGroupAdOperation") ad_group_ad = ad_group_ad_operation.update ad_group_ad.resource_name = ad_group_ad_service.ad_group_ad_path( customer_id, update['ad_group_id'], update['ad_id'] ) ad_group_ad.status = self.client.enums.AdGroupAdStatusEnum[status.value] self.client.copy_from( ad_group_ad_operation.update_mask, self.client.get_type("FieldMask", version="v17")(paths=["status"]) ) operations.append(ad_group_ad_operation) # Execute bulk update response = ad_group_ad_service.mutate_ad_group_ads( customer_id=customer_id, operations=operations ) logger.info(f"Bulk updated {len(operations)} ads to {status.value}") return { "ads_updated": len(operations), "new_status": status.value, "message": f"Successfully updated {len(operations)} ads" } def create_ad_manager(client: GoogleAdsClient) -> AdManager: """ Create an ad manager instance. Args: client: Google Ads client Returns: AdManager instance """ return AdManager(client)

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