meta-ads-mcp 0.7.3__tar.gz → 0.7.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/PKG-INFO +1 -1
  2. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/__init__.py +1 -1
  3. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/__init__.py +1 -2
  4. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/adsets.py +12 -2
  5. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/campaigns.py +77 -24
  6. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/pyproject.toml +1 -1
  7. meta_ads_mcp-0.7.5/tests/test_budget_update.py +461 -0
  8. meta_ads_mcp-0.7.5/tests/test_budget_update_e2e.py +797 -0
  9. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/.github/workflows/publish.yml +0 -0
  10. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/.github/workflows/test.yml +0 -0
  11. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/.gitignore +0 -0
  12. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/CUSTOM_META_APP.md +0 -0
  13. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/Dockerfile +0 -0
  14. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/LICENSE +0 -0
  15. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/LOCAL_INSTALLATION.md +0 -0
  16. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/META_API_NOTES.md +0 -0
  17. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/README.md +0 -0
  18. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/RELEASE.md +0 -0
  19. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/STREAMABLE_HTTP_SETUP.md +0 -0
  20. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/examples/README.md +0 -0
  21. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/examples/example_http_client.py +0 -0
  22. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/future_improvements.md +0 -0
  23. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/images/meta-ads-example.png +0 -0
  24. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_auth.sh +0 -0
  25. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/__main__.py +0 -0
  26. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/accounts.py +0 -0
  27. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/ads.py +0 -0
  28. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/ads_library.py +0 -0
  29. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/api.py +0 -0
  30. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/auth.py +0 -0
  31. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/authentication.py +0 -0
  32. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/budget_schedules.py +0 -0
  33. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/callback_server.py +0 -0
  34. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/duplication.py +0 -0
  35. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/http_auth_integration.py +0 -0
  36. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/insights.py +0 -0
  37. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/openai_deep_research.py +0 -0
  38. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
  39. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/reports.py +0 -0
  40. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/resources.py +0 -0
  41. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/server.py +0 -0
  42. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/targeting.py +0 -0
  43. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/meta_ads_mcp/core/utils.py +0 -0
  44. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/requirements.txt +0 -0
  45. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/setup.py +0 -0
  46. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/smithery.yaml +0 -0
  47. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/README.md +0 -0
  48. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/README_REGRESSION_TESTS.md +0 -0
  49. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/__init__.py +0 -0
  50. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/conftest.py +0 -0
  51. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_account_search.py +0 -0
  52. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_duplication.py +0 -0
  53. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_duplication_regression.py +0 -0
  54. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_get_ad_creatives_fix.py +0 -0
  55. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_get_ad_image_regression.py +0 -0
  56. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_http_transport.py +0 -0
  57. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_integration_openai_mcp.py +0 -0
  58. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_openai.py +0 -0
  59. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_openai_mcp_deep_research.py +0 -0
  60. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_targeting.py +0 -0
  61. {meta_ads_mcp-0.7.3 → meta_ads_mcp-0.7.5}/tests/test_targeting_search_e2e.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.7.3
3
+ Version: 0.7.5
4
4
  Summary: Model Context Protocol (MCP) plugin for interacting with Meta Ads API
5
5
  Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
6
6
  Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
@@ -7,7 +7,7 @@ with the Claude LLM.
7
7
 
8
8
  from meta_ads_mcp.core.server import main
9
9
 
10
- __version__ = "0.7.3"
10
+ __version__ = "0.7.5"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -9,7 +9,7 @@ from .insights import get_insights
9
9
  from .authentication import get_login_link
10
10
  from .server import login_cli, main
11
11
  from .auth import login
12
- from .ads_library import search_ads_archive
12
+ from . import ads_library # Import module to register conditional tools
13
13
  from .budget_schedules import create_budget_schedule
14
14
  from .targeting import search_interests, get_interest_suggestions, validate_interests, search_behaviors, search_demographics, search_geo_locations
15
15
  from . import reports # Import module to register conditional tools
@@ -36,7 +36,6 @@ __all__ = [
36
36
  'login_cli',
37
37
  'login',
38
38
  'main',
39
- 'search_ads_archive',
40
39
  'create_budget_schedule',
41
40
  'search_interests',
42
41
  'get_interest_suggestions',
@@ -197,9 +197,10 @@ async def create_adset(
197
197
  @meta_api_tool
198
198
  async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, Any]] = None, bid_strategy: str = None,
199
199
  bid_amount: int = None, status: str = None, targeting: Dict[str, Any] = None,
200
- optimization_goal: str = None, access_token: str = None) -> str:
200
+ optimization_goal: str = None, daily_budget = None, lifetime_budget = None,
201
+ access_token: str = None) -> str:
201
202
  """
202
- Update an ad set with new settings including frequency caps.
203
+ Update an ad set with new settings including frequency caps and budgets.
203
204
 
204
205
  Args:
205
206
  adset_id: Meta Ads ad set ID
@@ -211,6 +212,8 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
211
212
  targeting: Complete targeting specifications (will replace existing targeting)
212
213
  (e.g. {"targeting_automation":{"advantage_audience":1}, "geo_locations": {"countries": ["US"]}})
213
214
  optimization_goal: Conversion optimization goal (e.g., 'LINK_CLICKS', 'CONVERSIONS', 'APP_INSTALLS', etc.)
215
+ daily_budget: Daily budget in account currency (in cents) as a string
216
+ lifetime_budget: Lifetime budget in account currency (in cents) as a string
214
217
  access_token: Meta API access token (optional - will use cached token if not provided)
215
218
  """
216
219
  if not adset_id:
@@ -240,6 +243,13 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
240
243
  else:
241
244
  params['targeting'] = targeting # Already a string
242
245
 
246
+ # Add budget parameters if provided
247
+ if daily_budget is not None:
248
+ params['daily_budget'] = str(daily_budget)
249
+
250
+ if lifetime_budget is not None:
251
+ params['lifetime_budget'] = str(lifetime_budget)
252
+
243
253
  if not params:
244
254
  return json.dumps({"error": "No update parameters provided"}, indent=2)
245
255
 
@@ -99,7 +99,8 @@ async def create_campaign(
99
99
  bid_cap = None,
100
100
  spend_cap = None,
101
101
  campaign_budget_optimization: bool = None,
102
- ab_test_control_setups: Optional[List[Dict[str, Any]]] = None
102
+ ab_test_control_setups: Optional[List[Dict[str, Any]]] = None,
103
+ use_adset_level_budgets: bool = False
103
104
  ) -> str:
104
105
  """
105
106
  Create a new campaign in a Meta Ads account.
@@ -111,14 +112,15 @@ async def create_campaign(
111
112
  objective: Campaign objective. Validates ad objectives. enum{BRAND_AWARENESS, LEAD_GENERATION, LINK_CLICKS, CONVERSIONS, OUTCOME_TRAFFIC, etc.}.
112
113
  status: Initial campaign status (default: PAUSED)
113
114
  special_ad_categories: List of special ad categories if applicable
114
- daily_budget: Daily budget in account currency (in cents) as a string
115
- lifetime_budget: Lifetime budget in account currency (in cents) as a string
115
+ daily_budget: Daily budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False)
116
+ lifetime_budget: Lifetime budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False)
116
117
  buying_type: Buying type (e.g., 'AUCTION')
117
118
  bid_strategy: Bid strategy (e.g., 'LOWEST_COST', 'LOWEST_COST_WITH_BID_CAP', 'COST_CAP')
118
119
  bid_cap: Bid cap in account currency (in cents) as a string
119
120
  spend_cap: Spending limit for the campaign in account currency (in cents) as a string
120
- campaign_budget_optimization: Whether to enable campaign budget optimization
121
+ campaign_budget_optimization: Whether to enable campaign budget optimization (only used if use_adset_level_budgets=False)
121
122
  ab_test_control_setups: Settings for A/B testing (e.g., [{"name":"Creative A", "ad_format":"SINGLE_IMAGE"}])
123
+ use_adset_level_budgets: If True, budgets will be set at the ad set level instead of campaign level (default: False)
122
124
  """
123
125
  # Check required parameters
124
126
  if not account_id:
@@ -134,8 +136,8 @@ async def create_campaign(
134
136
  if special_ad_categories is None:
135
137
  special_ad_categories = []
136
138
 
137
- # For this example, we'll add a fixed daily budget if none is provided
138
- if not daily_budget and not lifetime_budget:
139
+ # For this example, we'll add a fixed daily budget if none is provided and we're not using ad set level budgets
140
+ if not daily_budget and not lifetime_budget and not use_adset_level_budgets:
139
141
  daily_budget = "1000" # Default to $10 USD
140
142
 
141
143
  endpoint = f"{account_id}/campaigns"
@@ -147,12 +149,17 @@ async def create_campaign(
147
149
  "special_ad_categories": json.dumps(special_ad_categories) # Properly format as JSON string
148
150
  }
149
151
 
150
- # Convert budget values to strings if they aren't already
151
- if daily_budget is not None:
152
- params["daily_budget"] = str(daily_budget)
153
-
154
- if lifetime_budget is not None:
155
- params["lifetime_budget"] = str(lifetime_budget)
152
+ # Only set campaign-level budgets if we're not using ad set level budgets
153
+ if not use_adset_level_budgets:
154
+ # Convert budget values to strings if they aren't already
155
+ if daily_budget is not None:
156
+ params["daily_budget"] = str(daily_budget)
157
+
158
+ if lifetime_budget is not None:
159
+ params["lifetime_budget"] = str(lifetime_budget)
160
+
161
+ if campaign_budget_optimization is not None:
162
+ params["campaign_budget_optimization"] = "true" if campaign_budget_optimization else "false"
156
163
 
157
164
  # Add new parameters
158
165
  if buying_type:
@@ -167,14 +174,17 @@ async def create_campaign(
167
174
  if spend_cap is not None:
168
175
  params["spend_cap"] = str(spend_cap)
169
176
 
170
- if campaign_budget_optimization is not None:
171
- params["campaign_budget_optimization"] = "true" if campaign_budget_optimization else "false"
172
-
173
177
  if ab_test_control_setups:
174
178
  params["ab_test_control_setups"] = json.dumps(ab_test_control_setups)
175
179
 
176
180
  try:
177
181
  data = await make_api_request(endpoint, access_token, params, method="POST")
182
+
183
+ # Add a note about budget strategy if using ad set level budgets
184
+ if use_adset_level_budgets:
185
+ data["budget_strategy"] = "ad_set_level"
186
+ data["note"] = "Campaign created with ad set level budgets. Set budgets when creating ad sets within this campaign."
187
+
178
188
  return json.dumps(data, indent=2)
179
189
  except Exception as e:
180
190
  error_msg = str(e)
@@ -200,7 +210,7 @@ async def update_campaign(
200
210
  spend_cap = None,
201
211
  campaign_budget_optimization: bool = None,
202
212
  objective: str = None, # Add objective if it's updatable
203
- # Add other updatable fields as needed based on API docs
213
+ use_adset_level_budgets: bool = None, # Add other updatable fields as needed based on API docs
204
214
  ) -> str:
205
215
  """
206
216
  Update an existing campaign in a Meta Ads account.
@@ -211,13 +221,16 @@ async def update_campaign(
211
221
  name: New campaign name
212
222
  status: New campaign status (e.g., 'ACTIVE', 'PAUSED')
213
223
  special_ad_categories: List of special ad categories if applicable
214
- daily_budget: New daily budget in account currency (in cents) as a string
215
- lifetime_budget: New lifetime budget in account currency (in cents) as a string
224
+ daily_budget: New daily budget in account currency (in cents) as a string.
225
+ Set to empty string "" to remove the daily budget.
226
+ lifetime_budget: New lifetime budget in account currency (in cents) as a string.
227
+ Set to empty string "" to remove the lifetime budget.
216
228
  bid_strategy: New bid strategy
217
229
  bid_cap: New bid cap in account currency (in cents) as a string
218
230
  spend_cap: New spending limit for the campaign in account currency (in cents) as a string
219
231
  campaign_budget_optimization: Enable/disable campaign budget optimization
220
232
  objective: New campaign objective (Note: May not always be updatable)
233
+ use_adset_level_budgets: If True, removes campaign-level budgets to switch to ad set level budgets
221
234
  """
222
235
  if not campaign_id:
223
236
  return json.dumps({"error": "No campaign ID provided"}, indent=2)
@@ -235,18 +248,52 @@ async def update_campaign(
235
248
  # Note: Updating special_ad_categories might have specific API rules or might not be allowed after creation.
236
249
  # The API might require an empty list `[]` to clear categories. Check Meta Docs.
237
250
  params["special_ad_categories"] = json.dumps(special_ad_categories)
238
- if daily_budget is not None:
239
- params["daily_budget"] = str(daily_budget)
240
- if lifetime_budget is not None:
241
- params["lifetime_budget"] = str(lifetime_budget)
251
+
252
+ # Handle budget parameters based on use_adset_level_budgets setting
253
+ if use_adset_level_budgets is not None:
254
+ if use_adset_level_budgets:
255
+ # Remove campaign-level budgets when switching to ad set level budgets
256
+ params["daily_budget"] = ""
257
+ params["lifetime_budget"] = ""
258
+ if campaign_budget_optimization is not None:
259
+ params["campaign_budget_optimization"] = "false"
260
+ else:
261
+ # If switching back to campaign-level budgets, use the provided budget values
262
+ if daily_budget is not None:
263
+ if daily_budget == "":
264
+ params["daily_budget"] = ""
265
+ else:
266
+ params["daily_budget"] = str(daily_budget)
267
+ if lifetime_budget is not None:
268
+ if lifetime_budget == "":
269
+ params["lifetime_budget"] = ""
270
+ else:
271
+ params["lifetime_budget"] = str(lifetime_budget)
272
+ if campaign_budget_optimization is not None:
273
+ params["campaign_budget_optimization"] = "true" if campaign_budget_optimization else "false"
274
+ else:
275
+ # Normal budget updates when not changing budget strategy
276
+ if daily_budget is not None:
277
+ # To remove budget, set to empty string
278
+ if daily_budget == "":
279
+ params["daily_budget"] = ""
280
+ else:
281
+ params["daily_budget"] = str(daily_budget)
282
+ if lifetime_budget is not None:
283
+ # To remove budget, set to empty string
284
+ if lifetime_budget == "":
285
+ params["lifetime_budget"] = ""
286
+ else:
287
+ params["lifetime_budget"] = str(lifetime_budget)
288
+ if campaign_budget_optimization is not None:
289
+ params["campaign_budget_optimization"] = "true" if campaign_budget_optimization else "false"
290
+
242
291
  if bid_strategy is not None:
243
292
  params["bid_strategy"] = bid_strategy
244
293
  if bid_cap is not None:
245
294
  params["bid_cap"] = str(bid_cap)
246
295
  if spend_cap is not None:
247
296
  params["spend_cap"] = str(spend_cap)
248
- if campaign_budget_optimization is not None:
249
- params["campaign_budget_optimization"] = "true" if campaign_budget_optimization else "false"
250
297
  if objective is not None:
251
298
  params["objective"] = objective # Caution: Objective changes might reset learning or be restricted
252
299
 
@@ -256,6 +303,12 @@ async def update_campaign(
256
303
  try:
257
304
  # Use POST method for updates as per Meta API documentation
258
305
  data = await make_api_request(endpoint, access_token, params, method="POST")
306
+
307
+ # Add a note about budget strategy if switching to ad set level budgets
308
+ if use_adset_level_budgets is not None and use_adset_level_budgets:
309
+ data["budget_strategy"] = "ad_set_level"
310
+ data["note"] = "Campaign updated to use ad set level budgets. Set budgets when creating ad sets within this campaign."
311
+
259
312
  return json.dumps(data, indent=2)
260
313
  except Exception as e:
261
314
  error_msg = str(e)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meta-ads-mcp"
7
- version = "0.7.3"
7
+ version = "0.7.5"
8
8
  description = "Model Context Protocol (MCP) plugin for interacting with Meta Ads API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"