Skip to main content
Glama
johnoconnor0

Google Ads MCP Server

by johnoconnor0
conversion_manager.py16.7 kB
""" Conversion Tracking Manager Handles conversion actions, offline conversion imports, and attribution. Conversion Types: - Website conversions (page views, form submissions, purchases) - Phone call conversions (click-to-call, call extensions) - App conversions (downloads, in-app actions) - Import conversions (offline, CRM, store sales) Attribution Models: - Last Click - First Click - Linear - Time Decay - Position Based - Data-Driven (recommended) """ from typing import Dict, Any, List, Optional from dataclasses import dataclass from enum import Enum from google.ads.googleads.client import GoogleAdsClient import hashlib class ConversionActionCategory(str, Enum): """Conversion action categories.""" DEFAULT = "DEFAULT" PAGE_VIEW = "PAGE_VIEW" PURCHASE = "PURCHASE" SIGNUP = "SIGNUP" LEAD = "LEAD" DOWNLOAD = "DOWNLOAD" ADD_TO_CART = "ADD_TO_CART" BEGIN_CHECKOUT = "BEGIN_CHECKOUT" SUBSCRIBE_PAID = "SUBSCRIBE_PAID" PHONE_CALL_LEAD = "PHONE_CALL_LEAD" IMPORTED_LEAD = "IMPORTED_LEAD" SUBMIT_LEAD_FORM = "SUBMIT_LEAD_FORM" BOOK_APPOINTMENT = "BOOK_APPOINTMENT" REQUEST_QUOTE = "REQUEST_QUOTE" CONTACT = "CONTACT" STORE_SALE = "STORE_SALE" STORE_VISIT = "STORE_VISIT" class ConversionOrigin(str, Enum): """Where conversions originate from.""" WEBSITE = "WEBSITE" GOOGLE_HOSTED = "GOOGLE_HOSTED" APP = "APP" CALL_FROM_ADS = "CALL_FROM_ADS" STORE = "STORE" YOUTUBE_HOSTED = "YOUTUBE_HOSTED" IMPORT = "IMPORT" class AttributionModel(str, Enum): """Attribution model types.""" LAST_CLICK = "LAST_CLICK" FIRST_CLICK = "FIRST_CLICK" LINEAR = "LINEAR" TIME_DECAY = "TIME_DECAY" POSITION_BASED = "POSITION_BASED" DATA_DRIVEN = "DATA_DRIVEN" @dataclass class ConversionActionConfig: """Configuration for creating a conversion action.""" name: str category: ConversionActionCategory origin: ConversionOrigin value: Optional[float] = None always_use_default_value: bool = False count_type: str = "ONE" # ONE or MANY click_through_lookback_window_days: int = 30 view_through_lookback_window_days: int = 1 phone_call_duration_seconds: Optional[int] = None # For call conversions class ConversionManager: """Manager for conversion tracking and attribution.""" def __init__(self, client: GoogleAdsClient): """Initialize the conversion manager. Args: client: Authenticated GoogleAdsClient instance """ self.client = client def create_conversion_action( self, customer_id: str, config: ConversionActionConfig ) -> Dict[str, Any]: """Create a conversion action for tracking. Args: customer_id: Customer ID (without hyphens) config: Conversion action configuration Returns: Dictionary with conversion action details and tracking tag """ conversion_action_service = self.client.get_service("ConversionActionService") conversion_action_operation = self.client.get_type("ConversionActionOperation") conversion_action = conversion_action_operation.create conversion_action.name = config.name conversion_action.category = self.client.enums.ConversionActionCategoryEnum[config.category.value] conversion_action.origin = self.client.enums.ConversionOriginEnum[config.origin.value] conversion_action.status = self.client.enums.ConversionActionStatusEnum.ENABLED # Set counting type if config.count_type == "ONE": conversion_action.counting_type = self.client.enums.ConversionActionCountingTypeEnum.ONE_PER_CLICK else: conversion_action.counting_type = self.client.enums.ConversionActionCountingTypeEnum.MANY_PER_CLICK # Set value settings conversion_action.value_settings.default_value = config.value if config.value else 0 conversion_action.value_settings.always_use_default_value = config.always_use_default_value # Set attribution settings conversion_action.click_through_lookback_window_days = config.click_through_lookback_window_days conversion_action.view_through_lookback_window_days = config.view_through_lookback_window_days # Set phone call duration for call conversions if config.phone_call_duration_seconds and config.category == ConversionActionCategory.PHONE_CALL_LEAD: conversion_action.phone_call_duration_seconds = config.phone_call_duration_seconds response = conversion_action_service.mutate_conversion_actions( customer_id=customer_id, operations=[conversion_action_operation] ) resource_name = response.results[0].resource_name conversion_action_id = resource_name.split("/")[-1] return { 'resource_name': resource_name, 'conversion_action_id': conversion_action_id, 'name': config.name, 'category': config.category.value, 'origin': config.origin.value } def list_conversion_actions( self, customer_id: str, include_removed: bool = False ) -> List[Dict[str, Any]]: """List all conversion actions in the account. Args: customer_id: Customer ID (without hyphens) include_removed: Whether to include removed conversions Returns: List of conversion actions """ ga_service = self.client.get_service("GoogleAdsService") query = """ SELECT conversion_action.id, conversion_action.name, conversion_action.category, conversion_action.origin, conversion_action.status, conversion_action.counting_type, conversion_action.value_settings.default_value, conversion_action.value_settings.always_use_default_value, conversion_action.click_through_lookback_window_days, conversion_action.view_through_lookback_window_days, conversion_action.attribution_model_settings.attribution_model FROM conversion_action """ if not include_removed: query += " WHERE conversion_action.status != 'REMOVED'" response = ga_service.search(customer_id=customer_id, query=query) conversions = [] for row in response: ca = row.conversion_action conversions.append({ 'id': str(ca.id), 'name': ca.name, 'category': ca.category.name, 'origin': ca.origin.name, 'status': ca.status.name, 'counting_type': ca.counting_type.name, 'default_value': ca.value_settings.default_value, 'always_use_default_value': ca.value_settings.always_use_default_value, 'click_through_window': ca.click_through_lookback_window_days, 'view_through_window': ca.view_through_lookback_window_days, 'attribution_model': ca.attribution_model_settings.attribution_model.name }) return conversions def upload_offline_conversions( self, customer_id: str, conversion_action_id: str, conversions: List[Dict[str, Any]] ) -> Dict[str, Any]: """Upload offline conversion data. Args: customer_id: Customer ID (without hyphens) conversion_action_id: Conversion action ID conversions: List of conversion dictionaries with: - gclid: Google Click ID - conversion_date_time: Conversion time (YYYY-MM-DD HH:MM:SS+TZ) - conversion_value: Optional conversion value - currency_code: Optional currency (e.g., "USD") Returns: Upload result """ conversion_upload_service = self.client.get_service("ConversionUploadService") conversion_action_service = self.client.get_service("ConversionActionService") click_conversions = [] for conv in conversions: click_conversion = self.client.get_type("ClickConversion") click_conversion.gclid = conv['gclid'] click_conversion.conversion_action = conversion_action_service.conversion_action_path( customer_id, conversion_action_id ) click_conversion.conversion_date_time = conv['conversion_date_time'] if 'conversion_value' in conv: click_conversion.conversion_value = conv['conversion_value'] if 'currency_code' in conv: click_conversion.currency_code = conv['currency_code'] click_conversions.append(click_conversion) response = conversion_upload_service.upload_click_conversions( customer_id=customer_id, conversions=click_conversions, partial_failure=True ) return { 'uploaded': len(conversions), 'results': len(response.results), 'partial_failure_error': str(response.partial_failure_error) if response.partial_failure_error else None } def upload_call_conversions( self, customer_id: str, conversion_action_id: str, call_conversions: List[Dict[str, Any]] ) -> Dict[str, Any]: """Upload call conversion data. Args: customer_id: Customer ID (without hyphens) conversion_action_id: Conversion action ID call_conversions: List with: - caller_id: Phone number that called - call_start_date_time: Call time - conversion_date_time: When call qualified as conversion - conversion_value: Optional value Returns: Upload result """ conversion_upload_service = self.client.get_service("ConversionUploadService") conversion_action_service = self.client.get_service("ConversionActionService") call_convs = [] for conv in call_conversions: call_conversion = self.client.get_type("CallConversion") call_conversion.caller_id = conv['caller_id'] call_conversion.call_start_date_time = conv['call_start_date_time'] call_conversion.conversion_date_time = conv['conversion_date_time'] call_conversion.conversion_action = conversion_action_service.conversion_action_path( customer_id, conversion_action_id ) if 'conversion_value' in conv: call_conversion.conversion_value = conv['conversion_value'] if 'currency_code' in conv: call_conversion.currency_code = conv['currency_code'] call_convs.append(call_conversion) response = conversion_upload_service.upload_call_conversions( customer_id=customer_id, conversions=call_convs, partial_failure=True ) return { 'uploaded': len(call_conversions), 'results': len(response.results), 'partial_failure_error': str(response.partial_failure_error) if response.partial_failure_error else None } def get_conversion_performance( self, customer_id: str, conversion_action_id: Optional[str] = None, date_range: str = "LAST_30_DAYS" ) -> List[Dict[str, Any]]: """Get conversion performance metrics. Args: customer_id: Customer ID (without hyphens) conversion_action_id: Optional specific conversion action date_range: Date range Returns: List of conversion performance data """ ga_service = self.client.get_service("GoogleAdsService") query = f""" SELECT conversion_action.id, conversion_action.name, conversion_action.category, metrics.conversions, metrics.conversions_value, metrics.cost_per_conversion, metrics.conversions_from_interactions_rate, metrics.value_per_conversion, metrics.all_conversions, metrics.all_conversions_value FROM conversion_action WHERE segments.date DURING {date_range} """ if conversion_action_id: query += f" AND conversion_action.id = {conversion_action_id}" response = ga_service.search(customer_id=customer_id, query=query) conversions = [] for row in response: conversions.append({ 'conversion_action_id': str(row.conversion_action.id), 'name': row.conversion_action.name, 'category': row.conversion_action.category.name, 'conversions': row.metrics.conversions, 'conversions_value': row.metrics.conversions_value, 'cost_per_conversion': row.metrics.cost_per_conversion, 'conversion_rate': row.metrics.conversions_from_interactions_rate, 'value_per_conversion': row.metrics.value_per_conversion, 'all_conversions': row.metrics.all_conversions, 'all_conversions_value': row.metrics.all_conversions_value }) return conversions def set_attribution_model( self, customer_id: str, conversion_action_id: str, attribution_model: AttributionModel ) -> Dict[str, Any]: """Set attribution model for a conversion action. Args: customer_id: Customer ID (without hyphens) conversion_action_id: Conversion action ID attribution_model: Attribution model to set Returns: Updated conversion action details """ conversion_action_service = self.client.get_service("ConversionActionService") conversion_action_operation = self.client.get_type("ConversionActionOperation") conversion_action = conversion_action_operation.update conversion_action.resource_name = conversion_action_service.conversion_action_path( customer_id, conversion_action_id ) conversion_action.attribution_model_settings.attribution_model = ( self.client.enums.AttributionModelEnum[attribution_model.value] ) self.client.copy_from( conversion_action_operation.update_mask, self.client.get_type("FieldMask", version="v17")( paths=["attribution_model_settings.attribution_model"] ) ) response = conversion_action_service.mutate_conversion_actions( customer_id=customer_id, operations=[conversion_action_operation] ) return { 'resource_name': response.results[0].resource_name, 'conversion_action_id': conversion_action_id, 'attribution_model': attribution_model.value } def get_conversion_tracking_tag( self, customer_id: str, conversion_action_id: str ) -> Dict[str, Any]: """Get the tracking tag/snippet for a conversion action. Args: customer_id: Customer ID (without hyphens) conversion_action_id: Conversion action ID Returns: Tracking tag details """ ga_service = self.client.get_service("GoogleAdsService") query = f""" SELECT conversion_action.id, conversion_action.name, conversion_action.tag_snippets FROM conversion_action WHERE conversion_action.id = {conversion_action_id} """ response = ga_service.search(customer_id=customer_id, query=query) results = list(response) if not results: return {'error': f'Conversion action {conversion_action_id} not found'} row = results[0] ca = row.conversion_action # Extract tag snippets tag_snippets = [] for snippet in ca.tag_snippets: tag_snippets.append({ 'type': snippet.type_.name, 'page_format': snippet.page_format.name, 'global_site_tag': snippet.global_site_tag, 'event_snippet': snippet.event_snippet }) return { 'conversion_action_id': str(ca.id), 'name': ca.name, 'tag_snippets': tag_snippets }

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