Skip to content

Commit de26da1

Browse files
nictukumdrokon-dev
authored andcommitted
fix(create_ad_creative): single video + IG actor -> object_story_spec for CTWA (pipeboard-co#119)
Route single-video + Instagram-actor creatives through object_story_spec.video_data instead of asset_feed_spec so CTWA campaigns (OUTCOME_SALES/OUTCOME_ENGAGEMENT, destination=WHATSAPP) accept them. Drop link from the WHATSAPP_MESSAGE CTA value (Meta error_subcode 1815630). Normalize optimization_type=REGULAR to no-op. Sanitized test fixtures. Bump to 1.0.104. Full suite: 450 passed.
1 parent a16b59e commit de26da1

5 files changed

Lines changed: 245 additions & 87 deletions

File tree

‎meta_ads_mcp/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from meta_ads_mcp.core.server import main
88

9-
__version__ = "1.0.87"
9+
__version__ = "1.0.104"
1010

1111
__all__ = [
1212
'get_ad_accounts',

‎meta_ads_mcp/core/ads.py‎

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,18 +2269,51 @@ async def create_ad_creative(
22692269

22702270
# Determine whether to use asset_feed_spec path:
22712271
# - plural parameters (headlines/descriptions/messages/image_hashes), OR
2272-
# - optimization_type is set (FLEX creatives always use asset_feed_spec), OR
2273-
# - asset_customization_rules requires asset_feed_spec, OR
2274-
# - video_id + description: Meta's video_data rejects "description" directly,
2275-
# so route through asset_feed_spec which supports descriptions for video ads
2276-
# - video_id + instagram_actor_id: Meta requires ad_formats=["SINGLE_VIDEO"] in
2277-
# asset_feed_spec alongside instagram_user_id in object_story_spec — error 1443048
2278-
# ("object_story_spec ill formed") occurs when asset_feed_spec is absent. Routing
2279-
# through asset_feed_spec ensures ad_formats is automatically included.
2272+
# - optimization_type is set to one of the dynamic-creative modes
2273+
# (DEGREES_OF_FREEDOM, PLACEMENT, ASSET_CUSTOMIZATION, LANGUAGE), OR
2274+
# - asset_customization_rules requires asset_feed_spec.
2275+
#
2276+
# NOTE: `optimization_type="REGULAR"` is a Meta-documented asset_feed_spec
2277+
# value meaning "no extra optimization on top of the spec", but callers
2278+
# also use it as a signal that they do NOT want a dynamic creative. With
2279+
# only single-variant inputs (one message, one headline, one video) the
2280+
# caller's intent is the plain `object_story_spec.video_data` shape, so
2281+
# we treat REGULAR as a no-op for routing and let the simple path render
2282+
# the creative. The value is dropped on the wire (we never send
2283+
# asset_feed_spec.optimization_type=REGULAR for a single-video creative).
2284+
#
2285+
# We do NOT route a single video + instagram_actor_id through asset_feed_spec.
2286+
# Per Meta's docs the canonical shape for a video creative with an Instagram
2287+
# identity is `object_story_spec.video_data` + `instagram_user_id` sibling, no
2288+
# asset_feed_spec. Routing every single-video creative through asset_feed_spec
2289+
# silently produces a "dynamic creative" that CTWA campaigns (OUTCOME_SALES /
2290+
# OUTCOME_ENGAGEMENT with destination=WHATSAPP) reject with
2291+
# `error_subcode 1885392` ("O objetivo da campanha nao e aceito pelo criativo
2292+
# dinamico"). The single-video path keeps the creative regular so it serves in
2293+
# CTWA, lead-gen, traffic, and engagement campaigns alike.
2294+
#
2295+
# `description` is also no longer a routing trigger. Meta's `video_data` schema
2296+
# does not carry a `description` field for a single-video creative, so when a
2297+
# caller passes `description` alongside a single video we drop it and surface a
2298+
# warning in the response (the caller's intent — video + IG + WhatsApp CTA — is
2299+
# what matters, not an unrenderable field). To attach descriptions to a video
2300+
# creative, pass `descriptions=[...]` (plural) or `optimization_type` to opt
2301+
# explicitly into the dynamic-creative path.
2302+
#
2303+
# Normalize REGULAR -> None so the rest of the function does not echo it
2304+
# into asset_feed_spec.optimization_type when asset_feed_spec is built for
2305+
# another reason (e.g. plural params).
2306+
if optimization_type == "REGULAR":
2307+
optimization_type = None
22802308
use_asset_feed = bool(
22812309
headlines or descriptions or messages or image_hashes or videos or images
2282-
or optimization_type or asset_customization_rules or (video_id and description)
2283-
or (video_id and instagram_actor_id)
2310+
or optimization_type or asset_customization_rules
2311+
)
2312+
2313+
# Track whether `description` was provided but cannot be rendered in the
2314+
# simple video_data path so we can warn the caller after the API call.
2315+
single_video_description_dropped = bool(
2316+
video_id and description and not use_asset_feed
22842317
)
22852318

22862319
# Track if this is a video creative
@@ -2617,16 +2650,27 @@ async def create_ad_creative(
26172650
# Build call_to_action with the destination URL.
26182651
# For video creatives, link_url MUST go in call_to_action.value.link
26192652
# (not as a top-level field in video_data).
2620-
cta_value = {}
2621-
if link_url:
2622-
cta_value["link"] = link_url
2623-
if lead_gen_form_id:
2624-
cta_value["lead_gen_form_id"] = lead_gen_form_id
2625-
if phone_number:
2626-
# CALL_NOW: Meta v24 supports only
2627-
# call_to_action.value.link = "tel:+<E.164 number>".
2628-
cta_value["link"] = f"tel:{phone_number}"
26292653
cta_type = call_to_action_type or ("LEARN_MORE" if link_url else None)
2654+
cta_value = {}
2655+
if cta_type == "WHATSAPP_MESSAGE":
2656+
# Click-to-WhatsApp: Meta derives the destination from the
2657+
# Page's linked WhatsApp number, so the CTA carries no value.
2658+
# Passing ANY extra parameter here (callers commonly send a
2659+
# wa.me URL via link_url) makes Meta v24 reject the creative
2660+
# with code 105 / error_subcode 1815630 ("Too many parameters
2661+
# in Call To Action — Please remove parameter 'link' from the
2662+
# value of WHATSAPP_MESSAGE call to action type"). The correct
2663+
# shape is just {"type": "WHATSAPP_MESSAGE"} with no value.
2664+
pass
2665+
else:
2666+
if link_url:
2667+
cta_value["link"] = link_url
2668+
if lead_gen_form_id:
2669+
cta_value["lead_gen_form_id"] = lead_gen_form_id
2670+
if phone_number:
2671+
# CALL_NOW: Meta v24 supports only
2672+
# call_to_action.value.link = "tel:+<E.164 number>".
2673+
cta_value["link"] = f"tel:{phone_number}"
26302674
if cta_type:
26312675
cta_data = {"type": cta_type}
26322676
if cta_value:
@@ -2829,6 +2873,16 @@ async def create_ad_creative(
28292873
"is_dynamic_creative=true, or use image_crops on a single "
28302874
"image_hash for per-placement cropping."
28312875
)
2876+
if single_video_description_dropped:
2877+
warnings_.append(
2878+
"`description` was dropped because Meta's video_data schema does "
2879+
"not carry a description field for a single-video creative. The "
2880+
"creative was created with message + headline only. To attach a "
2881+
"description to a video creative, pass `descriptions=[...]` "
2882+
"(plural) or `optimization_type` — both route through "
2883+
"asset_feed_spec, which is incompatible with CTWA campaigns "
2884+
"(OUTCOME_SALES/OUTCOME_ENGAGEMENT with destination=WHATSAPP)."
2885+
)
28322886
if warnings_:
28332887
result["warning"] = warnings_[0] if len(warnings_) == 1 else warnings_
28342888
return json.dumps(result, indent=2)

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "meta-ads-mcp"
7-
version = "1.0.103"
7+
version = "1.0.104"
88
description = "Model Context Protocol (MCP) server for Meta Ads - Use Remote MCP at pipeboard.co for easiest setup"
99
readme = "README.md"
1010
requires-python = ">=3.10"

‎server.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
33
"name": "co.pipeboard/meta-ads-mcp",
44
"description": "Facebook / Meta Ads automation with AI: analyze performance, test creatives, optimize spend.",
5-
"version": "1.0.103",
5+
"version": "1.0.104",
66
"remotes": [
77
{
88
"type": "streamable-http",

0 commit comments

Comments
 (0)