meta-ads-mcp 0.10.9__tar.gz → 0.11.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.
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/PKG-INFO +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/__init__.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/accounts.py +4 -4
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/ads.py +60 -66
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/ads_library.py +5 -5
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/adsets.py +28 -34
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/api.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/auth.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/authentication.py +2 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/budget_schedules.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/campaigns.py +33 -39
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/duplication.py +4 -4
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/insights.py +2 -2
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/openai_deep_research.py +4 -4
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/pipeboard_auth.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/reports.py +3 -3
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/targeting.py +14 -14
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/pyproject.toml +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_dsa_beneficiary.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_dsa_integration.py +1 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_dynamic_creatives.py +2 -1
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_insights_actions_and_values.py +7 -10
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_targeting.py +15 -6
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/.github/workflows/publish.yml +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/.github/workflows/test.yml +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/.gitignore +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/CUSTOM_META_APP.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/Dockerfile +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/LICENSE +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/LOCAL_INSTALLATION.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/META_API_NOTES.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/README.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/RELEASE.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/STREAMABLE_HTTP_SETUP.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/examples/README.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/examples/example_http_client.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/future_improvements.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/images/meta-ads-example.png +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_auth.sh +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/__main__.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/__init__.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/callback_server.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/http_auth_integration.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/resources.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/server.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/utils.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/requirements.txt +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/setup.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/smithery.yaml +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/README.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/README_REGRESSION_TESTS.md +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/__init__.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/conftest.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/e2e_account_info_search_issue.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_account_info_access_fix.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_account_search.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_budget_update.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_budget_update_e2e.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_duplication.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_duplication_regression.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_estimate_audience_size.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_estimate_audience_size_e2e.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_get_account_pages.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_get_ad_creatives_fix.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_get_ad_image_quality_improvements.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_get_ad_image_regression.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_http_transport.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_integration_openai_mcp.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_mobile_app_adset_creation.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_mobile_app_adset_issue.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_openai.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_openai_mcp_deep_research.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_page_discovery.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_page_discovery_integration.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_targeting_search_e2e.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/tests/test_update_ad_creative_id.py +0 -0
- {meta_ads_mcp-0.10.9 → meta_ads_mcp-0.11.0}/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.
|
|
3
|
+
Version: 0.11.0
|
|
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
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""Account-related functionality for Meta Ads API."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
5
|
from .api import meta_api_tool, make_api_request
|
|
6
6
|
from .server import mcp_server
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@mcp_server.tool()
|
|
10
10
|
@meta_api_tool
|
|
11
|
-
async def get_ad_accounts(access_token: str = None, user_id: str = "me", limit: int = 200) -> str:
|
|
11
|
+
async def get_ad_accounts(access_token: Optional[str] = None, user_id: str = "me", limit: int = 200) -> str:
|
|
12
12
|
"""
|
|
13
13
|
Get ad accounts accessible by a user.
|
|
14
14
|
|
|
@@ -30,13 +30,13 @@ async def get_ad_accounts(access_token: str = None, user_id: str = "me", limit:
|
|
|
30
30
|
|
|
31
31
|
@mcp_server.tool()
|
|
32
32
|
@meta_api_tool
|
|
33
|
-
async def get_account_info(
|
|
33
|
+
async def get_account_info(account_id: str, access_token: Optional[str] = None) -> str:
|
|
34
34
|
"""
|
|
35
35
|
Get detailed information about a specific ad account.
|
|
36
36
|
|
|
37
37
|
Args:
|
|
38
|
+
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
38
39
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
39
|
-
account_id: Meta Ads account ID (format: act_XXXXXXXXX) - REQUIRED
|
|
40
40
|
"""
|
|
41
41
|
if not account_id:
|
|
42
42
|
return {
|
|
@@ -16,27 +16,21 @@ from .server import mcp_server
|
|
|
16
16
|
|
|
17
17
|
@mcp_server.tool()
|
|
18
18
|
@meta_api_tool
|
|
19
|
-
async def get_ads(
|
|
19
|
+
async def get_ads(account_id: str, access_token: Optional[str] = None, limit: int = 10,
|
|
20
20
|
campaign_id: str = "", adset_id: str = "") -> str:
|
|
21
21
|
"""
|
|
22
22
|
Get ads for a Meta Ads account with optional filtering.
|
|
23
23
|
|
|
24
24
|
Args:
|
|
25
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
26
25
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
26
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
27
27
|
limit: Maximum number of ads to return (default: 10)
|
|
28
28
|
campaign_id: Optional campaign ID to filter by
|
|
29
29
|
adset_id: Optional ad set ID to filter by
|
|
30
30
|
"""
|
|
31
|
-
#
|
|
31
|
+
# Require explicit account_id
|
|
32
32
|
if not account_id:
|
|
33
|
-
|
|
34
|
-
accounts_data = json.loads(accounts_json)
|
|
35
|
-
|
|
36
|
-
if "data" in accounts_data and accounts_data["data"]:
|
|
37
|
-
account_id = accounts_data["data"][0]["id"]
|
|
38
|
-
else:
|
|
39
|
-
return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
|
|
33
|
+
return json.dumps({"error": "No account ID specified"}, indent=2)
|
|
40
34
|
|
|
41
35
|
# Prioritize adset_id over campaign_id - use adset-specific endpoint
|
|
42
36
|
if adset_id:
|
|
@@ -67,13 +61,13 @@ async def get_ads(access_token: str = None, account_id: str = None, limit: int =
|
|
|
67
61
|
|
|
68
62
|
@mcp_server.tool()
|
|
69
63
|
@meta_api_tool
|
|
70
|
-
async def get_ad_details(
|
|
64
|
+
async def get_ad_details(ad_id: str, access_token: Optional[str] = None) -> str:
|
|
71
65
|
"""
|
|
72
66
|
Get detailed information about a specific ad.
|
|
73
67
|
|
|
74
68
|
Args:
|
|
75
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
76
69
|
ad_id: Meta Ads ad ID
|
|
70
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
77
71
|
"""
|
|
78
72
|
if not ad_id:
|
|
79
73
|
return json.dumps({"error": "No ad ID provided"}, indent=2)
|
|
@@ -91,14 +85,14 @@ async def get_ad_details(access_token: str = None, ad_id: str = None) -> str:
|
|
|
91
85
|
@mcp_server.tool()
|
|
92
86
|
@meta_api_tool
|
|
93
87
|
async def create_ad(
|
|
94
|
-
account_id: str
|
|
95
|
-
name: str
|
|
96
|
-
adset_id: str
|
|
97
|
-
creative_id: str
|
|
88
|
+
account_id: str,
|
|
89
|
+
name: str,
|
|
90
|
+
adset_id: str,
|
|
91
|
+
creative_id: str,
|
|
98
92
|
status: str = "PAUSED",
|
|
99
|
-
bid_amount = None,
|
|
93
|
+
bid_amount: Optional[int] = None,
|
|
100
94
|
tracking_specs: Optional[List[Dict[str, Any]]] = None,
|
|
101
|
-
access_token: str = None
|
|
95
|
+
access_token: Optional[str] = None
|
|
102
96
|
) -> str:
|
|
103
97
|
"""
|
|
104
98
|
Create a new ad with an existing creative.
|
|
@@ -158,13 +152,13 @@ async def create_ad(
|
|
|
158
152
|
|
|
159
153
|
@mcp_server.tool()
|
|
160
154
|
@meta_api_tool
|
|
161
|
-
async def get_ad_creatives(
|
|
155
|
+
async def get_ad_creatives(ad_id: str, access_token: Optional[str] = None) -> str:
|
|
162
156
|
"""
|
|
163
157
|
Get creative details for a specific ad. Best if combined with get_ad_image to get the full image.
|
|
164
158
|
|
|
165
159
|
Args:
|
|
166
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
167
160
|
ad_id: Meta Ads ad ID
|
|
161
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
168
162
|
"""
|
|
169
163
|
if not ad_id:
|
|
170
164
|
return json.dumps({"error": "No ad ID provided"}, indent=2)
|
|
@@ -186,13 +180,13 @@ async def get_ad_creatives(access_token: str = None, ad_id: str = None) -> str:
|
|
|
186
180
|
|
|
187
181
|
@mcp_server.tool()
|
|
188
182
|
@meta_api_tool
|
|
189
|
-
async def get_ad_image(
|
|
183
|
+
async def get_ad_image(ad_id: str, access_token: Optional[str] = None) -> Image:
|
|
190
184
|
"""
|
|
191
185
|
Get, download, and visualize a Meta ad image in one step. Useful to see the image in the LLM.
|
|
192
186
|
|
|
193
187
|
Args:
|
|
194
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
195
188
|
ad_id: Meta Ads ad ID
|
|
189
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
196
190
|
|
|
197
191
|
Returns:
|
|
198
192
|
The ad image ready for direct visual analysis
|
|
@@ -388,13 +382,13 @@ async def get_ad_image(access_token: str = None, ad_id: str = None) -> Image:
|
|
|
388
382
|
|
|
389
383
|
@mcp_server.tool()
|
|
390
384
|
@meta_api_tool
|
|
391
|
-
async def save_ad_image_locally(
|
|
385
|
+
async def save_ad_image_locally(ad_id: str, access_token: Optional[str] = None, output_dir: str = "ad_images") -> str:
|
|
392
386
|
"""
|
|
393
387
|
Get, download, and save a Meta ad image locally, returning the file path.
|
|
394
388
|
|
|
395
389
|
Args:
|
|
396
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
397
390
|
ad_id: Meta Ads ad ID
|
|
391
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
398
392
|
output_dir: Directory to save the image file (default: 'ad_images')
|
|
399
393
|
|
|
400
394
|
Returns:
|
|
@@ -516,11 +510,11 @@ async def save_ad_image_locally(access_token: str = None, ad_id: str = None, out
|
|
|
516
510
|
@meta_api_tool
|
|
517
511
|
async def update_ad(
|
|
518
512
|
ad_id: str,
|
|
519
|
-
status: str = None,
|
|
520
|
-
bid_amount: int = None,
|
|
521
|
-
tracking_specs = None,
|
|
522
|
-
creative_id: str = None,
|
|
523
|
-
access_token: str = None
|
|
513
|
+
status: Optional[str] = None,
|
|
514
|
+
bid_amount: Optional[int] = None,
|
|
515
|
+
tracking_specs: Optional[List[Dict[str, Any]]] = None,
|
|
516
|
+
creative_id: Optional[str] = None,
|
|
517
|
+
access_token: Optional[str] = None
|
|
524
518
|
) -> str:
|
|
525
519
|
"""
|
|
526
520
|
Update an ad with new settings.
|
|
@@ -562,18 +556,18 @@ async def update_ad(
|
|
|
562
556
|
@mcp_server.tool()
|
|
563
557
|
@meta_api_tool
|
|
564
558
|
async def upload_ad_image(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
file: str = None,
|
|
568
|
-
image_url: str = None,
|
|
569
|
-
name: str = None
|
|
559
|
+
account_id: str,
|
|
560
|
+
access_token: Optional[str] = None,
|
|
561
|
+
file: Optional[str] = None,
|
|
562
|
+
image_url: Optional[str] = None,
|
|
563
|
+
name: Optional[str] = None
|
|
570
564
|
) -> str:
|
|
571
565
|
"""
|
|
572
566
|
Upload an image to use in Meta Ads creatives.
|
|
573
567
|
|
|
574
568
|
Args:
|
|
575
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
576
569
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
570
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
577
571
|
file: Data URL or raw base64 string of the image (e.g., "data:image/png;base64,iVBORw0KG...")
|
|
578
572
|
image_url: Direct URL to an image to fetch and upload
|
|
579
573
|
name: Optional name for the image (default: filename)
|
|
@@ -731,29 +725,29 @@ async def upload_ad_image(
|
|
|
731
725
|
@mcp_server.tool()
|
|
732
726
|
@meta_api_tool
|
|
733
727
|
async def create_ad_creative(
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
page_id: str = None,
|
|
739
|
-
link_url: str = None,
|
|
740
|
-
message: str = None,
|
|
741
|
-
headline: str = None,
|
|
742
|
-
headlines: List[str] = None,
|
|
743
|
-
description: str = None,
|
|
744
|
-
descriptions: List[str] = None,
|
|
745
|
-
dynamic_creative_spec: Dict[str, Any] = None,
|
|
746
|
-
call_to_action_type: str = None,
|
|
747
|
-
instagram_actor_id: str = None
|
|
728
|
+
account_id: str,
|
|
729
|
+
image_hash: str,
|
|
730
|
+
access_token: Optional[str] = None,
|
|
731
|
+
name: Optional[str] = None,
|
|
732
|
+
page_id: Optional[str] = None,
|
|
733
|
+
link_url: Optional[str] = None,
|
|
734
|
+
message: Optional[str] = None,
|
|
735
|
+
headline: Optional[str] = None,
|
|
736
|
+
headlines: Optional[List[str]] = None,
|
|
737
|
+
description: Optional[str] = None,
|
|
738
|
+
descriptions: Optional[List[str]] = None,
|
|
739
|
+
dynamic_creative_spec: Optional[Dict[str, Any]] = None,
|
|
740
|
+
call_to_action_type: Optional[str] = None,
|
|
741
|
+
instagram_actor_id: Optional[str] = None
|
|
748
742
|
) -> str:
|
|
749
743
|
"""
|
|
750
744
|
Create a new ad creative using an uploaded image hash.
|
|
751
745
|
|
|
752
746
|
Args:
|
|
753
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
754
747
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
755
|
-
name: Creative name
|
|
756
748
|
image_hash: Hash of the uploaded image
|
|
749
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
750
|
+
name: Creative name
|
|
757
751
|
page_id: Facebook Page ID to be used for the ad
|
|
758
752
|
link_url: Destination URL for the ad
|
|
759
753
|
message: Ad copy/text
|
|
@@ -943,23 +937,23 @@ async def create_ad_creative(
|
|
|
943
937
|
@mcp_server.tool()
|
|
944
938
|
@meta_api_tool
|
|
945
939
|
async def update_ad_creative(
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
name: str = None,
|
|
949
|
-
message: str = None,
|
|
950
|
-
headline: str = None,
|
|
951
|
-
headlines: List[str] = None,
|
|
952
|
-
description: str = None,
|
|
953
|
-
descriptions: List[str] = None,
|
|
954
|
-
dynamic_creative_spec: Dict[str, Any] = None,
|
|
955
|
-
call_to_action_type: str = None
|
|
940
|
+
creative_id: str,
|
|
941
|
+
access_token: Optional[str] = None,
|
|
942
|
+
name: Optional[str] = None,
|
|
943
|
+
message: Optional[str] = None,
|
|
944
|
+
headline: Optional[str] = None,
|
|
945
|
+
headlines: Optional[List[str]] = None,
|
|
946
|
+
description: Optional[str] = None,
|
|
947
|
+
descriptions: Optional[List[str]] = None,
|
|
948
|
+
dynamic_creative_spec: Optional[Dict[str, Any]] = None,
|
|
949
|
+
call_to_action_type: Optional[str] = None
|
|
956
950
|
) -> str:
|
|
957
951
|
"""
|
|
958
952
|
Update an existing ad creative with new content or settings.
|
|
959
953
|
|
|
960
954
|
Args:
|
|
961
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
962
955
|
creative_id: Meta Ads creative ID to update
|
|
956
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
963
957
|
name: New creative name
|
|
964
958
|
message: New ad copy/text
|
|
965
959
|
headline: Single headline for simple ads (cannot be used with headlines)
|
|
@@ -1252,13 +1246,13 @@ async def _search_pages_by_name_core(access_token: str, account_id: str, search_
|
|
|
1252
1246
|
|
|
1253
1247
|
@mcp_server.tool()
|
|
1254
1248
|
@meta_api_tool
|
|
1255
|
-
async def search_pages_by_name(
|
|
1249
|
+
async def search_pages_by_name(account_id: str, access_token: Optional[str] = None, search_term: Optional[str] = None) -> str:
|
|
1256
1250
|
"""
|
|
1257
1251
|
Search for pages by name within an account.
|
|
1258
1252
|
|
|
1259
1253
|
Args:
|
|
1260
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
1261
1254
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
1255
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
1262
1256
|
search_term: Search term to find pages by name (optional - returns all pages if not provided)
|
|
1263
1257
|
|
|
1264
1258
|
Returns:
|
|
@@ -1275,13 +1269,13 @@ async def search_pages_by_name(access_token: str = None, account_id: str = None,
|
|
|
1275
1269
|
|
|
1276
1270
|
@mcp_server.tool()
|
|
1277
1271
|
@meta_api_tool
|
|
1278
|
-
async def get_account_pages(
|
|
1272
|
+
async def get_account_pages(account_id: str, access_token: Optional[str] = None) -> str:
|
|
1279
1273
|
"""
|
|
1280
1274
|
Get pages associated with a Meta Ads account.
|
|
1281
1275
|
|
|
1282
1276
|
Args:
|
|
1283
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
1284
1277
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
1278
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
1285
1279
|
|
|
1286
1280
|
Returns:
|
|
1287
1281
|
JSON response with pages associated with the account
|
|
@@ -14,10 +14,10 @@ if not DISABLE_ADS_LIBRARY:
|
|
|
14
14
|
@mcp_server.tool()
|
|
15
15
|
@meta_api_tool
|
|
16
16
|
async def search_ads_archive(
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
search_terms: str,
|
|
18
|
+
ad_reached_countries: List[str],
|
|
19
|
+
access_token: Optional[str] = None,
|
|
19
20
|
ad_type: str = "ALL",
|
|
20
|
-
ad_reached_countries: List[str] = None,
|
|
21
21
|
limit: int = 25, # Default limit, adjust as needed
|
|
22
22
|
fields: str = "ad_creation_time,ad_creative_body,ad_creative_link_caption,ad_creative_link_description,ad_creative_link_title,ad_delivery_start_time,ad_delivery_stop_time,ad_snapshot_url,currency,demographic_distribution,funding_entity,impressions,page_id,page_name,publisher_platform,region_distribution,spend"
|
|
23
23
|
) -> str:
|
|
@@ -25,10 +25,10 @@ if not DISABLE_ADS_LIBRARY:
|
|
|
25
25
|
Search the Facebook Ads Library archive.
|
|
26
26
|
|
|
27
27
|
Args:
|
|
28
|
-
access_token: Meta API access token (optional - will use cached token if not provided).
|
|
29
28
|
search_terms: The search query for ads.
|
|
30
|
-
ad_type: Type of ads to search for (e.g., POLITICAL_AND_ISSUE_ADS, HOUSING_ADS, ALL).
|
|
31
29
|
ad_reached_countries: List of country codes (e.g., ["US", "GB"]).
|
|
30
|
+
access_token: Meta API access token (optional - will use cached token if not provided).
|
|
31
|
+
ad_type: Type of ads to search for (e.g., POLITICAL_AND_ISSUE_ADS, HOUSING_ADS, ALL).
|
|
32
32
|
limit: Maximum number of ads to return.
|
|
33
33
|
fields: Comma-separated string of fields to retrieve for each ad.
|
|
34
34
|
|
|
@@ -9,25 +9,19 @@ from .server import mcp_server
|
|
|
9
9
|
|
|
10
10
|
@mcp_server.tool()
|
|
11
11
|
@meta_api_tool
|
|
12
|
-
async def get_adsets(
|
|
12
|
+
async def get_adsets(account_id: str, access_token: Optional[str] = None, limit: int = 10, campaign_id: str = "") -> str:
|
|
13
13
|
"""
|
|
14
14
|
Get ad sets for a Meta Ads account with optional filtering by campaign.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
18
17
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
18
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
19
19
|
limit: Maximum number of ad sets to return (default: 10)
|
|
20
20
|
campaign_id: Optional campaign ID to filter by
|
|
21
21
|
"""
|
|
22
|
-
#
|
|
22
|
+
# Require explicit account_id
|
|
23
23
|
if not account_id:
|
|
24
|
-
|
|
25
|
-
accounts_data = json.loads(accounts_json)
|
|
26
|
-
|
|
27
|
-
if "data" in accounts_data and accounts_data["data"]:
|
|
28
|
-
account_id = accounts_data["data"][0]["id"]
|
|
29
|
-
else:
|
|
30
|
-
return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
|
|
24
|
+
return json.dumps({"error": "No account ID specified"}, indent=2)
|
|
31
25
|
|
|
32
26
|
# Change endpoint based on whether campaign_id is provided
|
|
33
27
|
if campaign_id:
|
|
@@ -53,12 +47,12 @@ async def get_adsets(access_token: str = None, account_id: str = None, limit: in
|
|
|
53
47
|
|
|
54
48
|
@mcp_server.tool()
|
|
55
49
|
@meta_api_tool
|
|
56
|
-
async def get_adset_details(
|
|
50
|
+
async def get_adset_details(adset_id: str, access_token: Optional[str] = None) -> str:
|
|
57
51
|
"""
|
|
58
52
|
Get detailed information about a specific ad set.
|
|
59
53
|
|
|
60
54
|
Args:
|
|
61
|
-
adset_id: Meta Ads ad set ID
|
|
55
|
+
adset_id: Meta Ads ad set ID
|
|
62
56
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
63
57
|
|
|
64
58
|
Example:
|
|
@@ -90,23 +84,23 @@ async def get_adset_details(access_token: str = None, adset_id: str = None) -> s
|
|
|
90
84
|
@mcp_server.tool()
|
|
91
85
|
@meta_api_tool
|
|
92
86
|
async def create_adset(
|
|
93
|
-
account_id: str
|
|
94
|
-
campaign_id: str
|
|
95
|
-
name: str
|
|
87
|
+
account_id: str,
|
|
88
|
+
campaign_id: str,
|
|
89
|
+
name: str,
|
|
90
|
+
optimization_goal: str,
|
|
91
|
+
billing_event: str,
|
|
96
92
|
status: str = "PAUSED",
|
|
97
|
-
daily_budget = None,
|
|
98
|
-
lifetime_budget = None,
|
|
99
|
-
targeting: Dict[str, Any] = None,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
destination_type: str = None,
|
|
109
|
-
access_token: str = None
|
|
93
|
+
daily_budget: Optional[int] = None,
|
|
94
|
+
lifetime_budget: Optional[int] = None,
|
|
95
|
+
targeting: Optional[Dict[str, Any]] = None,
|
|
96
|
+
bid_amount: Optional[int] = None,
|
|
97
|
+
bid_strategy: Optional[str] = None,
|
|
98
|
+
start_time: Optional[str] = None,
|
|
99
|
+
end_time: Optional[str] = None,
|
|
100
|
+
dsa_beneficiary: Optional[str] = None,
|
|
101
|
+
promoted_object: Optional[Dict[str, Any]] = None,
|
|
102
|
+
destination_type: Optional[str] = None,
|
|
103
|
+
access_token: Optional[str] = None
|
|
110
104
|
) -> str:
|
|
111
105
|
"""
|
|
112
106
|
Create a new ad set in a Meta Ads account.
|
|
@@ -115,13 +109,13 @@ async def create_adset(
|
|
|
115
109
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
116
110
|
campaign_id: Meta Ads campaign ID this ad set belongs to
|
|
117
111
|
name: Ad set name
|
|
112
|
+
optimization_goal: Conversion optimization goal (e.g., 'LINK_CLICKS', 'REACH', 'CONVERSIONS', 'APP_INSTALLS')
|
|
113
|
+
billing_event: How you're charged (e.g., 'IMPRESSIONS', 'LINK_CLICKS')
|
|
118
114
|
status: Initial ad set status (default: PAUSED)
|
|
119
115
|
daily_budget: Daily budget in account currency (in cents) as a string
|
|
120
116
|
lifetime_budget: Lifetime budget in account currency (in cents) as a string
|
|
121
117
|
targeting: Targeting specifications including age, location, interests, etc.
|
|
122
118
|
Use targeting_automation.advantage_audience=1 for automatic audience finding
|
|
123
|
-
optimization_goal: Conversion optimization goal (e.g., 'LINK_CLICKS', 'REACH', 'CONVERSIONS', 'APP_INSTALLS')
|
|
124
|
-
billing_event: How you're charged (e.g., 'IMPRESSIONS', 'LINK_CLICKS')
|
|
125
119
|
bid_amount: Bid amount in account currency (in cents)
|
|
126
120
|
bid_strategy: Bid strategy (e.g., 'LOWEST_COST', 'LOWEST_COST_WITH_BID_CAP')
|
|
127
121
|
start_time: Start time in ISO 8601 format (e.g., '2023-12-01T12:00:00-0800')
|
|
@@ -293,10 +287,10 @@ async def create_adset(
|
|
|
293
287
|
|
|
294
288
|
@mcp_server.tool()
|
|
295
289
|
@meta_api_tool
|
|
296
|
-
async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, Any]] = None, bid_strategy: str = None,
|
|
297
|
-
bid_amount: int = None, status: str = None, targeting: Dict[str, Any] = None,
|
|
298
|
-
optimization_goal: str = None, daily_budget = None, lifetime_budget = None,
|
|
299
|
-
access_token: str = None) -> str:
|
|
290
|
+
async def update_adset(adset_id: str, frequency_control_specs: Optional[List[Dict[str, Any]]] = None, bid_strategy: Optional[str] = None,
|
|
291
|
+
bid_amount: Optional[int] = None, status: Optional[str] = None, targeting: Optional[Dict[str, Any]] = None,
|
|
292
|
+
optimization_goal: Optional[str] = None, daily_budget: Optional[int] = None, lifetime_budget: Optional[int] = None,
|
|
293
|
+
access_token: Optional[str] = None) -> str:
|
|
300
294
|
"""
|
|
301
295
|
Update an ad set with new settings including frequency caps and budgets.
|
|
302
296
|
|
|
@@ -87,7 +87,7 @@ meta_config = MetaConfig()
|
|
|
87
87
|
|
|
88
88
|
class TokenInfo:
|
|
89
89
|
"""Stores token information including expiration"""
|
|
90
|
-
def __init__(self, access_token: str, expires_in: int = None, user_id: str = None):
|
|
90
|
+
def __init__(self, access_token: str, expires_in: Optional[int] = None, user_id: Optional[str] = None):
|
|
91
91
|
self.access_token = access_token
|
|
92
92
|
self.expires_in = expires_in
|
|
93
93
|
self.user_id = user_id
|
|
@@ -25,6 +25,7 @@ Environment Variables:
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
import json
|
|
28
|
+
from typing import Optional
|
|
28
29
|
import asyncio
|
|
29
30
|
import os
|
|
30
31
|
from .api import meta_api_tool
|
|
@@ -37,7 +38,7 @@ from .pipeboard_auth import pipeboard_auth_manager
|
|
|
37
38
|
ENABLE_LOGIN_LINK = not bool(os.environ.get("META_ADS_DISABLE_LOGIN_LINK", ""))
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
async def get_login_link(access_token: str = None) -> str:
|
|
41
|
+
async def get_login_link(access_token: Optional[str] = None) -> str:
|
|
41
42
|
"""
|
|
42
43
|
Get a clickable login link for Meta Ads authentication.
|
|
43
44
|
|
|
@@ -9,7 +9,7 @@ from .server import mcp_server
|
|
|
9
9
|
|
|
10
10
|
@mcp_server.tool()
|
|
11
11
|
@meta_api_tool
|
|
12
|
-
async def get_campaigns(
|
|
12
|
+
async def get_campaigns(account_id: str, access_token: Optional[str] = None, limit: int = 10, status_filter: str = "", after: str = "") -> str:
|
|
13
13
|
"""
|
|
14
14
|
Get campaigns for a Meta Ads account with optional filtering.
|
|
15
15
|
|
|
@@ -20,23 +20,17 @@ async def get_campaigns(access_token: str = None, account_id: str = None, limit:
|
|
|
20
20
|
in the API call (currently not exposed by this tool's parameters).
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
24
23
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
24
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
25
25
|
limit: Maximum number of campaigns to return (default: 10)
|
|
26
26
|
status_filter: Filter by effective status (e.g., 'ACTIVE', 'PAUSED', 'ARCHIVED').
|
|
27
27
|
Maps to the 'effective_status' API parameter, which expects an array
|
|
28
28
|
(this function handles the required JSON formatting). Leave empty for all statuses.
|
|
29
29
|
after: Pagination cursor to get the next set of results
|
|
30
30
|
"""
|
|
31
|
-
#
|
|
31
|
+
# Require explicit account_id
|
|
32
32
|
if not account_id:
|
|
33
|
-
|
|
34
|
-
accounts_data = json.loads(accounts_json)
|
|
35
|
-
|
|
36
|
-
if "data" in accounts_data and accounts_data["data"]:
|
|
37
|
-
account_id = accounts_data["data"][0]["id"]
|
|
38
|
-
else:
|
|
39
|
-
return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
|
|
33
|
+
return json.dumps({"error": "No account ID specified"}, indent=2)
|
|
40
34
|
|
|
41
35
|
endpoint = f"{account_id}/campaigns"
|
|
42
36
|
params = {
|
|
@@ -58,7 +52,7 @@ async def get_campaigns(access_token: str = None, account_id: str = None, limit:
|
|
|
58
52
|
|
|
59
53
|
@mcp_server.tool()
|
|
60
54
|
@meta_api_tool
|
|
61
|
-
async def get_campaign_details(
|
|
55
|
+
async def get_campaign_details(campaign_id: str, access_token: Optional[str] = None) -> str:
|
|
62
56
|
"""
|
|
63
57
|
Get detailed information about a specific campaign.
|
|
64
58
|
|
|
@@ -67,8 +61,8 @@ async def get_campaign_details(access_token: str = None, campaign_id: str = None
|
|
|
67
61
|
that could be added to the 'fields' parameter in the code if needed.
|
|
68
62
|
|
|
69
63
|
Args:
|
|
70
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
71
64
|
campaign_id: Meta Ads campaign ID
|
|
65
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
72
66
|
"""
|
|
73
67
|
if not campaign_id:
|
|
74
68
|
return json.dumps({"error": "No campaign ID provided"}, indent=2)
|
|
@@ -86,19 +80,19 @@ async def get_campaign_details(access_token: str = None, campaign_id: str = None
|
|
|
86
80
|
@mcp_server.tool()
|
|
87
81
|
@meta_api_tool
|
|
88
82
|
async def create_campaign(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
account_id: str,
|
|
84
|
+
name: str,
|
|
85
|
+
objective: str,
|
|
86
|
+
access_token: Optional[str] = None,
|
|
93
87
|
status: str = "PAUSED",
|
|
94
|
-
special_ad_categories: List[str] = None,
|
|
95
|
-
daily_budget = None,
|
|
96
|
-
lifetime_budget = None,
|
|
97
|
-
buying_type: str = None,
|
|
98
|
-
bid_strategy: str = None,
|
|
99
|
-
bid_cap = None,
|
|
100
|
-
spend_cap = None,
|
|
101
|
-
campaign_budget_optimization: bool = None,
|
|
88
|
+
special_ad_categories: Optional[List[str]] = None,
|
|
89
|
+
daily_budget: Optional[int] = None,
|
|
90
|
+
lifetime_budget: Optional[int] = None,
|
|
91
|
+
buying_type: Optional[str] = None,
|
|
92
|
+
bid_strategy: Optional[str] = None,
|
|
93
|
+
bid_cap: Optional[int] = None,
|
|
94
|
+
spend_cap: Optional[int] = None,
|
|
95
|
+
campaign_budget_optimization: Optional[bool] = None,
|
|
102
96
|
ab_test_control_setups: Optional[List[Dict[str, Any]]] = None,
|
|
103
97
|
use_adset_level_budgets: bool = False
|
|
104
98
|
) -> str:
|
|
@@ -106,7 +100,6 @@ async def create_campaign(
|
|
|
106
100
|
Create a new campaign in a Meta Ads account.
|
|
107
101
|
|
|
108
102
|
Args:
|
|
109
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
110
103
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
111
104
|
name: Campaign name
|
|
112
105
|
objective: Campaign objective (ODAX, outcome-based). Must be one of:
|
|
@@ -116,6 +109,7 @@ async def create_campaign(
|
|
|
116
109
|
CONVERSIONS, APP_INSTALLS, etc. are not valid for new
|
|
117
110
|
campaigns and will cause a 400 error. Use the outcome-based
|
|
118
111
|
values above (e.g., BRAND_AWARENESS → OUTCOME_AWARENESS).
|
|
112
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
119
113
|
status: Initial campaign status (default: PAUSED)
|
|
120
114
|
special_ad_categories: List of special ad categories if applicable
|
|
121
115
|
daily_budget: Daily budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False)
|
|
@@ -204,26 +198,26 @@ async def create_campaign(
|
|
|
204
198
|
@mcp_server.tool()
|
|
205
199
|
@meta_api_tool
|
|
206
200
|
async def update_campaign(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
name: str = None,
|
|
210
|
-
status: str = None,
|
|
211
|
-
special_ad_categories: List[str] = None,
|
|
212
|
-
daily_budget = None,
|
|
213
|
-
lifetime_budget = None,
|
|
214
|
-
bid_strategy: str = None,
|
|
215
|
-
bid_cap = None,
|
|
216
|
-
spend_cap = None,
|
|
217
|
-
campaign_budget_optimization: bool = None,
|
|
218
|
-
objective: str = None, # Add objective if it's updatable
|
|
219
|
-
use_adset_level_budgets: bool = None, # Add other updatable fields as needed based on API docs
|
|
201
|
+
campaign_id: str,
|
|
202
|
+
access_token: Optional[str] = None,
|
|
203
|
+
name: Optional[str] = None,
|
|
204
|
+
status: Optional[str] = None,
|
|
205
|
+
special_ad_categories: Optional[List[str]] = None,
|
|
206
|
+
daily_budget: Optional[int] = None,
|
|
207
|
+
lifetime_budget: Optional[int] = None,
|
|
208
|
+
bid_strategy: Optional[str] = None,
|
|
209
|
+
bid_cap: Optional[int] = None,
|
|
210
|
+
spend_cap: Optional[int] = None,
|
|
211
|
+
campaign_budget_optimization: Optional[bool] = None,
|
|
212
|
+
objective: Optional[str] = None, # Add objective if it's updatable
|
|
213
|
+
use_adset_level_budgets: Optional[bool] = None, # Add other updatable fields as needed based on API docs
|
|
220
214
|
) -> str:
|
|
221
215
|
"""
|
|
222
216
|
Update an existing campaign in a Meta Ads account.
|
|
223
217
|
|
|
224
218
|
Args:
|
|
219
|
+
campaign_id: Meta Ads campaign ID
|
|
225
220
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
226
|
-
campaign_id: Meta Ads campaign ID (required)
|
|
227
221
|
name: New campaign name
|
|
228
222
|
status: New campaign status (e.g., 'ACTIVE', 'PAUSED')
|
|
229
223
|
special_ad_categories: List of special ad categories if applicable
|
|
@@ -18,7 +18,7 @@ if ENABLE_DUPLICATION:
|
|
|
18
18
|
@meta_api_tool
|
|
19
19
|
async def duplicate_campaign(
|
|
20
20
|
campaign_id: str,
|
|
21
|
-
access_token: str = None,
|
|
21
|
+
access_token: Optional[str] = None,
|
|
22
22
|
name_suffix: Optional[str] = " - Copy",
|
|
23
23
|
include_ad_sets: bool = True,
|
|
24
24
|
include_ads: bool = True,
|
|
@@ -61,7 +61,7 @@ if ENABLE_DUPLICATION:
|
|
|
61
61
|
@meta_api_tool
|
|
62
62
|
async def duplicate_adset(
|
|
63
63
|
adset_id: str,
|
|
64
|
-
access_token: str = None,
|
|
64
|
+
access_token: Optional[str] = None,
|
|
65
65
|
target_campaign_id: Optional[str] = None,
|
|
66
66
|
name_suffix: Optional[str] = " - Copy",
|
|
67
67
|
include_ads: bool = True,
|
|
@@ -104,7 +104,7 @@ if ENABLE_DUPLICATION:
|
|
|
104
104
|
@meta_api_tool
|
|
105
105
|
async def duplicate_ad(
|
|
106
106
|
ad_id: str,
|
|
107
|
-
access_token: str = None,
|
|
107
|
+
access_token: Optional[str] = None,
|
|
108
108
|
target_adset_id: Optional[str] = None,
|
|
109
109
|
name_suffix: Optional[str] = " - Copy",
|
|
110
110
|
duplicate_creative: bool = True,
|
|
@@ -141,7 +141,7 @@ if ENABLE_DUPLICATION:
|
|
|
141
141
|
@meta_api_tool
|
|
142
142
|
async def duplicate_creative(
|
|
143
143
|
creative_id: str,
|
|
144
|
-
access_token: str = None,
|
|
144
|
+
access_token: Optional[str] = None,
|
|
145
145
|
name_suffix: Optional[str] = " - Copy",
|
|
146
146
|
new_primary_text: Optional[str] = None,
|
|
147
147
|
new_headline: Optional[str] = None,
|
|
@@ -11,15 +11,15 @@ import datetime
|
|
|
11
11
|
|
|
12
12
|
@mcp_server.tool()
|
|
13
13
|
@meta_api_tool
|
|
14
|
-
async def get_insights(
|
|
14
|
+
async def get_insights(object_id: str, access_token: Optional[str] = None,
|
|
15
15
|
time_range: Union[str, Dict[str, str]] = "maximum", breakdown: str = "",
|
|
16
16
|
level: str = "ad") -> str:
|
|
17
17
|
"""
|
|
18
18
|
Get performance insights for a campaign, ad set, ad or account.
|
|
19
19
|
|
|
20
20
|
Args:
|
|
21
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
22
21
|
object_id: ID of the campaign, ad set, ad or account
|
|
22
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
23
23
|
time_range: Either a preset time range string or a dictionary with "since" and "until" dates in YYYY-MM-DD format
|
|
24
24
|
Preset options: today, yesterday, this_month, last_month, this_quarter, maximum, data_maximum,
|
|
25
25
|
last_3d, last_7d, last_14d, last_28d, last_30d, last_90d, last_week_mon_sun,
|
|
@@ -310,8 +310,8 @@ _data_manager = MetaAdsDataManager()
|
|
|
310
310
|
@mcp_server.tool()
|
|
311
311
|
@meta_api_tool
|
|
312
312
|
async def search(
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
query: str,
|
|
314
|
+
access_token: Optional[str] = None
|
|
315
315
|
) -> str:
|
|
316
316
|
"""
|
|
317
317
|
Search through Meta Ads data and return matching record IDs.
|
|
@@ -321,8 +321,8 @@ async def search(
|
|
|
321
321
|
based on the provided query.
|
|
322
322
|
|
|
323
323
|
Args:
|
|
324
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
325
324
|
query: Search query string to find relevant Meta Ads records
|
|
325
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
326
326
|
|
|
327
327
|
Returns:
|
|
328
328
|
JSON response with list of matching record IDs
|
|
@@ -367,7 +367,7 @@ async def search(
|
|
|
367
367
|
|
|
368
368
|
@mcp_server.tool()
|
|
369
369
|
async def fetch(
|
|
370
|
-
id: str
|
|
370
|
+
id: str
|
|
371
371
|
) -> str:
|
|
372
372
|
"""
|
|
373
373
|
Fetch complete record data by ID.
|
|
@@ -21,7 +21,7 @@ logger.info(f"Pipeboard API base URL: {PIPEBOARD_API_BASE}")
|
|
|
21
21
|
|
|
22
22
|
class TokenInfo:
|
|
23
23
|
"""Stores token information including expiration"""
|
|
24
|
-
def __init__(self, access_token: str, expires_at: str = None, token_type: str = None):
|
|
24
|
+
def __init__(self, access_token: str, expires_at: Optional[str] = None, token_type: Optional[str] = None):
|
|
25
25
|
self.access_token = access_token
|
|
26
26
|
self.expires_at = expires_at
|
|
27
27
|
self.token_type = token_type
|
|
@@ -13,8 +13,8 @@ ENABLE_REPORT_GENERATION = bool(os.environ.get("META_ADS_ENABLE_REPORTS", ""))
|
|
|
13
13
|
if ENABLE_REPORT_GENERATION:
|
|
14
14
|
@mcp_server.tool()
|
|
15
15
|
async def generate_report(
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
account_id: str,
|
|
17
|
+
access_token: Optional[str] = None,
|
|
18
18
|
report_type: str = "account",
|
|
19
19
|
time_range: str = "last_30d",
|
|
20
20
|
campaign_ids: Optional[List[str]] = None,
|
|
@@ -30,8 +30,8 @@ if ENABLE_REPORT_GENERATION:
|
|
|
30
30
|
**This is a premium feature available with Pipeboard Pro.**
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
34
33
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
34
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
35
35
|
report_type: Type of report to generate (account, campaign, comparison)
|
|
36
36
|
time_range: Time period for the report (e.g., 'last_30d', 'last_7d', 'this_month')
|
|
37
37
|
campaign_ids: Specific campaign IDs (required for campaign/comparison reports)
|
|
@@ -8,13 +8,13 @@ from .server import mcp_server
|
|
|
8
8
|
|
|
9
9
|
@mcp_server.tool()
|
|
10
10
|
@meta_api_tool
|
|
11
|
-
async def search_interests(
|
|
11
|
+
async def search_interests(query: str, access_token: Optional[str] = None, limit: int = 25) -> str:
|
|
12
12
|
"""
|
|
13
13
|
Search for interest targeting options by keyword.
|
|
14
14
|
|
|
15
15
|
Args:
|
|
16
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
17
16
|
query: Search term for interests (e.g., "baseball", "cooking", "travel")
|
|
17
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
18
18
|
limit: Maximum number of results to return (default: 25)
|
|
19
19
|
|
|
20
20
|
Returns:
|
|
@@ -37,13 +37,13 @@ async def search_interests(access_token: str = None, query: str = None, limit: i
|
|
|
37
37
|
|
|
38
38
|
@mcp_server.tool()
|
|
39
39
|
@meta_api_tool
|
|
40
|
-
async def get_interest_suggestions(
|
|
40
|
+
async def get_interest_suggestions(interest_list: List[str], access_token: Optional[str] = None, limit: int = 25) -> str:
|
|
41
41
|
"""
|
|
42
42
|
Get interest suggestions based on existing interests.
|
|
43
43
|
|
|
44
44
|
Args:
|
|
45
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
46
45
|
interest_list: List of interest names to get suggestions for (e.g., ["Basketball", "Soccer"])
|
|
46
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
47
47
|
limit: Maximum number of suggestions to return (default: 25)
|
|
48
48
|
|
|
49
49
|
Returns:
|
|
@@ -67,13 +67,13 @@ async def get_interest_suggestions(access_token: str = None, interest_list: List
|
|
|
67
67
|
@mcp_server.tool()
|
|
68
68
|
@meta_api_tool
|
|
69
69
|
async def estimate_audience_size(
|
|
70
|
-
access_token: str = None,
|
|
71
|
-
account_id: str = None,
|
|
72
|
-
targeting: Dict[str, Any] = None,
|
|
70
|
+
access_token: Optional[str] = None,
|
|
71
|
+
account_id: Optional[str] = None,
|
|
72
|
+
targeting: Optional[Dict[str, Any]] = None,
|
|
73
73
|
optimization_goal: str = "REACH",
|
|
74
74
|
# Backwards compatibility for simple interest validation
|
|
75
|
-
interest_list: List[str] = None,
|
|
76
|
-
interest_fbid_list: List[str] = None
|
|
75
|
+
interest_list: Optional[List[str]] = None,
|
|
76
|
+
interest_fbid_list: Optional[List[str]] = None
|
|
77
77
|
) -> str:
|
|
78
78
|
"""
|
|
79
79
|
Estimate audience size for targeting specifications using Meta's delivery_estimate API.
|
|
@@ -247,7 +247,7 @@ async def estimate_audience_size(
|
|
|
247
247
|
|
|
248
248
|
@mcp_server.tool()
|
|
249
249
|
@meta_api_tool
|
|
250
|
-
async def search_behaviors(access_token: str = None, limit: int = 50) -> str:
|
|
250
|
+
async def search_behaviors(access_token: Optional[str] = None, limit: int = 50) -> str:
|
|
251
251
|
"""
|
|
252
252
|
Get all available behavior targeting options.
|
|
253
253
|
|
|
@@ -272,7 +272,7 @@ async def search_behaviors(access_token: str = None, limit: int = 50) -> str:
|
|
|
272
272
|
|
|
273
273
|
@mcp_server.tool()
|
|
274
274
|
@meta_api_tool
|
|
275
|
-
async def search_demographics(access_token: str = None, demographic_class: str = "demographics", limit: int = 50) -> str:
|
|
275
|
+
async def search_demographics(access_token: Optional[str] = None, demographic_class: str = "demographics", limit: int = 50) -> str:
|
|
276
276
|
"""
|
|
277
277
|
Get demographic targeting options.
|
|
278
278
|
|
|
@@ -299,14 +299,14 @@ async def search_demographics(access_token: str = None, demographic_class: str =
|
|
|
299
299
|
|
|
300
300
|
@mcp_server.tool()
|
|
301
301
|
@meta_api_tool
|
|
302
|
-
async def search_geo_locations(
|
|
303
|
-
location_types: List[str] = None, limit: int = 25) -> str:
|
|
302
|
+
async def search_geo_locations(query: str, access_token: Optional[str] = None,
|
|
303
|
+
location_types: Optional[List[str]] = None, limit: int = 25) -> str:
|
|
304
304
|
"""
|
|
305
305
|
Search for geographic targeting locations.
|
|
306
306
|
|
|
307
307
|
Args:
|
|
308
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
309
308
|
query: Search term for locations (e.g., "New York", "California", "Japan")
|
|
309
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
310
310
|
location_types: Types of locations to search. Options: ['country', 'region', 'city', 'zip',
|
|
311
311
|
'geo_market', 'electoral_district']. If not specified, searches all types.
|
|
312
312
|
limit: Maximum number of results to return (default: 25)
|
|
@@ -104,7 +104,7 @@ class TestDSABeneficiaryDetection:
|
|
|
104
104
|
mock_auth.return_value = "test_access_token"
|
|
105
105
|
|
|
106
106
|
# Test without account_id parameter
|
|
107
|
-
result = await get_account_info()
|
|
107
|
+
result = await get_account_info(account_id=None)
|
|
108
108
|
|
|
109
109
|
# Handle new return format (dictionary instead of JSON string)
|
|
110
110
|
if isinstance(result, dict):
|
|
@@ -402,7 +402,7 @@ class TestDSAIntegration:
|
|
|
402
402
|
mock_auth.return_value = "test_access_token"
|
|
403
403
|
|
|
404
404
|
# Test without account_id parameter
|
|
405
|
-
result = await get_account_info()
|
|
405
|
+
result = await get_account_info(account_id=None)
|
|
406
406
|
|
|
407
407
|
# Handle new return format (dictionary instead of JSON string)
|
|
408
408
|
if isinstance(result, dict):
|
|
@@ -502,9 +502,10 @@ class TestDynamicCreatives:
|
|
|
502
502
|
assert creative_data["dynamic_creative_spec"]["description_optimization"] is False
|
|
503
503
|
|
|
504
504
|
async def test_update_ad_creative_no_creative_id(self):
|
|
505
|
-
"""Test update_ad_creative with
|
|
505
|
+
"""Test update_ad_creative with empty creative_id provided."""
|
|
506
506
|
|
|
507
507
|
result = await update_ad_creative(
|
|
508
|
+
creative_id="", # Now provide the required parameter but with empty value
|
|
508
509
|
access_token="test_token",
|
|
509
510
|
headlines=["New Headline"]
|
|
510
511
|
)
|
|
@@ -260,17 +260,14 @@ class TestInsightsActionsAndValues:
|
|
|
260
260
|
level="campaign"
|
|
261
261
|
)
|
|
262
262
|
|
|
263
|
-
# Parse the result
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
# The error response is wrapped in a 'data' field
|
|
267
|
-
if 'data' in result_data:
|
|
268
|
-
error_data = json.loads(result_data['data'])
|
|
269
|
-
assert 'error' in error_data
|
|
270
|
-
assert 'No object ID provided' in error_data['error']
|
|
263
|
+
# Parse the result. The decorator returns a dict error for missing required args
|
|
264
|
+
if isinstance(result, dict):
|
|
265
|
+
result_data = result
|
|
271
266
|
else:
|
|
272
|
-
|
|
273
|
-
|
|
267
|
+
result_data = json.loads(result)
|
|
268
|
+
|
|
269
|
+
assert 'error' in result_data
|
|
270
|
+
assert "missing 1 required positional argument: 'object_id'" in result_data['error']
|
|
274
271
|
|
|
275
272
|
# Verify API was not called
|
|
276
273
|
mock_api_request.assert_not_called()
|
|
@@ -64,8 +64,11 @@ class TestSearchInterests:
|
|
|
64
64
|
|
|
65
65
|
@pytest.mark.asyncio
|
|
66
66
|
async def test_search_interests_no_query(self):
|
|
67
|
-
"""Test search_interests with
|
|
68
|
-
result = await search_interests(
|
|
67
|
+
"""Test search_interests with empty query parameter"""
|
|
68
|
+
result = await search_interests(
|
|
69
|
+
query="", # Now provide the required parameter but with empty value
|
|
70
|
+
access_token="test_token"
|
|
71
|
+
)
|
|
69
72
|
|
|
70
73
|
result_data = json.loads(result)
|
|
71
74
|
# The @meta_api_tool decorator wraps errors in a 'data' field
|
|
@@ -155,8 +158,11 @@ class TestGetInterestSuggestions:
|
|
|
155
158
|
|
|
156
159
|
@pytest.mark.asyncio
|
|
157
160
|
async def test_get_interest_suggestions_no_list(self):
|
|
158
|
-
"""Test get_interest_suggestions with
|
|
159
|
-
result = await get_interest_suggestions(
|
|
161
|
+
"""Test get_interest_suggestions with empty interest list"""
|
|
162
|
+
result = await get_interest_suggestions(
|
|
163
|
+
interest_list=[], # Now provide the required parameter but with empty value
|
|
164
|
+
access_token="test_token"
|
|
165
|
+
)
|
|
160
166
|
|
|
161
167
|
result_data = json.loads(result)
|
|
162
168
|
# The @meta_api_tool decorator wraps errors in a 'data' field
|
|
@@ -400,8 +406,11 @@ class TestSearchGeoLocations:
|
|
|
400
406
|
|
|
401
407
|
@pytest.mark.asyncio
|
|
402
408
|
async def test_search_geo_locations_no_query(self):
|
|
403
|
-
"""Test search_geo_locations with
|
|
404
|
-
result = await search_geo_locations(
|
|
409
|
+
"""Test search_geo_locations with empty query"""
|
|
410
|
+
result = await search_geo_locations(
|
|
411
|
+
query="", # Now provide the required parameter but with empty value
|
|
412
|
+
access_token="test_token"
|
|
413
|
+
)
|
|
405
414
|
|
|
406
415
|
result_data = json.loads(result)
|
|
407
416
|
# The @meta_api_tool decorator wraps errors in a 'data' field
|
|
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
|