Skip to content

Commit d5d3d8b

Browse files
committed
feat(insights): action_breakdowns override + auto-empty for media_type
Meta rejects (action_type, media_type) with (#100) invalid breakdown columns. Auto-set action_breakdowns=[] when breakdown=media_type so the request succeeds (empty rows for non-DCO ads, real rows for DCO ads). Add an action_breakdowns parameter so callers can override the heuristic or pass other Meta-supported values (action_target_id, action_destination, etc.). Bumps version 1.0.96 -> 1.0.97.
1 parent 0a1145a commit d5d3d8b

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

‎meta_ads_mcp/core/insights.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636
"platform_position",
3737
})
3838

39+
# Breakdowns that collide with the default action_breakdowns=[action_type] but
40+
# are real Meta fields (so we explicitly clear action_breakdowns instead of
41+
# dropping the action-typed response fields). For non-DCO ads this returns
42+
# empty rows; for DCO ads it returns the breakdown dimension populated.
43+
_BREAKDOWNS_REQUIRING_EMPTY_ACTION_BREAKDOWNS = frozenset({
44+
"media_type",
45+
})
46+
3947
_ACTION_TYPED_FIELDS = frozenset({
4048
"actions",
4149
"action_values",
@@ -66,6 +74,7 @@ async def get_insights(object_id: str = "", access_token: Optional[str] = None,
6674
time_range: Union[str, Dict[str, str]] = "maximum", breakdown: str = "",
6775
level: str = "ad", limit: int = 25, after: str = "",
6876
action_attribution_windows: Optional[List[str]] = None,
77+
action_breakdowns: Optional[List[str]] = None,
6978
compact: bool = False,
7079
account_id: str = "", campaign_id: str = "",
7180
adset_id: str = "", ad_id: str = "") -> str:
@@ -98,6 +107,10 @@ async def get_insights(object_id: str = "", access_token: Optional[str] = None,
98107
gen_ai_asset_type
99108
NOTE: Asset breakdowns (image_asset, video_asset, etc.) only return data for ads
100109
running with Dynamic Creative; for non-DCO ads, expect empty rows.
110+
NOTE: media_type collides with the default action_breakdowns=[action_type], so
111+
this tool auto-overrides action_breakdowns to [] when you pass media_type.
112+
Action-typed metrics (actions, action_values, conversions) are still returned
113+
but are no longer sliced by action_type alongside media_type.
101114
media_asset_url, media_creator, media_destination_url, media_format,
102115
media_origin_url, and media_text_content are NOT supported by Meta's Insights API
103116
(Meta returns "(#100) Tried accessing nonexisting field"). Use the asset breakdowns
@@ -122,6 +135,10 @@ async def get_insights(object_id: str = "", access_token: Optional[str] = None,
122135
after: Pagination cursor to get the next set of results. Use the 'after' cursor from previous response's paging.next field.
123136
action_attribution_windows: Optional list of attribution windows (e.g., ["1d_click", "7d_click", "1d_view"]).
124137
When specified, actions include additional fields for each window. The 'value' field always shows 7d_click.
138+
action_breakdowns: Optional list of action_breakdowns to apply to action-typed metrics. Pass [] to disable
139+
the default action_type slicing (required when combining action data with breakdowns that collide
140+
with action_type, e.g. media_type — auto-applied for media_type when not set).
141+
Meta supports values like action_type, action_target_id, action_destination, etc.
125142
compact: When True, strips redundant action-type duplicates from the response
126143
(omni_*, onsite_web_*, offsite_conversion.fb_pixel_*, etc.) to reduce
127144
payload size by ~60%. The canonical action types (purchase, add_to_cart,
@@ -162,6 +179,12 @@ async def get_insights(object_id: str = "", access_token: Optional[str] = None,
162179
breakdown_set.add("publisher_platform")
163180
if breakdown_set & _BREAKDOWNS_INCOMPATIBLE_WITH_ACTION_TYPE:
164181
fields = [f for f in fields if f not in _ACTION_TYPED_FIELDS]
182+
# media_type collides with action_breakdowns=[action_type] but is a real
183+
# field — override action_breakdowns to empty so the request succeeds with
184+
# the action-typed metrics intact.
185+
override_action_breakdowns_empty = bool(
186+
breakdown_set & _BREAKDOWNS_REQUIRING_EMPTY_ACTION_BREAKDOWNS
187+
)
165188

166189
params = {
167190
"fields": ",".join(fields),
@@ -182,6 +205,13 @@ async def get_insights(object_id: str = "", access_token: Optional[str] = None,
182205

183206
if breakdown_values:
184207
params["breakdowns"] = ",".join(breakdown_values)
208+
# Caller-supplied action_breakdowns wins; otherwise auto-empty for media_type.
209+
if action_breakdowns is not None:
210+
params["action_breakdowns"] = (
211+
"[" + ",".join(action_breakdowns) + "]" if action_breakdowns else "[]"
212+
)
213+
elif override_action_breakdowns_empty:
214+
params["action_breakdowns"] = "[]"
185215

186216
if after:
187217
params["after"] = after

‎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.96"
7+
version = "1.0.97"
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"

0 commit comments

Comments
 (0)