meta-ads-mcp 0.7.10__tar.gz → 0.8.0__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 (68) hide show
  1. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/PKG-INFO +40 -12
  2. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/README.md +39 -11
  3. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/__init__.py +1 -1
  4. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/ads.py +239 -16
  5. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/openai_deep_research.py +57 -1
  6. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/pyproject.toml +1 -1
  7. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_duplication_regression.py +22 -34
  8. meta_ads_mcp-0.8.0/tests/test_dynamic_creatives.py +860 -0
  9. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_openai_mcp_deep_research.py +52 -1
  10. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_page_discovery_integration.py +87 -49
  11. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/.github/workflows/publish.yml +0 -0
  12. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/.github/workflows/test.yml +0 -0
  13. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/.gitignore +0 -0
  14. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/CUSTOM_META_APP.md +0 -0
  15. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/Dockerfile +0 -0
  16. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/LICENSE +0 -0
  17. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/LOCAL_INSTALLATION.md +0 -0
  18. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/META_API_NOTES.md +0 -0
  19. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/RELEASE.md +0 -0
  20. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/STREAMABLE_HTTP_SETUP.md +0 -0
  21. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/examples/README.md +0 -0
  22. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/examples/example_http_client.py +0 -0
  23. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/future_improvements.md +0 -0
  24. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/images/meta-ads-example.png +0 -0
  25. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_auth.sh +0 -0
  26. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/__main__.py +0 -0
  27. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/__init__.py +0 -0
  28. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/accounts.py +0 -0
  29. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/ads_library.py +0 -0
  30. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/adsets.py +0 -0
  31. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/api.py +0 -0
  32. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/auth.py +0 -0
  33. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/authentication.py +0 -0
  34. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/budget_schedules.py +0 -0
  35. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/callback_server.py +0 -0
  36. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/campaigns.py +0 -0
  37. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/duplication.py +0 -0
  38. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/http_auth_integration.py +0 -0
  39. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/insights.py +0 -0
  40. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
  41. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/reports.py +0 -0
  42. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/resources.py +0 -0
  43. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/server.py +0 -0
  44. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/targeting.py +0 -0
  45. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/meta_ads_mcp/core/utils.py +0 -0
  46. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/requirements.txt +0 -0
  47. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/setup.py +0 -0
  48. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/smithery.yaml +0 -0
  49. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/README.md +0 -0
  50. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/README_REGRESSION_TESTS.md +0 -0
  51. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/__init__.py +0 -0
  52. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/conftest.py +0 -0
  53. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_account_search.py +0 -0
  54. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_budget_update.py +0 -0
  55. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_budget_update_e2e.py +0 -0
  56. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_dsa_beneficiary.py +0 -0
  57. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_dsa_integration.py +0 -0
  58. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_duplication.py +0 -0
  59. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_get_ad_creatives_fix.py +0 -0
  60. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_get_ad_image_quality_improvements.py +0 -0
  61. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_get_ad_image_regression.py +0 -0
  62. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_http_transport.py +0 -0
  63. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_insights_actions_and_values.py +0 -0
  64. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_integration_openai_mcp.py +0 -0
  65. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_openai.py +0 -0
  66. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_page_discovery.py +0 -0
  67. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/tests/test_targeting.py +0 -0
  68. {meta_ads_mcp-0.7.10 → meta_ads_mcp-0.8.0}/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.10
3
+ Version: 0.8.0
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
@@ -111,9 +111,11 @@ For detailed step-by-step instructions, authentication setup, debugging, and tro
111
111
  - **Automated Monitoring**: Ask any MCP-compatible LLM to track performance metrics and alert you about significant changes
112
112
  - **Budget Optimization**: Get recommendations for reallocating budget to better-performing ad sets
113
113
  - **Creative Improvement**: Receive feedback on ad copy, imagery, and calls-to-action
114
+ - **Dynamic Creative Testing**: Easy API for both simple ads (single headline/description) and advanced A/B testing (multiple headlines/descriptions)
114
115
  - **Campaign Management**: Request changes to campaigns, ad sets, and ads (all changes require explicit confirmation)
115
116
  - **Cross-Platform Integration**: Works with Facebook, Instagram, and all Meta ad platforms
116
117
  - **Universal LLM Support**: Compatible with any MCP client including Claude Desktop, Cursor, Cherry Studio, and more
118
+ - **Enhanced Search**: Generic search function includes page searching when queries mention "page" or "pages"
117
119
  - **Simple Authentication**: Easy setup with secure OAuth authentication
118
120
  - **Cross-Platform Support**: Works on Windows, macOS, and Linux
119
121
 
@@ -260,14 +262,32 @@ For local installation configuration, authentication options, and advanced techn
260
262
  - `page_id`: Facebook Page ID for the ad
261
263
  - `link_url`: Destination URL
262
264
  - `message`: Ad copy/text
263
- - `headline`: Ad headline
264
- - `description`: Ad description
265
+ - `headline`: Single headline for simple ads (cannot be used with headlines)
266
+ - `headlines`: List of headlines for dynamic creative testing (cannot be used with headline)
267
+ - `description`: Single description for simple ads (cannot be used with descriptions)
268
+ - `descriptions`: List of descriptions for dynamic creative testing (cannot be used with description)
269
+ - `dynamic_creative_spec`: Dynamic creative optimization settings
265
270
  - `call_to_action_type`: CTA button type (e.g., 'LEARN_MORE')
266
271
  - `instagram_actor_id`: Optional Instagram account ID
267
272
  - `access_token` (optional): Meta API access token
268
273
  - Returns: Confirmation with new creative details
269
274
 
270
- 15. `mcp_meta_ads_upload_ad_image`
275
+ 15. `mcp_meta_ads_update_ad_creative`
276
+ - Update an existing ad creative with new content or settings
277
+ - Inputs:
278
+ - `creative_id`: Meta Ads creative ID to update
279
+ - `name`: New creative name
280
+ - `message`: New ad copy/text
281
+ - `headline`: Single headline for simple ads (cannot be used with headlines)
282
+ - `headlines`: New list of headlines for dynamic creative testing (cannot be used with headline)
283
+ - `description`: Single description for simple ads (cannot be used with descriptions)
284
+ - `descriptions`: New list of descriptions for dynamic creative testing (cannot be used with description)
285
+ - `dynamic_creative_spec`: New dynamic creative optimization settings
286
+ - `call_to_action_type`: New call to action button type
287
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
288
+ - Returns: Confirmation with updated creative details
289
+
290
+ 16. `mcp_meta_ads_upload_ad_image`
271
291
  - Upload an image to use in Meta Ads creatives
272
292
  - Inputs:
273
293
  - `account_id`: Meta Ads account ID (format: act_XXXXXXXXX)
@@ -276,14 +296,14 @@ For local installation configuration, authentication options, and advanced techn
276
296
  - `access_token` (optional): Meta API access token
277
297
  - Returns: JSON response with image details including hash
278
298
 
279
- 16. `mcp_meta_ads_get_ad_image`
299
+ 17. `mcp_meta_ads_get_ad_image`
280
300
  - Get, download, and visualize a Meta ad image in one step
281
301
  - Inputs:
282
302
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
283
303
  - `ad_id`: Meta Ads ad ID
284
304
  - Returns: The ad image ready for direct visual analysis
285
305
 
286
- 17. `mcp_meta_ads_update_ad`
306
+ 18. `mcp_meta_ads_update_ad`
287
307
  - Update an ad with new settings
288
308
  - Inputs:
289
309
  - `ad_id`: Meta Ads ad ID
@@ -292,7 +312,7 @@ For local installation configuration, authentication options, and advanced techn
292
312
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
293
313
  - Returns: Confirmation with updated ad details and a confirmation link
294
314
 
295
- 18. `mcp_meta_ads_update_adset`
315
+ 19. `mcp_meta_ads_update_adset`
296
316
  - Update an ad set with new settings including frequency caps
297
317
  - Inputs:
298
318
  - `adset_id`: Meta Ads ad set ID
@@ -304,7 +324,7 @@ For local installation configuration, authentication options, and advanced techn
304
324
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
305
325
  - Returns: Confirmation with updated ad set details and a confirmation link
306
326
 
307
- 19. `mcp_meta_ads_get_insights`
327
+ 20. `mcp_meta_ads_get_insights`
308
328
  - Get performance insights for a campaign, ad set, ad or account
309
329
  - Inputs:
310
330
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
@@ -314,13 +334,13 @@ For local installation configuration, authentication options, and advanced techn
314
334
  - `level`: Level of aggregation (ad, adset, campaign, account)
315
335
  - Returns: Performance metrics for the specified object
316
336
 
317
- 20. `mcp_meta_ads_get_login_link`
337
+ 21. `mcp_meta_ads_get_login_link`
318
338
  - Get a clickable login link for Meta Ads authentication
319
339
  - Inputs:
320
340
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
321
341
  - Returns: A clickable resource link for Meta authentication
322
342
 
323
- 21. `mcp_meta-ads_create_budget_schedule`
343
+ 22. `mcp_meta-ads_create_budget_schedule`
324
344
  - Create a budget schedule for a Meta Ads campaign.
325
345
  - Inputs:
326
346
  - `campaign_id`: Meta Ads campaign ID.
@@ -331,7 +351,7 @@ For local installation configuration, authentication options, and advanced techn
331
351
  - `access_token` (optional): Meta API access token.
332
352
  - Returns: JSON string with the ID of the created budget schedule or an error message.
333
353
 
334
- 22. `mcp_meta_ads_search_interests`
354
+ 23. `mcp_meta_ads_search_interests`
335
355
  - Search for interest targeting options by keyword
336
356
  - Inputs:
337
357
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
@@ -339,7 +359,7 @@ For local installation configuration, authentication options, and advanced techn
339
359
  - `limit`: Maximum number of results to return (default: 25)
340
360
  - Returns: Interest data with id, name, audience_size, and path fields
341
361
 
342
- 23. `mcp_meta_ads_get_interest_suggestions`
362
+ 24. `mcp_meta_ads_get_interest_suggestions`
343
363
  - Get interest suggestions based on existing interests
344
364
  - Inputs:
345
365
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
@@ -379,6 +399,14 @@ For local installation configuration, authentication options, and advanced techn
379
399
  - `limit`: Maximum number of results to return (default: 25)
380
400
  - Returns: Location data with key, name, type, and geographic hierarchy information
381
401
 
402
+ 28. `mcp_meta_ads_search` (Enhanced)
403
+ - Generic search across accounts, campaigns, ads, and pages
404
+ - Automatically includes page searching when query mentions "page" or "pages"
405
+ - Inputs:
406
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
407
+ - `query`: Search query string (e.g., "Injury Payouts pages", "active campaigns")
408
+ - Returns: List of matching record IDs in ChatGPT-compatible format
409
+
382
410
  ## Privacy and Security
383
411
 
384
412
  Meta Ads MCP follows security best practices with secure token management and automatic authentication handling.
@@ -86,9 +86,11 @@ For detailed step-by-step instructions, authentication setup, debugging, and tro
86
86
  - **Automated Monitoring**: Ask any MCP-compatible LLM to track performance metrics and alert you about significant changes
87
87
  - **Budget Optimization**: Get recommendations for reallocating budget to better-performing ad sets
88
88
  - **Creative Improvement**: Receive feedback on ad copy, imagery, and calls-to-action
89
+ - **Dynamic Creative Testing**: Easy API for both simple ads (single headline/description) and advanced A/B testing (multiple headlines/descriptions)
89
90
  - **Campaign Management**: Request changes to campaigns, ad sets, and ads (all changes require explicit confirmation)
90
91
  - **Cross-Platform Integration**: Works with Facebook, Instagram, and all Meta ad platforms
91
92
  - **Universal LLM Support**: Compatible with any MCP client including Claude Desktop, Cursor, Cherry Studio, and more
93
+ - **Enhanced Search**: Generic search function includes page searching when queries mention "page" or "pages"
92
94
  - **Simple Authentication**: Easy setup with secure OAuth authentication
93
95
  - **Cross-Platform Support**: Works on Windows, macOS, and Linux
94
96
 
@@ -235,14 +237,32 @@ For local installation configuration, authentication options, and advanced techn
235
237
  - `page_id`: Facebook Page ID for the ad
236
238
  - `link_url`: Destination URL
237
239
  - `message`: Ad copy/text
238
- - `headline`: Ad headline
239
- - `description`: Ad description
240
+ - `headline`: Single headline for simple ads (cannot be used with headlines)
241
+ - `headlines`: List of headlines for dynamic creative testing (cannot be used with headline)
242
+ - `description`: Single description for simple ads (cannot be used with descriptions)
243
+ - `descriptions`: List of descriptions for dynamic creative testing (cannot be used with description)
244
+ - `dynamic_creative_spec`: Dynamic creative optimization settings
240
245
  - `call_to_action_type`: CTA button type (e.g., 'LEARN_MORE')
241
246
  - `instagram_actor_id`: Optional Instagram account ID
242
247
  - `access_token` (optional): Meta API access token
243
248
  - Returns: Confirmation with new creative details
244
249
 
245
- 15. `mcp_meta_ads_upload_ad_image`
250
+ 15. `mcp_meta_ads_update_ad_creative`
251
+ - Update an existing ad creative with new content or settings
252
+ - Inputs:
253
+ - `creative_id`: Meta Ads creative ID to update
254
+ - `name`: New creative name
255
+ - `message`: New ad copy/text
256
+ - `headline`: Single headline for simple ads (cannot be used with headlines)
257
+ - `headlines`: New list of headlines for dynamic creative testing (cannot be used with headline)
258
+ - `description`: Single description for simple ads (cannot be used with descriptions)
259
+ - `descriptions`: New list of descriptions for dynamic creative testing (cannot be used with description)
260
+ - `dynamic_creative_spec`: New dynamic creative optimization settings
261
+ - `call_to_action_type`: New call to action button type
262
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
263
+ - Returns: Confirmation with updated creative details
264
+
265
+ 16. `mcp_meta_ads_upload_ad_image`
246
266
  - Upload an image to use in Meta Ads creatives
247
267
  - Inputs:
248
268
  - `account_id`: Meta Ads account ID (format: act_XXXXXXXXX)
@@ -251,14 +271,14 @@ For local installation configuration, authentication options, and advanced techn
251
271
  - `access_token` (optional): Meta API access token
252
272
  - Returns: JSON response with image details including hash
253
273
 
254
- 16. `mcp_meta_ads_get_ad_image`
274
+ 17. `mcp_meta_ads_get_ad_image`
255
275
  - Get, download, and visualize a Meta ad image in one step
256
276
  - Inputs:
257
277
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
258
278
  - `ad_id`: Meta Ads ad ID
259
279
  - Returns: The ad image ready for direct visual analysis
260
280
 
261
- 17. `mcp_meta_ads_update_ad`
281
+ 18. `mcp_meta_ads_update_ad`
262
282
  - Update an ad with new settings
263
283
  - Inputs:
264
284
  - `ad_id`: Meta Ads ad ID
@@ -267,7 +287,7 @@ For local installation configuration, authentication options, and advanced techn
267
287
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
268
288
  - Returns: Confirmation with updated ad details and a confirmation link
269
289
 
270
- 18. `mcp_meta_ads_update_adset`
290
+ 19. `mcp_meta_ads_update_adset`
271
291
  - Update an ad set with new settings including frequency caps
272
292
  - Inputs:
273
293
  - `adset_id`: Meta Ads ad set ID
@@ -279,7 +299,7 @@ For local installation configuration, authentication options, and advanced techn
279
299
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
280
300
  - Returns: Confirmation with updated ad set details and a confirmation link
281
301
 
282
- 19. `mcp_meta_ads_get_insights`
302
+ 20. `mcp_meta_ads_get_insights`
283
303
  - Get performance insights for a campaign, ad set, ad or account
284
304
  - Inputs:
285
305
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
@@ -289,13 +309,13 @@ For local installation configuration, authentication options, and advanced techn
289
309
  - `level`: Level of aggregation (ad, adset, campaign, account)
290
310
  - Returns: Performance metrics for the specified object
291
311
 
292
- 20. `mcp_meta_ads_get_login_link`
312
+ 21. `mcp_meta_ads_get_login_link`
293
313
  - Get a clickable login link for Meta Ads authentication
294
314
  - Inputs:
295
315
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
296
316
  - Returns: A clickable resource link for Meta authentication
297
317
 
298
- 21. `mcp_meta-ads_create_budget_schedule`
318
+ 22. `mcp_meta-ads_create_budget_schedule`
299
319
  - Create a budget schedule for a Meta Ads campaign.
300
320
  - Inputs:
301
321
  - `campaign_id`: Meta Ads campaign ID.
@@ -306,7 +326,7 @@ For local installation configuration, authentication options, and advanced techn
306
326
  - `access_token` (optional): Meta API access token.
307
327
  - Returns: JSON string with the ID of the created budget schedule or an error message.
308
328
 
309
- 22. `mcp_meta_ads_search_interests`
329
+ 23. `mcp_meta_ads_search_interests`
310
330
  - Search for interest targeting options by keyword
311
331
  - Inputs:
312
332
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
@@ -314,7 +334,7 @@ For local installation configuration, authentication options, and advanced techn
314
334
  - `limit`: Maximum number of results to return (default: 25)
315
335
  - Returns: Interest data with id, name, audience_size, and path fields
316
336
 
317
- 23. `mcp_meta_ads_get_interest_suggestions`
337
+ 24. `mcp_meta_ads_get_interest_suggestions`
318
338
  - Get interest suggestions based on existing interests
319
339
  - Inputs:
320
340
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
@@ -354,6 +374,14 @@ For local installation configuration, authentication options, and advanced techn
354
374
  - `limit`: Maximum number of results to return (default: 25)
355
375
  - Returns: Location data with key, name, type, and geographic hierarchy information
356
376
 
377
+ 28. `mcp_meta_ads_search` (Enhanced)
378
+ - Generic search across accounts, campaigns, ads, and pages
379
+ - Automatically includes page searching when query mentions "page" or "pages"
380
+ - Inputs:
381
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
382
+ - `query`: Search query string (e.g., "Injury Payouts pages", "active campaigns")
383
+ - Returns: List of matching record IDs in ChatGPT-compatible format
384
+
357
385
  ## Privacy and Security
358
386
 
359
387
  Meta Ads MCP follows security best practices with secure token management and automatic authentication handling.
@@ -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.10"
10
+ __version__ = "0.8.0"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -633,7 +633,10 @@ async def create_ad_creative(
633
633
  link_url: str = None,
634
634
  message: str = None,
635
635
  headline: str = None,
636
+ headlines: List[str] = None,
636
637
  description: str = None,
638
+ descriptions: List[str] = None,
639
+ dynamic_creative_spec: Dict[str, Any] = None,
637
640
  call_to_action_type: str = None,
638
641
  instagram_actor_id: str = None
639
642
  ) -> str:
@@ -648,8 +651,11 @@ async def create_ad_creative(
648
651
  page_id: Facebook Page ID to be used for the ad
649
652
  link_url: Destination URL for the ad
650
653
  message: Ad copy/text
651
- headline: Ad headline
652
- description: Ad description
654
+ headline: Single headline for simple ads (cannot be used with headlines)
655
+ headlines: List of headlines for dynamic creative testing (cannot be used with headline)
656
+ description: Single description for simple ads (cannot be used with descriptions)
657
+ descriptions: List of descriptions for dynamic creative testing (cannot be used with description)
658
+ dynamic_creative_spec: Dynamic creative optimization settings
653
659
  call_to_action_type: Call to action button type (e.g., 'LEARN_MORE', 'SIGN_UP', 'SHOP_NOW')
654
660
  instagram_actor_id: Optional Instagram account ID for Instagram placements
655
661
 
@@ -697,29 +703,98 @@ async def create_ad_creative(
697
703
  "suggestion": "Please provide a page_id parameter or use get_account_pages to find available pages"
698
704
  }, indent=2)
699
705
 
706
+ # Validate headline/description parameters - cannot mix simple and complex
707
+ if headline and headlines:
708
+ return json.dumps({"error": "Cannot specify both 'headline' and 'headlines'. Use 'headline' for single headline or 'headlines' for multiple."}, indent=2)
709
+
710
+ if description and descriptions:
711
+ return json.dumps({"error": "Cannot specify both 'description' and 'descriptions'. Use 'description' for single description or 'descriptions' for multiple."}, indent=2)
712
+
713
+ # Convert simple parameters to complex format for internal processing
714
+ final_headlines = None
715
+ final_descriptions = None
716
+
717
+ if headline:
718
+ final_headlines = [headline]
719
+ elif headlines:
720
+ final_headlines = headlines
721
+
722
+ if description:
723
+ final_descriptions = [description]
724
+ elif descriptions:
725
+ final_descriptions = descriptions
726
+
727
+ # Validate dynamic creative parameters
728
+ if final_headlines:
729
+ if len(final_headlines) > 5:
730
+ return json.dumps({"error": "Maximum 5 headlines allowed for dynamic creatives"}, indent=2)
731
+ for i, h in enumerate(final_headlines):
732
+ if len(h) > 40:
733
+ return json.dumps({"error": f"Headline {i+1} exceeds 40 character limit"}, indent=2)
734
+
735
+ if final_descriptions:
736
+ if len(final_descriptions) > 5:
737
+ return json.dumps({"error": "Maximum 5 descriptions allowed for dynamic creatives"}, indent=2)
738
+ for i, d in enumerate(final_descriptions):
739
+ if len(d) > 125:
740
+ return json.dumps({"error": f"Description {i+1} exceeds 125 character limit"}, indent=2)
741
+
700
742
  # Prepare the creative data
701
743
  creative_data = {
702
- "name": name,
703
- "object_story_spec": {
744
+ "name": name
745
+ }
746
+
747
+ # Choose between asset_feed_spec (dynamic creative) or object_story_spec (traditional)
748
+ if final_headlines or final_descriptions:
749
+ # Use asset_feed_spec for dynamic creatives
750
+ asset_feed_spec = {
751
+ "ad_formats": ["SINGLE_IMAGE"],
752
+ "images": [{"hash": image_hash}],
753
+ "link_urls": [{"website_url": link_url if link_url else "https://facebook.com"}]
754
+ }
755
+
756
+ # Handle headlines
757
+ if final_headlines:
758
+ asset_feed_spec["headlines"] = [{"text": headline_text} for headline_text in final_headlines]
759
+
760
+ # Handle descriptions
761
+ if final_descriptions:
762
+ asset_feed_spec["descriptions"] = [{"text": description_text} for description_text in final_descriptions]
763
+
764
+ # Add message as primary_texts if provided
765
+ if message:
766
+ asset_feed_spec["primary_texts"] = [{"text": message}]
767
+
768
+ # Add call_to_action_types if provided
769
+ if call_to_action_type:
770
+ asset_feed_spec["call_to_action_types"] = [call_to_action_type]
771
+
772
+ creative_data["asset_feed_spec"] = asset_feed_spec
773
+
774
+ # For dynamic creatives, we need a simplified object_story_spec
775
+ creative_data["object_story_spec"] = {
776
+ "page_id": page_id
777
+ }
778
+ else:
779
+ # Use traditional object_story_spec for single creative
780
+ creative_data["object_story_spec"] = {
704
781
  "page_id": page_id,
705
782
  "link_data": {
706
783
  "image_hash": image_hash,
707
784
  "link": link_url if link_url else "https://facebook.com"
708
785
  }
709
786
  }
710
- }
711
-
712
- # Add optional parameters if provided
713
- if message:
714
- creative_data["object_story_spec"]["link_data"]["message"] = message
715
-
716
- if headline:
717
- creative_data["object_story_spec"]["link_data"]["name"] = headline
718
787
 
719
- if description:
720
- creative_data["object_story_spec"]["link_data"]["description"] = description
788
+ # Add optional parameters if provided
789
+ if message:
790
+ creative_data["object_story_spec"]["link_data"]["message"] = message
721
791
 
722
- if call_to_action_type:
792
+ # Add dynamic creative spec if provided
793
+ if dynamic_creative_spec:
794
+ creative_data["dynamic_creative_spec"] = dynamic_creative_spec
795
+
796
+ # Only add call_to_action to object_story_spec if we're not using asset_feed_spec
797
+ if call_to_action_type and "asset_feed_spec" not in creative_data:
723
798
  creative_data["object_story_spec"]["link_data"]["call_to_action"] = {
724
799
  "type": call_to_action_type
725
800
  }
@@ -739,7 +814,7 @@ async def create_ad_creative(
739
814
  creative_id = data["id"]
740
815
  creative_endpoint = f"{creative_id}"
741
816
  creative_params = {
742
- "fields": "id,name,status,thumbnail_url,image_url,image_hash,object_story_spec,url_tags,link_url"
817
+ "fields": "id,name,status,thumbnail_url,image_url,image_hash,object_story_spec,asset_feed_spec,url_tags,link_url"
743
818
  }
744
819
 
745
820
  creative_details = await make_api_request(creative_endpoint, access_token, creative_params)
@@ -759,6 +834,154 @@ async def create_ad_creative(
759
834
  }, indent=2)
760
835
 
761
836
 
837
+ @mcp_server.tool()
838
+ @meta_api_tool
839
+ async def update_ad_creative(
840
+ access_token: str = None,
841
+ creative_id: str = None,
842
+ name: str = None,
843
+ message: str = None,
844
+ headline: str = None,
845
+ headlines: List[str] = None,
846
+ description: str = None,
847
+ descriptions: List[str] = None,
848
+ dynamic_creative_spec: Dict[str, Any] = None,
849
+ call_to_action_type: str = None
850
+ ) -> str:
851
+ """
852
+ Update an existing ad creative with new content or settings.
853
+
854
+ Args:
855
+ access_token: Meta API access token (optional - will use cached token if not provided)
856
+ creative_id: Meta Ads creative ID to update
857
+ name: New creative name
858
+ message: New ad copy/text
859
+ headline: Single headline for simple ads (cannot be used with headlines)
860
+ headlines: New list of headlines for dynamic creative testing (cannot be used with headline)
861
+ description: Single description for simple ads (cannot be used with descriptions)
862
+ descriptions: New list of descriptions for dynamic creative testing (cannot be used with description)
863
+ dynamic_creative_spec: New dynamic creative optimization settings
864
+ call_to_action_type: New call to action button type
865
+
866
+ Returns:
867
+ JSON response with updated creative details
868
+ """
869
+ # Check required parameters
870
+ if not creative_id:
871
+ return json.dumps({"error": "No creative ID provided"}, indent=2)
872
+
873
+ # Validate headline/description parameters - cannot mix simple and complex
874
+ if headline and headlines:
875
+ return json.dumps({"error": "Cannot specify both 'headline' and 'headlines'. Use 'headline' for single headline or 'headlines' for multiple."}, indent=2)
876
+
877
+ if description and descriptions:
878
+ return json.dumps({"error": "Cannot specify both 'description' and 'descriptions'. Use 'description' for single description or 'descriptions' for multiple."}, indent=2)
879
+
880
+ # Convert simple parameters to complex format for internal processing
881
+ final_headlines = None
882
+ final_descriptions = None
883
+
884
+ if headline:
885
+ final_headlines = [headline]
886
+ elif headlines:
887
+ final_headlines = headlines
888
+
889
+ if description:
890
+ final_descriptions = [description]
891
+ elif descriptions:
892
+ final_descriptions = descriptions
893
+
894
+ # Validate dynamic creative parameters
895
+ if final_headlines:
896
+ if len(final_headlines) > 5:
897
+ return json.dumps({"error": "Maximum 5 headlines allowed for dynamic creatives"}, indent=2)
898
+ for i, h in enumerate(final_headlines):
899
+ if len(h) > 40:
900
+ return json.dumps({"error": f"Headline {i+1} exceeds 40 character limit"}, indent=2)
901
+
902
+ if final_descriptions:
903
+ if len(final_descriptions) > 5:
904
+ return json.dumps({"error": "Maximum 5 descriptions allowed for dynamic creatives"}, indent=2)
905
+ for i, d in enumerate(final_descriptions):
906
+ if len(d) > 125:
907
+ return json.dumps({"error": f"Description {i+1} exceeds 125 character limit"}, indent=2)
908
+
909
+ # Prepare the update data
910
+ update_data = {}
911
+
912
+ if name:
913
+ update_data["name"] = name
914
+
915
+ if message:
916
+ update_data["object_story_spec"] = {"link_data": {"message": message}}
917
+
918
+ # Handle dynamic creative assets via asset_feed_spec
919
+ if final_headlines or final_descriptions or dynamic_creative_spec:
920
+ asset_feed_spec = {}
921
+
922
+ # Add required ad_formats field for dynamic creatives
923
+ asset_feed_spec["ad_formats"] = ["SINGLE_IMAGE"]
924
+
925
+ # Handle headlines
926
+ if final_headlines:
927
+ asset_feed_spec["headlines"] = [{"text": headline_text} for headline_text in final_headlines]
928
+
929
+ # Handle descriptions
930
+ if final_descriptions:
931
+ asset_feed_spec["descriptions"] = [{"text": description_text} for description_text in final_descriptions]
932
+
933
+ # Add message as primary_texts if provided
934
+ if message:
935
+ asset_feed_spec["primary_texts"] = [{"text": message}]
936
+
937
+ update_data["asset_feed_spec"] = asset_feed_spec
938
+
939
+ # Add dynamic creative spec if provided
940
+ if dynamic_creative_spec:
941
+ update_data["dynamic_creative_spec"] = dynamic_creative_spec
942
+
943
+ # Handle call_to_action - add to asset_feed_spec if using dynamic creative, otherwise to object_story_spec
944
+ if call_to_action_type:
945
+ if "asset_feed_spec" in update_data:
946
+ update_data["asset_feed_spec"]["call_to_action_types"] = [call_to_action_type]
947
+ else:
948
+ if "object_story_spec" not in update_data:
949
+ update_data["object_story_spec"] = {"link_data": {}}
950
+ update_data["object_story_spec"]["link_data"]["call_to_action"] = {
951
+ "type": call_to_action_type
952
+ }
953
+
954
+ # Prepare the API endpoint for updating the creative
955
+ endpoint = f"{creative_id}"
956
+
957
+ try:
958
+ # Make API request to update the creative
959
+ data = await make_api_request(endpoint, access_token, update_data, method="POST")
960
+
961
+ # If successful, get more details about the updated creative
962
+ if "id" in data:
963
+ creative_endpoint = f"{creative_id}"
964
+ creative_params = {
965
+ "fields": "id,name,status,thumbnail_url,image_url,image_hash,object_story_spec,url_tags,link_url,dynamic_creative_spec"
966
+ }
967
+
968
+ creative_details = await make_api_request(creative_endpoint, access_token, creative_params)
969
+ return json.dumps({
970
+ "success": True,
971
+ "creative_id": creative_id,
972
+ "details": creative_details
973
+ }, indent=2)
974
+
975
+ return json.dumps(data, indent=2)
976
+
977
+ except Exception as e:
978
+ return json.dumps({
979
+ "error": "Failed to update ad creative",
980
+ "details": str(e),
981
+ "update_data_sent": update_data
982
+ }, indent=2)
983
+
984
+
762
985
  async def _discover_pages_for_account(account_id: str, access_token: str) -> dict:
763
986
  """
764
987
  Internal function to discover pages for an account using multiple approaches.
@@ -77,6 +77,32 @@ class MetaAdsDataManager:
77
77
  logger.error(f"Error fetching ads for {account_id}: {e}")
78
78
  return []
79
79
 
80
+ async def _get_pages_for_account(self, access_token: str, account_id: str) -> List[Dict[str, Any]]:
81
+ """Get pages associated with an account"""
82
+ try:
83
+ # Import the page discovery function from ads module
84
+ from .ads import _discover_pages_for_account
85
+
86
+ # Ensure account_id has the 'act_' prefix
87
+ if not account_id.startswith("act_"):
88
+ account_id = f"act_{account_id}"
89
+
90
+ page_discovery_result = await _discover_pages_for_account(account_id, access_token)
91
+
92
+ if not page_discovery_result.get("success"):
93
+ return []
94
+
95
+ # Return page data in a consistent format
96
+ return [{
97
+ "id": page_discovery_result["page_id"],
98
+ "name": page_discovery_result.get("page_name", "Unknown"),
99
+ "source": page_discovery_result.get("source", "unknown"),
100
+ "account_id": account_id
101
+ }]
102
+ except Exception as e:
103
+ logger.error(f"Error fetching pages for {account_id}: {e}")
104
+ return []
105
+
80
106
  async def search_records(self, query: str, access_token: str) -> List[str]:
81
107
  """Search Meta Ads data and return matching record IDs
82
108
 
@@ -176,6 +202,34 @@ class MetaAdsDataManager:
176
202
  },
177
203
  "raw_data": ad
178
204
  }
205
+
206
+ # If query specifically mentions "page" or "pages", also search pages
207
+ if any(term in ['page', 'pages', 'facebook page'] for term in query_terms):
208
+ for account in accounts[:5]: # Limit to first 5 accounts for performance
209
+ pages = await self._get_pages_for_account(access_token, account['id'])
210
+ for page in pages:
211
+ page_text = f"{page.get('name', '')} {page.get('source', '')}".lower()
212
+
213
+ if any(term in page_text for term in query_terms):
214
+ page_record_id = f"page:{page['id']}"
215
+ matching_ids.append(page_record_id)
216
+
217
+ # Cache the page data
218
+ self._cache[page_record_id] = {
219
+ "id": page_record_id,
220
+ "type": "page",
221
+ "title": f"Facebook Page: {page.get('name', 'Unnamed Page')}",
222
+ "text": f"Facebook Page {page.get('name', 'Unnamed')} (ID: {page.get('id', 'N/A')}) - Source: {page.get('source', 'Unknown')}, Account: {account.get('name', 'Unknown')}",
223
+ "metadata": {
224
+ "page_id": page.get('id'),
225
+ "page_name": page.get('name'),
226
+ "source": page.get('source'),
227
+ "account_id": account.get('id'),
228
+ "account_name": account.get('name'),
229
+ "data_type": "meta_ads_page"
230
+ },
231
+ "raw_data": page
232
+ }
179
233
 
180
234
  except Exception as e:
181
235
  logger.error(f"Error during search operation: {e}")
@@ -219,7 +273,7 @@ async def search(
219
273
  Search through Meta Ads data and return matching record IDs.
220
274
 
221
275
  This tool is required for OpenAI ChatGPT Deep Research integration.
222
- It searches across ad accounts, campaigns, and ads to find relevant records
276
+ It searches across ad accounts, campaigns, ads, and pages to find relevant records
223
277
  based on the provided query.
224
278
 
225
279
  Args:
@@ -233,6 +287,7 @@ async def search(
233
287
  search(query="active campaigns")
234
288
  search(query="account spending")
235
289
  search(query="facebook ads performance")
290
+ search(query="facebook pages")
236
291
  """
237
292
  if not query:
238
293
  return json.dumps({
@@ -285,6 +340,7 @@ async def fetch(
285
340
  fetch(id="account:act_123456789")
286
341
  fetch(id="campaign:23842588888640185")
287
342
  fetch(id="ad:23842614006130185")
343
+ fetch(id="page:123456789")
288
344
  """
289
345
  if not id:
290
346
  return json.dumps({