Skip to content

Commit e83774e

Browse files
committed
fix(create_ad_creative): CTWA single video + IG actor -> object_story_spec; release 1.0.105
Hotfix branched off the 1.0.103 tag and cherry-picked from PR #119, deliberately excluding the mcp dependency bump (PR #121) that caused the 1.0.104 production transport regression. Routes single video + Instagram actor through object_story_spec.video_data instead of asset_feed_spec (CTWA OUTCOME_SALES/OUTCOME_ENGAGEMENT accept it; no subcode 1885392), drops link from the WHATSAPP_MESSAGE CTA value (no subcode 1815630), normalizes optimization_type=REGULAR. Dependencies are identical to 1.0.103 (no mcp upgrade). Full suite: 450 passed.
1 parent e7ff4e1 commit e83774e

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.105"
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
@@ -2066,18 +2066,51 @@ async def create_ad_creative(
20662066

20672067
# Determine whether to use asset_feed_spec path:
20682068
# - plural parameters (headlines/descriptions/messages/image_hashes), OR
2069-
# - optimization_type is set (FLEX creatives always use asset_feed_spec), OR
2070-
# - asset_customization_rules requires asset_feed_spec, OR
2071-
# - video_id + description: Meta's video_data rejects "description" directly,
2072-
# so route through asset_feed_spec which supports descriptions for video ads
2073-
# - video_id + instagram_actor_id: Meta requires ad_formats=["SINGLE_VIDEO"] in
2074-
# asset_feed_spec alongside instagram_user_id in object_story_spec — error 1443048
2075-
# ("object_story_spec ill formed") occurs when asset_feed_spec is absent. Routing
2076-
# through asset_feed_spec ensures ad_formats is automatically included.
2069+
# - optimization_type is set to one of the dynamic-creative modes
2070+
# (DEGREES_OF_FREEDOM, PLACEMENT, ASSET_CUSTOMIZATION, LANGUAGE), OR
2071+
# - asset_customization_rules requires asset_feed_spec.
2072+
#
2073+
# NOTE: `optimization_type="REGULAR"` is a Meta-documented asset_feed_spec
2074+
# value meaning "no extra optimization on top of the spec", but callers
2075+
# also use it as a signal that they do NOT want a dynamic creative. With
2076+
# only single-variant inputs (one message, one headline, one video) the
2077+
# caller's intent is the plain `object_story_spec.video_data` shape, so
2078+
# we treat REGULAR as a no-op for routing and let the simple path render
2079+
# the creative. The value is dropped on the wire (we never send
2080+
# asset_feed_spec.optimization_type=REGULAR for a single-video creative).
2081+
#
2082+
# We do NOT route a single video + instagram_actor_id through asset_feed_spec.
2083+
# Per Meta's docs the canonical shape for a video creative with an Instagram
2084+
# identity is `object_story_spec.video_data` + `instagram_user_id` sibling, no
2085+
# asset_feed_spec. Routing every single-video creative through asset_feed_spec
2086+
# silently produces a "dynamic creative" that CTWA campaigns (OUTCOME_SALES /
2087+
# OUTCOME_ENGAGEMENT with destination=WHATSAPP) reject with
2088+
# `error_subcode 1885392` ("O objetivo da campanha nao e aceito pelo criativo
2089+
# dinamico"). The single-video path keeps the creative regular so it serves in
2090+
# CTWA, lead-gen, traffic, and engagement campaigns alike.
2091+
#
2092+
# `description` is also no longer a routing trigger. Meta's `video_data` schema
2093+
# does not carry a `description` field for a single-video creative, so when a
2094+
# caller passes `description` alongside a single video we drop it and surface a
2095+
# warning in the response (the caller's intent — video + IG + WhatsApp CTA — is
2096+
# what matters, not an unrenderable field). To attach descriptions to a video
2097+
# creative, pass `descriptions=[...]` (plural) or `optimization_type` to opt
2098+
# explicitly into the dynamic-creative path.
2099+
#
2100+
# Normalize REGULAR -> None so the rest of the function does not echo it
2101+
# into asset_feed_spec.optimization_type when asset_feed_spec is built for
2102+
# another reason (e.g. plural params).
2103+
if optimization_type == "REGULAR":
2104+
optimization_type = None
20772105
use_asset_feed = bool(
20782106
headlines or descriptions or messages or image_hashes or videos or images
2079-
or optimization_type or asset_customization_rules or (video_id and description)
2080-
or (video_id and instagram_actor_id)
2107+
or optimization_type or asset_customization_rules
2108+
)
2109+
2110+
# Track whether `description` was provided but cannot be rendered in the
2111+
# simple video_data path so we can warn the caller after the API call.
2112+
single_video_description_dropped = bool(
2113+
video_id and description and not use_asset_feed
20812114
)
20822115

20832116
# Track if this is a video creative
@@ -2414,16 +2447,27 @@ async def create_ad_creative(
24142447
# Build call_to_action with the destination URL.
24152448
# For video creatives, link_url MUST go in call_to_action.value.link
24162449
# (not as a top-level field in video_data).
2417-
cta_value = {}
2418-
if link_url:
2419-
cta_value["link"] = link_url
2420-
if lead_gen_form_id:
2421-
cta_value["lead_gen_form_id"] = lead_gen_form_id
2422-
if phone_number:
2423-
# CALL_NOW: Meta v24 supports only
2424-
# call_to_action.value.link = "tel:+<E.164 number>".
2425-
cta_value["link"] = f"tel:{phone_number}"
24262450
cta_type = call_to_action_type or ("LEARN_MORE" if link_url else None)
2451+
cta_value = {}
2452+
if cta_type == "WHATSAPP_MESSAGE":
2453+
# Click-to-WhatsApp: Meta derives the destination from the
2454+
# Page's linked WhatsApp number, so the CTA carries no value.
2455+
# Passing ANY extra parameter here (callers commonly send a
2456+
# wa.me URL via link_url) makes Meta v24 reject the creative
2457+
# with code 105 / error_subcode 1815630 ("Too many parameters
2458+
# in Call To Action — Please remove parameter 'link' from the
2459+
# value of WHATSAPP_MESSAGE call to action type"). The correct
2460+
# shape is just {"type": "WHATSAPP_MESSAGE"} with no value.
2461+
pass
2462+
else:
2463+
if link_url:
2464+
cta_value["link"] = link_url
2465+
if lead_gen_form_id:
2466+
cta_value["lead_gen_form_id"] = lead_gen_form_id
2467+
if phone_number:
2468+
# CALL_NOW: Meta v24 supports only
2469+
# call_to_action.value.link = "tel:+<E.164 number>".
2470+
cta_value["link"] = f"tel:{phone_number}"
24272471
if cta_type:
24282472
cta_data = {"type": cta_type}
24292473
if cta_value:
@@ -2626,6 +2670,16 @@ async def create_ad_creative(
26262670
"is_dynamic_creative=true, or use image_crops on a single "
26272671
"image_hash for per-placement cropping."
26282672
)
2673+
if single_video_description_dropped:
2674+
warnings_.append(
2675+
"`description` was dropped because Meta's video_data schema does "
2676+
"not carry a description field for a single-video creative. The "
2677+
"creative was created with message + headline only. To attach a "
2678+
"description to a video creative, pass `descriptions=[...]` "
2679+
"(plural) or `optimization_type` — both route through "
2680+
"asset_feed_spec, which is incompatible with CTWA campaigns "
2681+
"(OUTCOME_SALES/OUTCOME_ENGAGEMENT with destination=WHATSAPP)."
2682+
)
26292683
if warnings_:
26302684
result["warning"] = warnings_[0] if len(warnings_) == 1 else warnings_
26312685
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.105"
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.105",
66
"remotes": [
77
{
88
"type": "streamable-http",

0 commit comments

Comments
 (0)