meta-ads-mcp 0.11.1__tar.gz → 0.11.3__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.
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/PKG-INFO +18 -18
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/README.md +16 -16
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/__init__.py +1 -1
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/ads.py +4 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/adsets.py +16 -5
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/pyproject.toml +2 -2
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/requirements.txt +1 -1
- meta_ads_mcp-0.11.3/tests/test_is_dynamic_creative_adset.py +77 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/.github/workflows/publish.yml +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/.github/workflows/test.yml +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/.gitignore +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/CUSTOM_META_APP.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/Dockerfile +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/LICENSE +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/LOCAL_INSTALLATION.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/META_API_NOTES.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/RELEASE.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/STREAMABLE_HTTP_SETUP.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/examples/README.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/examples/example_http_client.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/future_improvements.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/images/meta-ads-example.png +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_auth.sh +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/__main__.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/__init__.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/accounts.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/ads_library.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/api.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/auth.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/authentication.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/budget_schedules.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/callback_server.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/campaigns.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/duplication.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/http_auth_integration.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/insights.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/openai_deep_research.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/reports.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/resources.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/server.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/targeting.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/meta_ads_mcp/core/utils.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/setup.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/smithery.yaml +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/README.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/README_REGRESSION_TESTS.md +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/__init__.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/conftest.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/e2e_account_info_search_issue.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_account_info_access_fix.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_account_search.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_budget_update.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_budget_update_e2e.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_dsa_beneficiary.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_dsa_integration.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_duplication.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_duplication_regression.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_dynamic_creatives.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_estimate_audience_size.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_estimate_audience_size_e2e.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_get_account_pages.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_get_ad_creatives_fix.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_get_ad_image_quality_improvements.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_get_ad_image_regression.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_http_transport.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_insights_actions_and_values.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_integration_openai_mcp.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_mobile_app_adset_creation.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_mobile_app_adset_issue.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_openai.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_openai_mcp_deep_research.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_page_discovery.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_page_discovery_integration.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_targeting.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_targeting_search_e2e.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_update_ad_creative_id.py +0 -0
- {meta_ads_mcp-0.11.1 → meta_ads_mcp-0.11.3}/tests/test_upload_ad_image.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meta-ads-mcp
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.3
|
|
4
4
|
Summary: Model Context Protocol (MCP) server 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
|
|
@@ -13,7 +13,7 @@ Classifier: Operating System :: OS Independent
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Requires-Python: >=3.10
|
|
15
15
|
Requires-Dist: httpx>=0.26.0
|
|
16
|
-
Requires-Dist: mcp[cli]
|
|
16
|
+
Requires-Dist: mcp[cli]==1.12.2
|
|
17
17
|
Requires-Dist: pathlib>=1.0.1
|
|
18
18
|
Requires-Dist: pillow>=10.0.0
|
|
19
19
|
Requires-Dist: pytest-asyncio>=1.0.0
|
|
@@ -371,16 +371,16 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
371
371
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
372
372
|
- Returns: A clickable resource link for Meta authentication
|
|
373
373
|
|
|
374
|
-
22. `
|
|
375
|
-
- Create a budget schedule for a Meta Ads campaign
|
|
374
|
+
22. `mcp_meta_ads_create_budget_schedule`
|
|
375
|
+
- Create a budget schedule for a Meta Ads campaign
|
|
376
376
|
- Inputs:
|
|
377
|
-
- `campaign_id`: Meta Ads campaign ID
|
|
378
|
-
- `budget_value`: Amount of budget increase
|
|
379
|
-
- `budget_value_type`: Type of budget value ("ABSOLUTE" or "MULTIPLIER")
|
|
380
|
-
- `time_start`: Unix timestamp for when the high demand period should start
|
|
381
|
-
- `time_end`: Unix timestamp for when the high demand period should end
|
|
382
|
-
- `access_token` (optional): Meta API access token
|
|
383
|
-
- Returns: JSON string with the ID of the created budget schedule or an error message
|
|
377
|
+
- `campaign_id`: Meta Ads campaign ID
|
|
378
|
+
- `budget_value`: Amount of budget increase
|
|
379
|
+
- `budget_value_type`: Type of budget value ("ABSOLUTE" or "MULTIPLIER")
|
|
380
|
+
- `time_start`: Unix timestamp for when the high demand period should start
|
|
381
|
+
- `time_end`: Unix timestamp for when the high demand period should end
|
|
382
|
+
- `access_token` (optional): Meta API access token
|
|
383
|
+
- Returns: JSON string with the ID of the created budget schedule or an error message
|
|
384
384
|
|
|
385
385
|
23. `mcp_meta_ads_search_interests`
|
|
386
386
|
- Search for interest targeting options by keyword
|
|
@@ -398,7 +398,7 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
398
398
|
- `limit`: Maximum number of suggestions to return (default: 25)
|
|
399
399
|
- Returns: Suggested interests with id, name, audience_size, and description fields
|
|
400
400
|
|
|
401
|
-
|
|
401
|
+
25. `mcp_meta_ads_validate_interests`
|
|
402
402
|
- Validate interest names or IDs for targeting
|
|
403
403
|
- Inputs:
|
|
404
404
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
@@ -406,14 +406,14 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
406
406
|
- `interest_fbid_list`: List of interest IDs to validate (e.g., ["6003700426513"])
|
|
407
407
|
- Returns: Validation results showing valid status and audience_size for each interest
|
|
408
408
|
|
|
409
|
-
|
|
409
|
+
26. `mcp_meta_ads_search_behaviors`
|
|
410
410
|
- Get all available behavior targeting options
|
|
411
411
|
- Inputs:
|
|
412
412
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
413
413
|
- `limit`: Maximum number of results to return (default: 50)
|
|
414
414
|
- Returns: Behavior targeting options with id, name, audience_size bounds, path, and description
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
27. `mcp_meta_ads_search_demographics`
|
|
417
417
|
- Get demographic targeting options
|
|
418
418
|
- Inputs:
|
|
419
419
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
@@ -421,7 +421,7 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
421
421
|
- `limit`: Maximum number of results to return (default: 50)
|
|
422
422
|
- Returns: Demographic targeting options with id, name, audience_size bounds, path, and description
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
28. `mcp_meta_ads_search_geo_locations`
|
|
425
425
|
- Search for geographic targeting locations
|
|
426
426
|
- Inputs:
|
|
427
427
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
@@ -430,7 +430,7 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
430
430
|
- `limit`: Maximum number of results to return (default: 25)
|
|
431
431
|
- Returns: Location data with key, name, type, and geographic hierarchy information
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
29. `mcp_meta_ads_search` (Enhanced)
|
|
434
434
|
- Generic search across accounts, campaigns, ads, and pages
|
|
435
435
|
- Automatically includes page searching when query mentions "page" or "pages"
|
|
436
436
|
- Inputs:
|
|
@@ -479,7 +479,7 @@ The easiest way to avoid any setup issues is to **[🎯 use our Remote MCP inste
|
|
|
479
479
|
For comprehensive troubleshooting, debugging, and local installation issues, see our **[Local Installation Guide](LOCAL_INSTALLATION.md)** which includes:
|
|
480
480
|
|
|
481
481
|
- Authentication troubleshooting
|
|
482
|
-
- Installation issues and solutions
|
|
482
|
+
- Installation issues and solutions
|
|
483
483
|
- API error resolution
|
|
484
484
|
- Debug logs and diagnostic commands
|
|
485
|
-
- Performance optimization tips
|
|
485
|
+
- Performance optimization tips
|
|
@@ -346,16 +346,16 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
346
346
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
347
347
|
- Returns: A clickable resource link for Meta authentication
|
|
348
348
|
|
|
349
|
-
22. `
|
|
350
|
-
- Create a budget schedule for a Meta Ads campaign
|
|
349
|
+
22. `mcp_meta_ads_create_budget_schedule`
|
|
350
|
+
- Create a budget schedule for a Meta Ads campaign
|
|
351
351
|
- Inputs:
|
|
352
|
-
- `campaign_id`: Meta Ads campaign ID
|
|
353
|
-
- `budget_value`: Amount of budget increase
|
|
354
|
-
- `budget_value_type`: Type of budget value ("ABSOLUTE" or "MULTIPLIER")
|
|
355
|
-
- `time_start`: Unix timestamp for when the high demand period should start
|
|
356
|
-
- `time_end`: Unix timestamp for when the high demand period should end
|
|
357
|
-
- `access_token` (optional): Meta API access token
|
|
358
|
-
- Returns: JSON string with the ID of the created budget schedule or an error message
|
|
352
|
+
- `campaign_id`: Meta Ads campaign ID
|
|
353
|
+
- `budget_value`: Amount of budget increase
|
|
354
|
+
- `budget_value_type`: Type of budget value ("ABSOLUTE" or "MULTIPLIER")
|
|
355
|
+
- `time_start`: Unix timestamp for when the high demand period should start
|
|
356
|
+
- `time_end`: Unix timestamp for when the high demand period should end
|
|
357
|
+
- `access_token` (optional): Meta API access token
|
|
358
|
+
- Returns: JSON string with the ID of the created budget schedule or an error message
|
|
359
359
|
|
|
360
360
|
23. `mcp_meta_ads_search_interests`
|
|
361
361
|
- Search for interest targeting options by keyword
|
|
@@ -373,7 +373,7 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
373
373
|
- `limit`: Maximum number of suggestions to return (default: 25)
|
|
374
374
|
- Returns: Suggested interests with id, name, audience_size, and description fields
|
|
375
375
|
|
|
376
|
-
|
|
376
|
+
25. `mcp_meta_ads_validate_interests`
|
|
377
377
|
- Validate interest names or IDs for targeting
|
|
378
378
|
- Inputs:
|
|
379
379
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
@@ -381,14 +381,14 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
381
381
|
- `interest_fbid_list`: List of interest IDs to validate (e.g., ["6003700426513"])
|
|
382
382
|
- Returns: Validation results showing valid status and audience_size for each interest
|
|
383
383
|
|
|
384
|
-
|
|
384
|
+
26. `mcp_meta_ads_search_behaviors`
|
|
385
385
|
- Get all available behavior targeting options
|
|
386
386
|
- Inputs:
|
|
387
387
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
388
388
|
- `limit`: Maximum number of results to return (default: 50)
|
|
389
389
|
- Returns: Behavior targeting options with id, name, audience_size bounds, path, and description
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
27. `mcp_meta_ads_search_demographics`
|
|
392
392
|
- Get demographic targeting options
|
|
393
393
|
- Inputs:
|
|
394
394
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
@@ -396,7 +396,7 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
396
396
|
- `limit`: Maximum number of results to return (default: 50)
|
|
397
397
|
- Returns: Demographic targeting options with id, name, audience_size bounds, path, and description
|
|
398
398
|
|
|
399
|
-
|
|
399
|
+
28. `mcp_meta_ads_search_geo_locations`
|
|
400
400
|
- Search for geographic targeting locations
|
|
401
401
|
- Inputs:
|
|
402
402
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
@@ -405,7 +405,7 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
405
405
|
- `limit`: Maximum number of results to return (default: 25)
|
|
406
406
|
- Returns: Location data with key, name, type, and geographic hierarchy information
|
|
407
407
|
|
|
408
|
-
|
|
408
|
+
29. `mcp_meta_ads_search` (Enhanced)
|
|
409
409
|
- Generic search across accounts, campaigns, ads, and pages
|
|
410
410
|
- Automatically includes page searching when query mentions "page" or "pages"
|
|
411
411
|
- Inputs:
|
|
@@ -454,7 +454,7 @@ The easiest way to avoid any setup issues is to **[🎯 use our Remote MCP inste
|
|
|
454
454
|
For comprehensive troubleshooting, debugging, and local installation issues, see our **[Local Installation Guide](LOCAL_INSTALLATION.md)** which includes:
|
|
455
455
|
|
|
456
456
|
- Authentication troubleshooting
|
|
457
|
-
- Installation issues and solutions
|
|
457
|
+
- Installation issues and solutions
|
|
458
458
|
- API error resolution
|
|
459
459
|
- Debug logs and diagnostic commands
|
|
460
|
-
- Performance optimization tips
|
|
460
|
+
- Performance optimization tips
|
|
@@ -111,6 +111,10 @@ async def create_ad(
|
|
|
111
111
|
tracking_specs: Optional tracking specifications (e.g., for pixel events).
|
|
112
112
|
Example: [{"action.type":"offsite_conversion","fb_pixel":["YOUR_PIXEL_ID"]}]
|
|
113
113
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
114
|
+
|
|
115
|
+
Note:
|
|
116
|
+
Dynamic Creative creatives require the parent ad set to have `is_dynamic_creative=true`.
|
|
117
|
+
Otherwise, ad creation will fail with error_subcode 1885998.
|
|
114
118
|
"""
|
|
115
119
|
# Check required parameters
|
|
116
120
|
if not account_id:
|
|
@@ -27,14 +27,14 @@ async def get_adsets(account_id: str, access_token: Optional[str] = None, limit:
|
|
|
27
27
|
if campaign_id:
|
|
28
28
|
endpoint = f"{campaign_id}/adsets"
|
|
29
29
|
params = {
|
|
30
|
-
"fields": "id,name,campaign_id,status,daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,frequency_control_specs{event,interval_days,max_frequency}",
|
|
30
|
+
"fields": "id,name,campaign_id,status,daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,is_dynamic_creative,frequency_control_specs{event,interval_days,max_frequency}",
|
|
31
31
|
"limit": limit
|
|
32
32
|
}
|
|
33
33
|
else:
|
|
34
34
|
# Use account endpoint if no campaign_id is given
|
|
35
35
|
endpoint = f"{account_id}/adsets"
|
|
36
36
|
params = {
|
|
37
|
-
"fields": "id,name,campaign_id,status,daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,frequency_control_specs{event,interval_days,max_frequency}",
|
|
37
|
+
"fields": "id,name,campaign_id,status,daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,is_dynamic_creative,frequency_control_specs{event,interval_days,max_frequency}",
|
|
38
38
|
"limit": limit
|
|
39
39
|
}
|
|
40
40
|
# Note: Removed the attempt to add campaign_id to params for the account endpoint case,
|
|
@@ -67,7 +67,7 @@ async def get_adset_details(adset_id: str, access_token: Optional[str] = None) -
|
|
|
67
67
|
endpoint = f"{adset_id}"
|
|
68
68
|
# Explicitly prioritize frequency_control_specs in the fields request
|
|
69
69
|
params = {
|
|
70
|
-
"fields": "id,name,campaign_id,status,frequency_control_specs{event,interval_days,max_frequency},daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,attribution_spec,destination_type,promoted_object,pacing_type,budget_remaining,dsa_beneficiary"
|
|
70
|
+
"fields": "id,name,campaign_id,status,frequency_control_specs{event,interval_days,max_frequency},daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,attribution_spec,destination_type,promoted_object,pacing_type,budget_remaining,dsa_beneficiary,is_dynamic_creative"
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
data = await make_api_request(endpoint, access_token, params)
|
|
@@ -100,6 +100,7 @@ async def create_adset(
|
|
|
100
100
|
dsa_beneficiary: Optional[str] = None,
|
|
101
101
|
promoted_object: Optional[Dict[str, Any]] = None,
|
|
102
102
|
destination_type: Optional[str] = None,
|
|
103
|
+
is_dynamic_creative: Optional[bool] = None,
|
|
103
104
|
access_token: Optional[str] = None
|
|
104
105
|
) -> str:
|
|
105
106
|
"""
|
|
@@ -125,8 +126,9 @@ async def create_adset(
|
|
|
125
126
|
Optional fields: custom_event_type, pixel_id, page_id.
|
|
126
127
|
Example: {"application_id": "123456789012345", "object_store_url": "https://apps.apple.com/app/id123456789"}
|
|
127
128
|
destination_type: Where users are directed after clicking the ad (e.g., 'APP_STORE', 'DEEPLINK', 'APP_INSTALL', 'ON_AD').
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
Required for mobile app campaigns and lead generation campaigns.
|
|
130
|
+
Use 'ON_AD' for lead generation campaigns where user interaction happens within the ad.
|
|
131
|
+
is_dynamic_creative: Enable Dynamic Creative for this ad set (required when using dynamic creatives with asset_feed_spec/dynamic_creative_spec).
|
|
130
132
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
131
133
|
"""
|
|
132
134
|
# Check required parameters
|
|
@@ -249,6 +251,10 @@ async def create_adset(
|
|
|
249
251
|
if destination_type:
|
|
250
252
|
params["destination_type"] = destination_type
|
|
251
253
|
|
|
254
|
+
# Enable Dynamic Creative if requested
|
|
255
|
+
if is_dynamic_creative is not None:
|
|
256
|
+
params["is_dynamic_creative"] = "true" if bool(is_dynamic_creative) else "false"
|
|
257
|
+
|
|
252
258
|
try:
|
|
253
259
|
data = await make_api_request(endpoint, access_token, params, method="POST")
|
|
254
260
|
return json.dumps(data, indent=2)
|
|
@@ -290,6 +296,7 @@ async def create_adset(
|
|
|
290
296
|
async def update_adset(adset_id: str, frequency_control_specs: Optional[List[Dict[str, Any]]] = None, bid_strategy: Optional[str] = None,
|
|
291
297
|
bid_amount: Optional[int] = None, status: Optional[str] = None, targeting: Optional[Dict[str, Any]] = None,
|
|
292
298
|
optimization_goal: Optional[str] = None, daily_budget: Optional[int] = None, lifetime_budget: Optional[int] = None,
|
|
299
|
+
is_dynamic_creative: Optional[bool] = None,
|
|
293
300
|
access_token: Optional[str] = None) -> str:
|
|
294
301
|
"""
|
|
295
302
|
Update an ad set with new settings including frequency caps and budgets.
|
|
@@ -306,6 +313,7 @@ async def update_adset(adset_id: str, frequency_control_specs: Optional[List[Dic
|
|
|
306
313
|
optimization_goal: Conversion optimization goal (e.g., 'LINK_CLICKS', 'CONVERSIONS', 'APP_INSTALLS', etc.)
|
|
307
314
|
daily_budget: Daily budget in account currency (in cents) as a string
|
|
308
315
|
lifetime_budget: Lifetime budget in account currency (in cents) as a string
|
|
316
|
+
is_dynamic_creative: Enable/disable Dynamic Creative for this ad set.
|
|
309
317
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
310
318
|
"""
|
|
311
319
|
if not adset_id:
|
|
@@ -342,6 +350,9 @@ async def update_adset(adset_id: str, frequency_control_specs: Optional[List[Dic
|
|
|
342
350
|
if lifetime_budget is not None:
|
|
343
351
|
params['lifetime_budget'] = str(lifetime_budget)
|
|
344
352
|
|
|
353
|
+
if is_dynamic_creative is not None:
|
|
354
|
+
params['is_dynamic_creative'] = "true" if bool(is_dynamic_creative) else "false"
|
|
355
|
+
|
|
345
356
|
if not params:
|
|
346
357
|
return json.dumps({"error": "No update parameters provided"}, indent=2)
|
|
347
358
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meta-ads-mcp"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.3"
|
|
8
8
|
description = "Model Context Protocol (MCP) server for interacting with Meta Ads API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
22
|
"httpx>=0.26.0",
|
|
23
|
-
"mcp[cli]
|
|
23
|
+
"mcp[cli]==1.12.2",
|
|
24
24
|
"python-dotenv>=1.1.0",
|
|
25
25
|
"requests>=2.32.3",
|
|
26
26
|
"Pillow>=10.0.0",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pytest
|
|
3
|
+
from unittest.mock import AsyncMock, patch
|
|
4
|
+
|
|
5
|
+
from meta_ads_mcp.core.adsets import create_adset, update_adset, get_adsets, get_adset_details
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.asyncio
|
|
9
|
+
async def test_create_adset_includes_is_dynamic_creative_true():
|
|
10
|
+
sample_response = {"id": "adset_1", "name": "DC Adset"}
|
|
11
|
+
with patch('meta_ads_mcp.core.adsets.make_api_request', new_callable=AsyncMock) as mock_api:
|
|
12
|
+
mock_api.return_value = sample_response
|
|
13
|
+
|
|
14
|
+
result = await create_adset(
|
|
15
|
+
account_id="act_123",
|
|
16
|
+
campaign_id="cmp_1",
|
|
17
|
+
name="DC Adset",
|
|
18
|
+
optimization_goal="LINK_CLICKS",
|
|
19
|
+
billing_event="IMPRESSIONS",
|
|
20
|
+
targeting={"geo_locations": {"countries": ["US"]}},
|
|
21
|
+
is_dynamic_creative=True,
|
|
22
|
+
access_token="test_token",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
assert json.loads(result)["id"] == "adset_1"
|
|
26
|
+
# Verify param was sent as string boolean
|
|
27
|
+
call_args = mock_api.call_args
|
|
28
|
+
params = call_args[0][2]
|
|
29
|
+
assert params["is_dynamic_creative"] == "true"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.asyncio
|
|
33
|
+
async def test_update_adset_includes_is_dynamic_creative_false():
|
|
34
|
+
sample_response = {"success": True}
|
|
35
|
+
with patch('meta_ads_mcp.core.adsets.make_api_request', new_callable=AsyncMock) as mock_api:
|
|
36
|
+
mock_api.return_value = sample_response
|
|
37
|
+
|
|
38
|
+
result = await update_adset(
|
|
39
|
+
adset_id="120",
|
|
40
|
+
is_dynamic_creative=False,
|
|
41
|
+
access_token="test_token",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
assert json.loads(result)["success"] is True
|
|
45
|
+
call_args = mock_api.call_args
|
|
46
|
+
params = call_args[0][2]
|
|
47
|
+
assert params["is_dynamic_creative"] == "false"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_get_adsets_fields_include_is_dynamic_creative():
|
|
52
|
+
sample_response = {"data": []}
|
|
53
|
+
with patch('meta_ads_mcp.core.adsets.make_api_request', new_callable=AsyncMock) as mock_api:
|
|
54
|
+
mock_api.return_value = sample_response
|
|
55
|
+
|
|
56
|
+
result = await get_adsets(account_id="act_123", access_token="test_token", limit=1)
|
|
57
|
+
assert json.loads(result)["data"] == []
|
|
58
|
+
|
|
59
|
+
call_args = mock_api.call_args
|
|
60
|
+
params = call_args[0][2]
|
|
61
|
+
assert "is_dynamic_creative" in params.get("fields", "")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_get_adset_details_fields_include_is_dynamic_creative():
|
|
66
|
+
sample_response = {"id": "120", "name": "Test", "is_dynamic_creative": True}
|
|
67
|
+
with patch('meta_ads_mcp.core.adsets.make_api_request', new_callable=AsyncMock) as mock_api:
|
|
68
|
+
mock_api.return_value = sample_response
|
|
69
|
+
|
|
70
|
+
result = await get_adset_details(adset_id="120", access_token="test_token")
|
|
71
|
+
assert json.loads(result)["id"] == "120"
|
|
72
|
+
|
|
73
|
+
call_args = mock_api.call_args
|
|
74
|
+
params = call_args[0][2]
|
|
75
|
+
assert "is_dynamic_creative" in params.get("fields", "")
|
|
76
|
+
|
|
77
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|