get_ad_image
Retrieve and display Meta ad images for visual analysis by providing an ad ID. This tool downloads ad creatives to enable direct examination of visual content within AI models.
Instructions
Get, download, and visualize a Meta ad image in one step. Useful to see the image in the LLM.
Args:
ad_id: Meta Ads ad ID
access_token: Meta API access token (optional - will use cached token if not provided)
Returns:
The ad image ready for direct visual analysis
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ad_id | Yes | ||
| access_token | No |
Implementation Reference
- meta_ads_mcp/core/ads.py:189-389 (handler)The core handler function for the 'get_ad_image' MCP tool. It retrieves ad details, extracts creative image hashes or fallback URLs, downloads the image using Meta Ads API endpoints, processes it with PIL for MCP Image format, and handles multiple fallback paths for robustness (hash-based lookup, direct URLs from creatives, etc.). Decorated with @mcp_server.tool() for automatic MCP registration.@mcp_server.tool() @meta_api_tool async def get_ad_image(ad_id: str, access_token: Optional[str] = None) -> Image: """ Get, download, and visualize a Meta ad image in one step. Useful to see the image in the LLM. Args: ad_id: Meta Ads ad ID access_token: Meta API access token (optional - will use cached token if not provided) Returns: The ad image ready for direct visual analysis """ if not ad_id: return "Error: No ad ID provided" print(f"Attempting to get and analyze creative image for ad {ad_id}") # First, get creative and account IDs ad_endpoint = f"{ad_id}" ad_params = { "fields": "creative{id},account_id" } ad_data = await make_api_request(ad_endpoint, access_token, ad_params) if "error" in ad_data: return f"Error: Could not get ad data - {json.dumps(ad_data)}" # Extract account_id account_id = ad_data.get("account_id", "") if not account_id: return "Error: No account ID found" # Extract creative ID if "creative" not in ad_data: return "Error: No creative found for this ad" creative_data = ad_data.get("creative", {}) creative_id = creative_data.get("id") if not creative_id: return "Error: No creative ID found" # Get creative details to find image hash creative_endpoint = f"{creative_id}" creative_params = { "fields": "id,name,image_hash,asset_feed_spec" } creative_details = await make_api_request(creative_endpoint, access_token, creative_params) # Identify image hashes to use from creative image_hashes = [] # Check for direct image_hash on creative if "image_hash" in creative_details: image_hashes.append(creative_details["image_hash"]) # Check asset_feed_spec for image hashes - common in Advantage+ ads if "asset_feed_spec" in creative_details and "images" in creative_details["asset_feed_spec"]: for image in creative_details["asset_feed_spec"]["images"]: if "hash" in image: image_hashes.append(image["hash"]) if not image_hashes: # If no hashes found, try to extract from the first creative we found in the API # and also check for direct URLs as fallback creative_json = await get_ad_creatives(access_token=access_token, ad_id=ad_id) creative_data = json.loads(creative_json) # Try to extract hash from data array if "data" in creative_data and creative_data["data"]: for creative in creative_data["data"]: # Check object_story_spec for image hash if "object_story_spec" in creative and "link_data" in creative["object_story_spec"]: link_data = creative["object_story_spec"]["link_data"] if "image_hash" in link_data: image_hashes.append(link_data["image_hash"]) # Check direct image_hash on creative elif "image_hash" in creative: image_hashes.append(creative["image_hash"]) # Check asset_feed_spec for image hashes elif "asset_feed_spec" in creative and "images" in creative["asset_feed_spec"]: images = creative["asset_feed_spec"]["images"] if images and len(images) > 0 and "hash" in images[0]: image_hashes.append(images[0]["hash"]) # If still no image hashes found, try direct URL fallback approach if not image_hashes: print("No image hashes found, trying direct URL fallback...") image_url = None if "data" in creative_data and creative_data["data"]: creative = creative_data["data"][0] # Prioritize higher quality image URLs in this order: # 1. image_urls_for_viewing (usually highest quality) # 2. image_url (direct field) # 3. object_story_spec.link_data.picture (usually full size) # 4. thumbnail_url (last resort - often profile thumbnail) if "image_urls_for_viewing" in creative and creative["image_urls_for_viewing"]: image_url = creative["image_urls_for_viewing"][0] print(f"Using image_urls_for_viewing: {image_url}") elif "image_url" in creative and creative["image_url"]: image_url = creative["image_url"] print(f"Using image_url: {image_url}") elif "object_story_spec" in creative and "link_data" in creative["object_story_spec"]: link_data = creative["object_story_spec"]["link_data"] if "picture" in link_data and link_data["picture"]: image_url = link_data["picture"] print(f"Using object_story_spec.link_data.picture: {image_url}") elif "thumbnail_url" in creative and creative["thumbnail_url"]: image_url = creative["thumbnail_url"] print(f"Using thumbnail_url (fallback): {image_url}") if not image_url: return "Error: No image URLs found in creative" # Download the image directly print(f"Downloading image from direct URL: {image_url}") image_bytes = await download_image(image_url) if not image_bytes: return "Error: Failed to download image from direct URL" try: # Convert bytes to PIL Image img = PILImage.open(io.BytesIO(image_bytes)) # Convert to RGB if needed if img.mode != "RGB": img = img.convert("RGB") # Create a byte stream of the image data byte_arr = io.BytesIO() img.save(byte_arr, format="JPEG") img_bytes = byte_arr.getvalue() # Return as an Image object that LLM can directly analyze return Image(data=img_bytes, format="jpeg") except Exception as e: return f"Error processing image from direct URL: {str(e)}" print(f"Found image hashes: {image_hashes}") # Now fetch image data using adimages endpoint with specific format image_endpoint = f"act_{account_id}/adimages" # Format the hashes parameter exactly as in our successful curl test hashes_str = f'["{image_hashes[0]}"]' # Format first hash only, as JSON string array image_params = { "fields": "hash,url,width,height,name,status", "hashes": hashes_str } print(f"Requesting image data with params: {image_params}") image_data = await make_api_request(image_endpoint, access_token, image_params) if "error" in image_data: return f"Error: Failed to get image data - {json.dumps(image_data)}" if "data" not in image_data or not image_data["data"]: return "Error: No image data returned from API" # Get the first image URL first_image = image_data["data"][0] image_url = first_image.get("url") if not image_url: return "Error: No valid image URL found" print(f"Downloading image from URL: {image_url}") # Download the image image_bytes = await download_image(image_url) if not image_bytes: return "Error: Failed to download image" try: # Convert bytes to PIL Image img = PILImage.open(io.BytesIO(image_bytes)) # Convert to RGB if needed if img.mode != "RGB": img = img.convert("RGB") # Create a byte stream of the image data byte_arr = io.BytesIO() img.save(byte_arr, format="JPEG") img_bytes = byte_arr.getvalue() # Return as an Image object that LLM can directly analyze return Image(data=img_bytes, format="jpeg") except Exception as e: return f"Error processing image: {str(e)}"
- meta_ads_mcp/__init__.py:23-50 (registration)Package-level export and re-export of get_ad_image in __all__ and from .core import, making it available at the top-level package.'get_ad_image', 'update_ad', 'get_insights', # 'get_login_link' is conditionally exported via core.__all__ 'login_cli', 'main', 'search_interests', 'get_interest_suggestions', 'estimate_audience_size', 'search_behaviors', 'search_demographics', 'search_geo_locations' ] # Import key functions to make them available at package level from .core import ( get_ad_accounts, get_account_info, get_campaigns, get_campaign_details, create_campaign, get_adsets, get_adset_details, update_adset, get_ads, get_ad_details, get_ad_creatives, get_ad_image,
- meta_ads_mcp/__init__.py:50-50 (registration)Explicit import from .core making get_ad_image available at package level.get_ad_image,