Skip to main content
Glama
johnoconnor0

Google Ads MCP Server

by johnoconnor0
ad_group_manager.py18.9 kB
""" Ad Group Manager for Google Ads MCP Server Provides complete ad group lifecycle management including: - Ad group creation and configuration - Status and bid management - Ad group targeting - Performance retrieval """ 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 AdGroupStatus(str, Enum): """Ad group status options.""" ENABLED = "ENABLED" PAUSED = "PAUSED" REMOVED = "REMOVED" class AdGroupType(str, Enum): """Ad group type options.""" SEARCH_STANDARD = "SEARCH_STANDARD" DISPLAY_STANDARD = "DISPLAY_STANDARD" SHOPPING_PRODUCT_ADS = "SHOPPING_PRODUCT_ADS" VIDEO_BUMPER = "VIDEO_BUMPER" VIDEO_TRUE_VIEW_IN_STREAM = "VIDEO_TRUE_VIEW_IN_STREAM" VIDEO_TRUE_VIEW_IN_DISPLAY = "VIDEO_TRUE_VIEW_IN_DISPLAY" VIDEO_NON_SKIPPABLE_IN_STREAM = "VIDEO_NON_SKIPPABLE_IN_STREAM" VIDEO_OUTSTREAM = "VIDEO_OUTSTREAM" @dataclass class AdGroupConfig: """Configuration for creating an ad group.""" name: str campaign_id: str status: AdGroupStatus = AdGroupStatus.PAUSED cpc_bid_micros: Optional[int] = None # Manual CPC bid cpm_bid_micros: Optional[int] = None # CPM bid (display) cpv_bid_micros: Optional[int] = None # CPV bid (video) target_cpa_micros: Optional[int] = None # Target CPA percent_cpc_bid_micros: Optional[int] = None # Enhanced CPC ad_group_type: Optional[AdGroupType] = None # ============================================================================ # Ad Group Manager # ============================================================================ class AdGroupManager: """Manages Google Ads ad groups.""" def __init__(self, client: GoogleAdsClient): """ Initialize the ad group manager. Args: client: Authenticated Google Ads client """ self.client = client # ======================================================================== # Ad Group Creation # ======================================================================== def create_ad_group( self, customer_id: str, config: AdGroupConfig ) -> Dict[str, Any]: """ Create a new ad group. Args: customer_id: Customer ID config: Ad group configuration Returns: Created ad group details """ ad_group_service = self.client.get_service("AdGroupService") campaign_service = self.client.get_service("CampaignService") # Create operation ad_group_operation = self.client.get_type("AdGroupOperation") ad_group = ad_group_operation.create # Set basic fields ad_group.name = config.name ad_group.campaign = campaign_service.campaign_path( customer_id, config.campaign_id ) ad_group.status = self.client.enums.AdGroupStatusEnum[config.status.value] # Set ad group type if specified if config.ad_group_type: ad_group.type_ = self.client.enums.AdGroupTypeEnum[config.ad_group_type.value] # Set bidding (only set the relevant bid type) if config.cpc_bid_micros is not None: ad_group.cpc_bid_micros = config.cpc_bid_micros elif config.cpm_bid_micros is not None: ad_group.cpm_bid_micros = config.cpm_bid_micros elif config.cpv_bid_micros is not None: ad_group.cpv_bid_micros = config.cpv_bid_micros elif config.target_cpa_micros is not None: ad_group.target_cpa_micros = config.target_cpa_micros elif config.percent_cpc_bid_micros is not None: ad_group.percent_cpc_bid_micros = config.percent_cpc_bid_micros # Create ad group response = ad_group_service.mutate_ad_groups( customer_id=customer_id, operations=[ad_group_operation] ) ad_group_resource_name = response.results[0].resource_name ad_group_id = ad_group_resource_name.split("/")[-1] logger.info(f"Created ad group: {ad_group_id} in campaign {config.campaign_id}") return { "ad_group_id": ad_group_id, "resource_name": ad_group_resource_name, "name": config.name, "campaign_id": config.campaign_id, "status": config.status.value } # ======================================================================== # Ad Group Updates # ======================================================================== def update_ad_group( self, customer_id: str, ad_group_id: str, updates: Dict[str, Any] ) -> Dict[str, Any]: """ Update ad group settings. Args: customer_id: Customer ID ad_group_id: Ad group ID updates: Dictionary of fields to update (name, status, cpc_bid_micros, etc.) Returns: Operation result """ ad_group_service = self.client.get_service("AdGroupService") ad_group_operation = self.client.get_type("AdGroupOperation") ad_group = ad_group_operation.update ad_group.resource_name = ad_group_service.ad_group_path(customer_id, ad_group_id) # Track updated fields for field mask update_mask_paths = [] # Update name if "name" in updates: ad_group.name = updates["name"] update_mask_paths.append("name") # Update status if "status" in updates: ad_group.status = self.client.enums.AdGroupStatusEnum[updates["status"]] update_mask_paths.append("status") # Update CPC bid if "cpc_bid_micros" in updates: ad_group.cpc_bid_micros = updates["cpc_bid_micros"] update_mask_paths.append("cpc_bid_micros") # Update CPM bid if "cpm_bid_micros" in updates: ad_group.cpm_bid_micros = updates["cpm_bid_micros"] update_mask_paths.append("cpm_bid_micros") # Update CPV bid if "cpv_bid_micros" in updates: ad_group.cpv_bid_micros = updates["cpv_bid_micros"] update_mask_paths.append("cpv_bid_micros") # Set field mask self.client.copy_from( ad_group_operation.update_mask, self.client.get_type("FieldMask", version="v17")(paths=update_mask_paths) ) # Update ad group response = ad_group_service.mutate_ad_groups( customer_id=customer_id, operations=[ad_group_operation] ) logger.info(f"Updated ad group {ad_group_id}: {', '.join(update_mask_paths)}") return { "ad_group_id": ad_group_id, "updated_fields": update_mask_paths, "message": f"Ad group updated successfully" } def update_ad_group_status( self, customer_id: str, ad_group_id: str, status: AdGroupStatus ) -> Dict[str, Any]: """ Update ad group status. Args: customer_id: Customer ID ad_group_id: Ad group ID status: New status Returns: Operation result """ return self.update_ad_group( customer_id, ad_group_id, {"status": status.value} ) def update_ad_group_cpc_bid( self, customer_id: str, ad_group_id: str, cpc_bid_micros: int ) -> Dict[str, Any]: """ Update ad group CPC bid. Args: customer_id: Customer ID ad_group_id: Ad group ID cpc_bid_micros: New CPC bid in micros Returns: Operation result with bid amount """ result = self.update_ad_group( customer_id, ad_group_id, {"cpc_bid_micros": cpc_bid_micros} ) result["new_cpc_bid"] = cpc_bid_micros / 1_000_000 return result # ======================================================================== # Ad Group Information # ======================================================================== def get_ad_group_details( self, customer_id: str, ad_group_id: str ) -> Optional[Dict[str, Any]]: """ Get detailed information about an ad group. Args: customer_id: Customer ID ad_group_id: Ad group ID Returns: Ad group details or None if not found """ query = f""" SELECT ad_group.id, ad_group.name, ad_group.status, ad_group.type, ad_group.cpc_bid_micros, ad_group.cpm_bid_micros, ad_group.cpv_bid_micros, ad_group.target_cpa_micros, campaign.id, campaign.name, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.ctr, metrics.average_cpc FROM ad_group WHERE ad_group.id = {ad_group_id} """ ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) for row in response: return { "id": str(row.ad_group.id), "name": row.ad_group.name, "status": row.ad_group.status.name, "type": row.ad_group.type_.name if row.ad_group.type_ else "UNSPECIFIED", "campaign": { "id": str(row.campaign.id), "name": row.campaign.name }, "bids": { "cpc_bid": row.ad_group.cpc_bid_micros / 1_000_000 if row.ad_group.cpc_bid_micros else None, "cpm_bid": row.ad_group.cpm_bid_micros / 1_000_000 if row.ad_group.cpm_bid_micros else None, "cpv_bid": row.ad_group.cpv_bid_micros / 1_000_000 if row.ad_group.cpv_bid_micros else None, "target_cpa": row.ad_group.target_cpa_micros / 1_000_000 if row.ad_group.target_cpa_micros else None }, "metrics": { "impressions": row.metrics.impressions, "clicks": row.metrics.clicks, "cost": row.metrics.cost_micros / 1_000_000, "conversions": row.metrics.conversions, "ctr": row.metrics.ctr, "average_cpc": row.metrics.average_cpc / 1_000_000 if row.metrics.average_cpc else 0 } } return None def list_ad_groups( self, customer_id: str, campaign_id: Optional[str] = None, status: Optional[AdGroupStatus] = None ) -> List[Dict[str, Any]]: """ List ad groups with optional filters. Args: customer_id: Customer ID campaign_id: Optional campaign ID to filter by status: Optional status to filter by Returns: List of ad groups """ query = """ SELECT ad_group.id, ad_group.name, ad_group.status, ad_group.type, ad_group.cpc_bid_micros, campaign.id, campaign.name, metrics.impressions, metrics.clicks, metrics.cost_micros FROM ad_group WHERE ad_group.status != 'REMOVED' """ # Add filters if campaign_id: query += f" AND campaign.id = {campaign_id}" if status: query += f" AND ad_group.status = {status.value}" query += " ORDER BY ad_group.name" ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) ad_groups = [] for row in response: ad_groups.append({ "id": str(row.ad_group.id), "name": row.ad_group.name, "status": row.ad_group.status.name, "type": row.ad_group.type_.name if row.ad_group.type_ else "UNSPECIFIED", "campaign_id": str(row.campaign.id), "campaign_name": row.campaign.name, "cpc_bid": row.ad_group.cpc_bid_micros / 1_000_000 if row.ad_group.cpc_bid_micros else None, "metrics": { "impressions": row.metrics.impressions, "clicks": row.metrics.clicks, "cost": row.metrics.cost_micros / 1_000_000 } }) logger.info(f"Retrieved {len(ad_groups)} ad groups") return ad_groups # ======================================================================== # Ad Group Performance # ======================================================================== def get_ad_group_performance( self, customer_id: str, ad_group_id: str, date_range: str = "LAST_30_DAYS" ) -> Dict[str, Any]: """ Get performance metrics for an ad group. Args: customer_id: Customer ID ad_group_id: Ad group ID date_range: Date range for metrics (e.g., LAST_30_DAYS, LAST_7_DAYS) Returns: Performance metrics """ query = f""" SELECT ad_group.id, ad_group.name, campaign.name, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions, metrics.conversions_value, metrics.cost_per_conversion, metrics.conversion_rate, metrics.all_conversions, metrics.view_through_conversions FROM ad_group WHERE ad_group.id = {ad_group_id} AND segments.date DURING {date_range} """ ga_service = self.client.get_service("GoogleAdsService") response = ga_service.search(customer_id=customer_id, query=query) # Aggregate metrics total_metrics = { "impressions": 0, "clicks": 0, "cost": 0, "conversions": 0, "conversions_value": 0, "all_conversions": 0, "view_through_conversions": 0 } ad_group_info = None for row in response: if not ad_group_info: ad_group_info = { "id": str(row.ad_group.id), "name": row.ad_group.name, "campaign_name": row.campaign.name } total_metrics["impressions"] += row.metrics.impressions total_metrics["clicks"] += row.metrics.clicks total_metrics["cost"] += row.metrics.cost_micros / 1_000_000 total_metrics["conversions"] += row.metrics.conversions total_metrics["conversions_value"] += row.metrics.conversions_value total_metrics["all_conversions"] += row.metrics.all_conversions total_metrics["view_through_conversions"] += row.metrics.view_through_conversions # Calculate derived metrics if total_metrics["impressions"] > 0: total_metrics["ctr"] = (total_metrics["clicks"] / total_metrics["impressions"]) * 100 else: total_metrics["ctr"] = 0 if total_metrics["clicks"] > 0: total_metrics["average_cpc"] = total_metrics["cost"] / total_metrics["clicks"] else: total_metrics["average_cpc"] = 0 if total_metrics["conversions"] > 0: total_metrics["cost_per_conversion"] = total_metrics["cost"] / total_metrics["conversions"] total_metrics["conversion_rate"] = (total_metrics["conversions"] / total_metrics["clicks"]) * 100 else: total_metrics["cost_per_conversion"] = 0 total_metrics["conversion_rate"] = 0 if ad_group_info: ad_group_info["metrics"] = total_metrics ad_group_info["date_range"] = date_range return ad_group_info else: return { "id": ad_group_id, "error": "No data found for the specified date range" } # ======================================================================== # Bulk Operations # ======================================================================== def bulk_update_ad_group_status( self, customer_id: str, ad_group_ids: List[str], status: AdGroupStatus ) -> Dict[str, Any]: """ Update status for multiple ad groups at once. Args: customer_id: Customer ID ad_group_ids: List of ad group IDs status: New status for all ad groups Returns: Bulk operation result """ ad_group_service = self.client.get_service("AdGroupService") operations = [] for ad_group_id in ad_group_ids: ad_group_operation = self.client.get_type("AdGroupOperation") ad_group = ad_group_operation.update ad_group.resource_name = ad_group_service.ad_group_path(customer_id, ad_group_id) ad_group.status = self.client.enums.AdGroupStatusEnum[status.value] self.client.copy_from( ad_group_operation.update_mask, self.client.get_type("FieldMask", version="v17")(paths=["status"]) ) operations.append(ad_group_operation) # Execute bulk update response = ad_group_service.mutate_ad_groups( customer_id=customer_id, operations=operations ) logger.info(f"Bulk updated {len(ad_group_ids)} ad groups to {status.value}") return { "ad_groups_updated": len(ad_group_ids), "new_status": status.value, "message": f"Successfully updated {len(ad_group_ids)} ad groups" } def create_ad_group_manager(client: GoogleAdsClient) -> AdGroupManager: """ Create an ad group manager instance. Args: client: Google Ads client Returns: AdGroupManager instance """ return AdGroupManager(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