meta-ads-mcp 0.10.7__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.7 → meta_ads_mcp-0.11.0}/PKG-INFO +31 -2
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/README.md +30 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/__init__.py +2 -3
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/accounts.py +4 -4
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/ads.py +109 -67
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/ads_library.py +5 -5
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/adsets.py +28 -34
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/api.py +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/auth.py +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/authentication.py +2 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/budget_schedules.py +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/campaigns.py +40 -40
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/duplication.py +4 -4
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/insights.py +2 -2
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/openai_deep_research.py +4 -4
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/pipeboard_auth.py +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/reports.py +3 -3
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/targeting.py +14 -14
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/pyproject.toml +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_dsa_beneficiary.py +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_dsa_integration.py +1 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_dynamic_creatives.py +2 -1
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_insights_actions_and_values.py +7 -10
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_targeting.py +15 -6
- meta_ads_mcp-0.11.0/tests/test_upload_ad_image.py +134 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/.github/workflows/publish.yml +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/.github/workflows/test.yml +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/.gitignore +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/CUSTOM_META_APP.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/Dockerfile +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/LICENSE +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/LOCAL_INSTALLATION.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/META_API_NOTES.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/RELEASE.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/STREAMABLE_HTTP_SETUP.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/examples/README.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/examples/example_http_client.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/future_improvements.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/images/meta-ads-example.png +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_auth.sh +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/__main__.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/__init__.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/callback_server.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/http_auth_integration.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/resources.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/server.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/meta_ads_mcp/core/utils.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/requirements.txt +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/setup.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/smithery.yaml +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/README.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/README_REGRESSION_TESTS.md +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/__init__.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/conftest.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/e2e_account_info_search_issue.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_account_info_access_fix.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_account_search.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_budget_update.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_budget_update_e2e.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_duplication.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_duplication_regression.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_estimate_audience_size.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_estimate_audience_size_e2e.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_get_account_pages.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_get_ad_creatives_fix.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_get_ad_image_quality_improvements.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_get_ad_image_regression.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_http_transport.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_integration_openai_mcp.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_mobile_app_adset_creation.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_mobile_app_adset_issue.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_openai.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_openai_mcp_deep_research.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_page_discovery.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_page_discovery_integration.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_targeting_search_e2e.py +0 -0
- {meta_ads_mcp-0.10.7 → meta_ads_mcp-0.11.0}/tests/test_update_ad_creative_id.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
|
|
@@ -176,7 +176,22 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
176
176
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
177
177
|
- `account_id`: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
178
178
|
- `name`: Campaign name
|
|
179
|
-
- `objective`: Campaign objective (
|
|
179
|
+
- `objective`: Campaign objective (ODAX, outcome-based). Must be one of:
|
|
180
|
+
- `OUTCOME_AWARENESS`
|
|
181
|
+
- `OUTCOME_TRAFFIC`
|
|
182
|
+
- `OUTCOME_ENGAGEMENT`
|
|
183
|
+
- `OUTCOME_LEADS`
|
|
184
|
+
- `OUTCOME_SALES`
|
|
185
|
+
- `OUTCOME_APP_PROMOTION`
|
|
186
|
+
|
|
187
|
+
Note: Legacy objectives such as `BRAND_AWARENESS`, `LINK_CLICKS`, `CONVERSIONS`, `APP_INSTALLS`, etc. are no longer valid for new campaigns and will cause a 400 error. Use the outcome-based values above. Common mappings:
|
|
188
|
+
- `BRAND_AWARENESS` → `OUTCOME_AWARENESS`
|
|
189
|
+
- `REACH` → `OUTCOME_AWARENESS`
|
|
190
|
+
- `LINK_CLICKS`, `TRAFFIC` → `OUTCOME_TRAFFIC`
|
|
191
|
+
- `POST_ENGAGEMENT`, `PAGE_LIKES`, `EVENT_RESPONSES`, `VIDEO_VIEWS` → `OUTCOME_ENGAGEMENT`
|
|
192
|
+
- `LEAD_GENERATION` → `OUTCOME_LEADS`
|
|
193
|
+
- `CONVERSIONS`, `CATALOG_SALES`, `MESSAGES` (sales-focused flows) → `OUTCOME_SALES`
|
|
194
|
+
- `APP_INSTALLS` → `OUTCOME_APP_PROMOTION`
|
|
180
195
|
- `status`: Initial campaign status (default: PAUSED)
|
|
181
196
|
- `special_ad_categories`: List of special ad categories if applicable
|
|
182
197
|
- `daily_budget`: Daily budget in account currency (in cents)
|
|
@@ -184,6 +199,20 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
184
199
|
- `bid_strategy`: Bid strategy. Must be one of: `LOWEST_COST_WITHOUT_CAP`, `LOWEST_COST_WITH_BID_CAP`, `COST_CAP`, `LOWEST_COST_WITH_MIN_ROAS`.
|
|
185
200
|
- Returns: Confirmation with new campaign details
|
|
186
201
|
|
|
202
|
+
- Example:
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"name": "2025 - Bedroom Furniture - Awareness",
|
|
206
|
+
"account_id": "act_123456789012345",
|
|
207
|
+
"objective": "OUTCOME_AWARENESS",
|
|
208
|
+
"special_ad_categories": [],
|
|
209
|
+
"status": "PAUSED",
|
|
210
|
+
"buying_type": "AUCTION",
|
|
211
|
+
"bid_strategy": "LOWEST_COST_WITHOUT_CAP",
|
|
212
|
+
"daily_budget": 10000
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
187
216
|
7. `mcp_meta_ads_get_adsets`
|
|
188
217
|
- Get ad sets for a Meta Ads account with optional filtering by campaign
|
|
189
218
|
- Inputs:
|
|
@@ -151,7 +151,22 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
151
151
|
- `access_token` (optional): Meta API access token (will use cached token if not provided)
|
|
152
152
|
- `account_id`: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
153
153
|
- `name`: Campaign name
|
|
154
|
-
- `objective`: Campaign objective (
|
|
154
|
+
- `objective`: Campaign objective (ODAX, outcome-based). Must be one of:
|
|
155
|
+
- `OUTCOME_AWARENESS`
|
|
156
|
+
- `OUTCOME_TRAFFIC`
|
|
157
|
+
- `OUTCOME_ENGAGEMENT`
|
|
158
|
+
- `OUTCOME_LEADS`
|
|
159
|
+
- `OUTCOME_SALES`
|
|
160
|
+
- `OUTCOME_APP_PROMOTION`
|
|
161
|
+
|
|
162
|
+
Note: Legacy objectives such as `BRAND_AWARENESS`, `LINK_CLICKS`, `CONVERSIONS`, `APP_INSTALLS`, etc. are no longer valid for new campaigns and will cause a 400 error. Use the outcome-based values above. Common mappings:
|
|
163
|
+
- `BRAND_AWARENESS` → `OUTCOME_AWARENESS`
|
|
164
|
+
- `REACH` → `OUTCOME_AWARENESS`
|
|
165
|
+
- `LINK_CLICKS`, `TRAFFIC` → `OUTCOME_TRAFFIC`
|
|
166
|
+
- `POST_ENGAGEMENT`, `PAGE_LIKES`, `EVENT_RESPONSES`, `VIDEO_VIEWS` → `OUTCOME_ENGAGEMENT`
|
|
167
|
+
- `LEAD_GENERATION` → `OUTCOME_LEADS`
|
|
168
|
+
- `CONVERSIONS`, `CATALOG_SALES`, `MESSAGES` (sales-focused flows) → `OUTCOME_SALES`
|
|
169
|
+
- `APP_INSTALLS` → `OUTCOME_APP_PROMOTION`
|
|
155
170
|
- `status`: Initial campaign status (default: PAUSED)
|
|
156
171
|
- `special_ad_categories`: List of special ad categories if applicable
|
|
157
172
|
- `daily_budget`: Daily budget in account currency (in cents)
|
|
@@ -159,6 +174,20 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
159
174
|
- `bid_strategy`: Bid strategy. Must be one of: `LOWEST_COST_WITHOUT_CAP`, `LOWEST_COST_WITH_BID_CAP`, `COST_CAP`, `LOWEST_COST_WITH_MIN_ROAS`.
|
|
160
175
|
- Returns: Confirmation with new campaign details
|
|
161
176
|
|
|
177
|
+
- Example:
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"name": "2025 - Bedroom Furniture - Awareness",
|
|
181
|
+
"account_id": "act_123456789012345",
|
|
182
|
+
"objective": "OUTCOME_AWARENESS",
|
|
183
|
+
"special_ad_categories": [],
|
|
184
|
+
"status": "PAUSED",
|
|
185
|
+
"buying_type": "AUCTION",
|
|
186
|
+
"bid_strategy": "LOWEST_COST_WITHOUT_CAP",
|
|
187
|
+
"daily_budget": 10000
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
162
191
|
7. `mcp_meta_ads_get_adsets`
|
|
163
192
|
- Get ad sets for a Meta Ads account with optional filtering by campaign
|
|
164
193
|
- Inputs:
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Meta Ads MCP - Python Package
|
|
3
3
|
|
|
4
|
-
This package provides a Meta Ads
|
|
5
|
-
with the Claude LLM.
|
|
4
|
+
This package provides a Meta Ads MCP integration
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from meta_ads_mcp.core.server import main
|
|
9
8
|
|
|
10
|
-
__version__ = "0.
|
|
9
|
+
__version__ = "0.11.0"
|
|
11
10
|
|
|
12
11
|
__all__ = [
|
|
13
12
|
'get_ad_accounts',
|
|
@@ -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)
|
|
@@ -671,7 +665,55 @@ async def upload_ad_image(
|
|
|
671
665
|
print(f"Uploading image to Facebook Ad Account {account_id}")
|
|
672
666
|
data = await make_api_request(endpoint, access_token, params, method="POST")
|
|
673
667
|
|
|
674
|
-
|
|
668
|
+
# Normalize/structure the response for callers (e.g., to easily grab image_hash)
|
|
669
|
+
# Typical Graph API response shape:
|
|
670
|
+
# { "images": { "<hash>": { "hash": "<hash>", "url": "...", "width": ..., "height": ..., "name": "...", "status": 1 } } }
|
|
671
|
+
if isinstance(data, dict) and "images" in data and isinstance(data["images"], dict) and data["images"]:
|
|
672
|
+
images_dict = data["images"]
|
|
673
|
+
images_list = []
|
|
674
|
+
for hash_key, info in images_dict.items():
|
|
675
|
+
# Some responses may omit the nested hash, so ensure it's present
|
|
676
|
+
normalized = {
|
|
677
|
+
"hash": (info.get("hash") or hash_key),
|
|
678
|
+
"url": info.get("url"),
|
|
679
|
+
"width": info.get("width"),
|
|
680
|
+
"height": info.get("height"),
|
|
681
|
+
"name": info.get("name"),
|
|
682
|
+
}
|
|
683
|
+
# Drop null/None values
|
|
684
|
+
normalized = {k: v for k, v in normalized.items() if v is not None}
|
|
685
|
+
images_list.append(normalized)
|
|
686
|
+
|
|
687
|
+
# Sort deterministically by hash
|
|
688
|
+
images_list.sort(key=lambda i: i.get("hash", ""))
|
|
689
|
+
primary_hash = images_list[0].get("hash") if images_list else None
|
|
690
|
+
|
|
691
|
+
result = {
|
|
692
|
+
"success": True,
|
|
693
|
+
"account_id": account_id,
|
|
694
|
+
"name": final_name,
|
|
695
|
+
"image_hash": primary_hash,
|
|
696
|
+
"images_count": len(images_list),
|
|
697
|
+
"images": images_list
|
|
698
|
+
}
|
|
699
|
+
return json.dumps(result, indent=2)
|
|
700
|
+
|
|
701
|
+
# If the API returned an error-like structure, surface it consistently
|
|
702
|
+
if isinstance(data, dict) and "error" in data:
|
|
703
|
+
return json.dumps({
|
|
704
|
+
"error": "Failed to upload image",
|
|
705
|
+
"details": data.get("error"),
|
|
706
|
+
"account_id": account_id,
|
|
707
|
+
"name": final_name
|
|
708
|
+
}, indent=2)
|
|
709
|
+
|
|
710
|
+
# Fallback: return a wrapped raw response to avoid breaking callers
|
|
711
|
+
return json.dumps({
|
|
712
|
+
"success": True,
|
|
713
|
+
"account_id": account_id,
|
|
714
|
+
"name": final_name,
|
|
715
|
+
"raw_response": data
|
|
716
|
+
}, indent=2)
|
|
675
717
|
|
|
676
718
|
except Exception as e:
|
|
677
719
|
return json.dumps({
|
|
@@ -683,29 +725,29 @@ async def upload_ad_image(
|
|
|
683
725
|
@mcp_server.tool()
|
|
684
726
|
@meta_api_tool
|
|
685
727
|
async def create_ad_creative(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
page_id: str = None,
|
|
691
|
-
link_url: str = None,
|
|
692
|
-
message: str = None,
|
|
693
|
-
headline: str = None,
|
|
694
|
-
headlines: List[str] = None,
|
|
695
|
-
description: str = None,
|
|
696
|
-
descriptions: List[str] = None,
|
|
697
|
-
dynamic_creative_spec: Dict[str, Any] = None,
|
|
698
|
-
call_to_action_type: str = None,
|
|
699
|
-
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
|
|
700
742
|
) -> str:
|
|
701
743
|
"""
|
|
702
744
|
Create a new ad creative using an uploaded image hash.
|
|
703
745
|
|
|
704
746
|
Args:
|
|
705
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
706
747
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
707
|
-
name: Creative name
|
|
708
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
|
|
709
751
|
page_id: Facebook Page ID to be used for the ad
|
|
710
752
|
link_url: Destination URL for the ad
|
|
711
753
|
message: Ad copy/text
|
|
@@ -895,23 +937,23 @@ async def create_ad_creative(
|
|
|
895
937
|
@mcp_server.tool()
|
|
896
938
|
@meta_api_tool
|
|
897
939
|
async def update_ad_creative(
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
name: str = None,
|
|
901
|
-
message: str = None,
|
|
902
|
-
headline: str = None,
|
|
903
|
-
headlines: List[str] = None,
|
|
904
|
-
description: str = None,
|
|
905
|
-
descriptions: List[str] = None,
|
|
906
|
-
dynamic_creative_spec: Dict[str, Any] = None,
|
|
907
|
-
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
|
|
908
950
|
) -> str:
|
|
909
951
|
"""
|
|
910
952
|
Update an existing ad creative with new content or settings.
|
|
911
953
|
|
|
912
954
|
Args:
|
|
913
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
914
955
|
creative_id: Meta Ads creative ID to update
|
|
956
|
+
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
915
957
|
name: New creative name
|
|
916
958
|
message: New ad copy/text
|
|
917
959
|
headline: Single headline for simple ads (cannot be used with headlines)
|
|
@@ -1204,13 +1246,13 @@ async def _search_pages_by_name_core(access_token: str, account_id: str, search_
|
|
|
1204
1246
|
|
|
1205
1247
|
@mcp_server.tool()
|
|
1206
1248
|
@meta_api_tool
|
|
1207
|
-
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:
|
|
1208
1250
|
"""
|
|
1209
1251
|
Search for pages by name within an account.
|
|
1210
1252
|
|
|
1211
1253
|
Args:
|
|
1212
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
1213
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)
|
|
1214
1256
|
search_term: Search term to find pages by name (optional - returns all pages if not provided)
|
|
1215
1257
|
|
|
1216
1258
|
Returns:
|
|
@@ -1227,13 +1269,13 @@ async def search_pages_by_name(access_token: str = None, account_id: str = None,
|
|
|
1227
1269
|
|
|
1228
1270
|
@mcp_server.tool()
|
|
1229
1271
|
@meta_api_tool
|
|
1230
|
-
async def get_account_pages(
|
|
1272
|
+
async def get_account_pages(account_id: str, access_token: Optional[str] = None) -> str:
|
|
1231
1273
|
"""
|
|
1232
1274
|
Get pages associated with a Meta Ads account.
|
|
1233
1275
|
|
|
1234
1276
|
Args:
|
|
1235
|
-
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
1236
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)
|
|
1237
1279
|
|
|
1238
1280
|
Returns:
|
|
1239
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
|
|