meta-ads-mcp 0.10.0__tar.gz → 0.10.1__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.0/README.md → meta_ads_mcp-0.10.1/PKG-INFO +47 -0
- meta_ads_mcp-0.10.0/PKG-INFO → meta_ads_mcp-0.10.1/README.md +22 -25
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/__init__.py +1 -1
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/targeting.py +65 -21
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/pyproject.toml +2 -2
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_estimate_audience_size.py +15 -25
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_estimate_audience_size_e2e.py +1 -1
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/.github/workflows/publish.yml +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/.github/workflows/test.yml +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/.gitignore +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/CUSTOM_META_APP.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/Dockerfile +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/LICENSE +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/LOCAL_INSTALLATION.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/META_API_NOTES.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/RELEASE.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/STREAMABLE_HTTP_SETUP.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/examples/README.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/examples/example_http_client.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/future_improvements.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/images/meta-ads-example.png +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_auth.sh +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/__main__.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/__init__.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/accounts.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/ads.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/ads_library.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/adsets.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/api.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/auth.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/authentication.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/budget_schedules.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/callback_server.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/campaigns.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/duplication.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/http_auth_integration.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/insights.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/openai_deep_research.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/reports.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/resources.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/server.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/meta_ads_mcp/core/utils.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/requirements.txt +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/setup.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/smithery.yaml +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/README.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/README_REGRESSION_TESTS.md +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/__init__.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/conftest.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/e2e_account_info_search_issue.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_account_info_access_fix.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_account_search.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_budget_update.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_budget_update_e2e.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_dsa_beneficiary.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_dsa_integration.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_duplication.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_duplication_regression.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_dynamic_creatives.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_get_account_pages.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_get_ad_creatives_fix.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_get_ad_image_quality_improvements.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_get_ad_image_regression.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_http_transport.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_insights_actions_and_values.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_integration_openai_mcp.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_mobile_app_adset_creation.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_mobile_app_adset_issue.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_openai.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_openai_mcp_deep_research.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_page_discovery.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_page_discovery_integration.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_targeting.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_targeting_search_e2e.py +0 -0
- {meta_ads_mcp-0.10.0 → meta_ads_mcp-0.10.1}/tests/test_update_ad_creative_id.py +0 -0
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meta-ads-mcp
|
|
3
|
+
Version: 0.10.1
|
|
4
|
+
Summary: Model Context Protocol (MCP) server for interacting with Meta Ads API
|
|
5
|
+
Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
|
|
7
|
+
Author-email: Yves Junqueira <yves.junqueira@gmail.com>
|
|
8
|
+
License: BUSL-1.1
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ads,api,claude,facebook,mcp,meta
|
|
11
|
+
Classifier: License :: Other/Proprietary License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: httpx>=0.26.0
|
|
16
|
+
Requires-Dist: mcp[cli]<=1.12.2,>=1.10.1
|
|
17
|
+
Requires-Dist: pathlib>=1.0.1
|
|
18
|
+
Requires-Dist: pillow>=10.0.0
|
|
19
|
+
Requires-Dist: pytest-asyncio>=1.0.0
|
|
20
|
+
Requires-Dist: pytest>=8.4.1
|
|
21
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
22
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
23
|
+
Requires-Dist: requests>=2.32.3
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
1
26
|
# Meta Ads MCP
|
|
2
27
|
|
|
3
28
|
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for interacting with Meta Ads API. This tool enables AI models to access, analyze, and manage Meta advertising campaigns through a standardized interface, allowing LLMs to retrieve performance data, visualize ad creatives, and provide strategic insights for Facebook, Instagram, and other Meta platforms.
|
|
@@ -18,6 +43,7 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for in
|
|
|
18
43
|
- [Features](#features)
|
|
19
44
|
- [Configuration](#configuration)
|
|
20
45
|
- [Available MCP Tools](#available-mcp-tools)
|
|
46
|
+
- [Licensing](#licensing)
|
|
21
47
|
- [Privacy and Security](#privacy-and-security)
|
|
22
48
|
- [Testing](#testing)
|
|
23
49
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -382,6 +408,27 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
382
408
|
- `query`: Search query string (e.g., "Injury Payouts pages", "active campaigns")
|
|
383
409
|
- Returns: List of matching record IDs in ChatGPT-compatible format
|
|
384
410
|
|
|
411
|
+
## Licensing
|
|
412
|
+
|
|
413
|
+
Meta Ads MCP is licensed under the [Business Source License 1.1](LICENSE). This means:
|
|
414
|
+
|
|
415
|
+
### ✅ **What you CAN do:**
|
|
416
|
+
- ✅ **Use for free** - Individual and business use at no cost
|
|
417
|
+
- ✅ **Modify and customize** - Edit the source code for your needs
|
|
418
|
+
- ✅ **Internal commercial use** - Deploy within your organization
|
|
419
|
+
- ✅ **Redistribute** - Share the software with others
|
|
420
|
+
- ✅ **Create derivative works** - Build upon the codebase
|
|
421
|
+
|
|
422
|
+
### ❌ **What you CANNOT do:**
|
|
423
|
+
- ❌ **Offer as competing SaaS** - Cannot offer this as a hosted service that competes with ARTELL SOLUÇÕES TECNOLÓGICAS LTDA's commercial offerings
|
|
424
|
+
|
|
425
|
+
### 🔄 **Future Open Source:**
|
|
426
|
+
- **Change Date**: January 1, 2029
|
|
427
|
+
- **After Change Date**: Automatically converts to Apache License 2.0 (fully open source)
|
|
428
|
+
- **No restrictions**: After the change date, you can use it for any purpose, including competing services
|
|
429
|
+
|
|
430
|
+
This licensing model ensures the software remains accessible while protecting the commercial interests of the original developers. For questions about commercial licensing or use cases, please contact us.
|
|
431
|
+
|
|
385
432
|
## Privacy and Security
|
|
386
433
|
|
|
387
434
|
Meta Ads MCP follows security best practices with secure token management and automatic authentication handling.
|
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: meta-ads-mcp
|
|
3
|
-
Version: 0.10.0
|
|
4
|
-
Summary: Model Context Protocol (MCP) plugin for interacting with Meta Ads API
|
|
5
|
-
Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
|
|
6
|
-
Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
|
|
7
|
-
Author-email: Yves Junqueira <yves.junqueira@gmail.com>
|
|
8
|
-
License: BUSL-1.1
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Keywords: ads,api,claude,facebook,mcp,meta
|
|
11
|
-
Classifier: License :: Other/Proprietary License
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Requires-Python: >=3.10
|
|
15
|
-
Requires-Dist: httpx>=0.26.0
|
|
16
|
-
Requires-Dist: mcp[cli]<=1.12.2,>=1.10.1
|
|
17
|
-
Requires-Dist: pathlib>=1.0.1
|
|
18
|
-
Requires-Dist: pillow>=10.0.0
|
|
19
|
-
Requires-Dist: pytest-asyncio>=1.0.0
|
|
20
|
-
Requires-Dist: pytest>=8.4.1
|
|
21
|
-
Requires-Dist: python-dateutil>=2.8.2
|
|
22
|
-
Requires-Dist: python-dotenv>=1.1.0
|
|
23
|
-
Requires-Dist: requests>=2.32.3
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
1
|
# Meta Ads MCP
|
|
27
2
|
|
|
28
3
|
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for interacting with Meta Ads API. This tool enables AI models to access, analyze, and manage Meta advertising campaigns through a standardized interface, allowing LLMs to retrieve performance data, visualize ad creatives, and provide strategic insights for Facebook, Instagram, and other Meta platforms.
|
|
@@ -43,6 +18,7 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for in
|
|
|
43
18
|
- [Features](#features)
|
|
44
19
|
- [Configuration](#configuration)
|
|
45
20
|
- [Available MCP Tools](#available-mcp-tools)
|
|
21
|
+
- [Licensing](#licensing)
|
|
46
22
|
- [Privacy and Security](#privacy-and-security)
|
|
47
23
|
- [Testing](#testing)
|
|
48
24
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -407,6 +383,27 @@ For local installation configuration, authentication options, and advanced techn
|
|
|
407
383
|
- `query`: Search query string (e.g., "Injury Payouts pages", "active campaigns")
|
|
408
384
|
- Returns: List of matching record IDs in ChatGPT-compatible format
|
|
409
385
|
|
|
386
|
+
## Licensing
|
|
387
|
+
|
|
388
|
+
Meta Ads MCP is licensed under the [Business Source License 1.1](LICENSE). This means:
|
|
389
|
+
|
|
390
|
+
### ✅ **What you CAN do:**
|
|
391
|
+
- ✅ **Use for free** - Individual and business use at no cost
|
|
392
|
+
- ✅ **Modify and customize** - Edit the source code for your needs
|
|
393
|
+
- ✅ **Internal commercial use** - Deploy within your organization
|
|
394
|
+
- ✅ **Redistribute** - Share the software with others
|
|
395
|
+
- ✅ **Create derivative works** - Build upon the codebase
|
|
396
|
+
|
|
397
|
+
### ❌ **What you CANNOT do:**
|
|
398
|
+
- ❌ **Offer as competing SaaS** - Cannot offer this as a hosted service that competes with ARTELL SOLUÇÕES TECNOLÓGICAS LTDA's commercial offerings
|
|
399
|
+
|
|
400
|
+
### 🔄 **Future Open Source:**
|
|
401
|
+
- **Change Date**: January 1, 2029
|
|
402
|
+
- **After Change Date**: Automatically converts to Apache License 2.0 (fully open source)
|
|
403
|
+
- **No restrictions**: After the change date, you can use it for any purpose, including competing services
|
|
404
|
+
|
|
405
|
+
This licensing model ensures the software remains accessible while protecting the commercial interests of the original developers. For questions about commercial licensing or use cases, please contact us.
|
|
406
|
+
|
|
410
407
|
## Privacy and Security
|
|
411
408
|
|
|
412
409
|
Meta Ads MCP follows security best practices with secure token management and automatic authentication handling.
|
|
@@ -147,27 +147,16 @@ async def estimate_audience_size(
|
|
|
147
147
|
}
|
|
148
148
|
}, indent=2)
|
|
149
149
|
|
|
150
|
-
# Build
|
|
151
|
-
endpoint = f"{account_id}/
|
|
150
|
+
# Build reach estimate request (using correct Meta API endpoint)
|
|
151
|
+
endpoint = f"{account_id}/reachestimate"
|
|
152
152
|
params = {
|
|
153
|
-
"
|
|
154
|
-
"optimization_goal": optimization_goal
|
|
153
|
+
"targeting_spec": targeting
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
#
|
|
158
|
-
if optimization_goal == "REACH":
|
|
159
|
-
params["objective"] = "REACH"
|
|
160
|
-
elif optimization_goal in ["LINK_CLICKS", "LANDING_PAGE_VIEWS"]:
|
|
161
|
-
params["objective"] = "TRAFFIC"
|
|
162
|
-
elif optimization_goal == "CONVERSIONS":
|
|
163
|
-
params["objective"] = "CONVERSIONS"
|
|
164
|
-
elif optimization_goal == "APP_INSTALLS":
|
|
165
|
-
params["objective"] = "APP_INSTALLS"
|
|
166
|
-
else:
|
|
167
|
-
params["objective"] = "REACH" # Default fallback
|
|
156
|
+
# Note: reachestimate endpoint doesn't support optimization_goal or objective parameters
|
|
168
157
|
|
|
169
158
|
try:
|
|
170
|
-
data = await make_api_request(endpoint, access_token, params, method="
|
|
159
|
+
data = await make_api_request(endpoint, access_token, params, method="GET")
|
|
171
160
|
|
|
172
161
|
# Format the response for easier consumption
|
|
173
162
|
if "data" in data and len(data["data"]) > 0:
|
|
@@ -191,14 +180,69 @@ async def estimate_audience_size(
|
|
|
191
180
|
else:
|
|
192
181
|
return json.dumps({
|
|
193
182
|
"error": "No estimation data returned from Meta API",
|
|
194
|
-
"raw_response": data
|
|
183
|
+
"raw_response": data,
|
|
184
|
+
"debug_info": {
|
|
185
|
+
"response_keys": list(data.keys()) if isinstance(data, dict) else "not_a_dict",
|
|
186
|
+
"response_type": str(type(data)),
|
|
187
|
+
"endpoint_used": f"{account_id}/reachestimate"
|
|
188
|
+
}
|
|
195
189
|
}, indent=2)
|
|
196
190
|
|
|
197
191
|
except Exception as e:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
192
|
+
# Check if this is the specific Business Manager system user permission error
|
|
193
|
+
error_str = str(e)
|
|
194
|
+
if "100" in error_str and "33" in error_str:
|
|
195
|
+
# Try to provide fallback estimation using individual interests if available
|
|
196
|
+
interests_found = []
|
|
197
|
+
if targeting and "interests" in targeting:
|
|
198
|
+
interests_found.extend([interest.get("id") for interest in targeting["interests"] if interest.get("id")])
|
|
199
|
+
elif targeting and "flexible_spec" in targeting:
|
|
200
|
+
for spec in targeting["flexible_spec"]:
|
|
201
|
+
if "interests" in spec:
|
|
202
|
+
interests_found.extend([interest.get("id") for interest in spec["interests"] if interest.get("id")])
|
|
203
|
+
|
|
204
|
+
if interests_found:
|
|
205
|
+
# Attempt to get individual interest data as fallback
|
|
206
|
+
try:
|
|
207
|
+
fallback_result = await estimate_audience_size(
|
|
208
|
+
access_token=access_token,
|
|
209
|
+
interest_fbid_list=interests_found
|
|
210
|
+
)
|
|
211
|
+
fallback_data = json.loads(fallback_result)
|
|
212
|
+
|
|
213
|
+
return json.dumps({
|
|
214
|
+
"comprehensive_targeting_failed": True,
|
|
215
|
+
"error_code": "100-33",
|
|
216
|
+
"fallback_used": True,
|
|
217
|
+
"details": {
|
|
218
|
+
"issue": "reachestimate endpoint returned error - possibly due to targeting parameters or account limitations",
|
|
219
|
+
"solution": "Individual interest validation used as fallback - comprehensive targeting may have specific requirements",
|
|
220
|
+
"endpoint_used": f"{account_id}/reachestimate"
|
|
221
|
+
},
|
|
222
|
+
"individual_interest_data": fallback_data,
|
|
223
|
+
"note": "Individual interest audience sizes provided as fallback. Comprehensive targeting via reachestimate endpoint failed."
|
|
224
|
+
}, indent=2)
|
|
225
|
+
except:
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
return json.dumps({
|
|
229
|
+
"error": "reachestimate endpoint returned error (previously was incorrectly using delivery_estimate)",
|
|
230
|
+
"error_code": "100-33",
|
|
231
|
+
"details": {
|
|
232
|
+
"issue": "The endpoint returned an error, possibly due to targeting parameters or account limitations",
|
|
233
|
+
"endpoint_used": f"{account_id}/reachestimate",
|
|
234
|
+
"previous_issue": "Code was previously using non-existent delivery_estimate endpoint - now fixed",
|
|
235
|
+
"available_alternative": "Use interest_list or interest_fbid_list parameters for individual interest validation"
|
|
236
|
+
},
|
|
237
|
+
"raw_error": error_str
|
|
238
|
+
}, indent=2)
|
|
239
|
+
else:
|
|
240
|
+
return json.dumps({
|
|
241
|
+
"error": f"Failed to get audience estimation from reachestimate endpoint: {str(e)}",
|
|
242
|
+
"details": "Check targeting parameters and account permissions",
|
|
243
|
+
"error_type": "general_api_error",
|
|
244
|
+
"endpoint_used": f"{account_id}/reachestimate"
|
|
245
|
+
}, indent=2)
|
|
202
246
|
|
|
203
247
|
|
|
204
248
|
@mcp_server.tool()
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meta-ads-mcp"
|
|
7
|
-
version = "0.10.
|
|
8
|
-
description = "Model Context Protocol (MCP)
|
|
7
|
+
version = "0.10.1"
|
|
8
|
+
description = "Model Context Protocol (MCP) server for interacting with Meta Ads API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
authors = [
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Unit tests for estimate_audience_size functionality in Meta Ads MCP.
|
|
4
4
|
|
|
5
5
|
This module tests the new estimate_audience_size function that replaces validate_interests
|
|
6
|
-
and provides comprehensive audience estimation using Meta's
|
|
6
|
+
and provides comprehensive audience estimation using Meta's reachestimate API.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
@@ -58,14 +58,12 @@ class TestEstimateAudienceSize:
|
|
|
58
58
|
|
|
59
59
|
# Verify API call
|
|
60
60
|
mock_api.assert_called_once_with(
|
|
61
|
-
"act_123456789/
|
|
61
|
+
"act_123456789/reachestimate",
|
|
62
62
|
"test_token",
|
|
63
63
|
{
|
|
64
|
-
"
|
|
65
|
-
"optimization_goal": "REACH",
|
|
66
|
-
"objective": "REACH"
|
|
64
|
+
"targeting_spec": targeting_spec
|
|
67
65
|
},
|
|
68
|
-
method="
|
|
66
|
+
method="GET"
|
|
69
67
|
)
|
|
70
68
|
|
|
71
69
|
# Verify response format
|
|
@@ -80,7 +78,7 @@ class TestEstimateAudienceSize:
|
|
|
80
78
|
|
|
81
79
|
@pytest.mark.asyncio
|
|
82
80
|
async def test_different_optimization_goals(self):
|
|
83
|
-
"""Test audience estimation with different optimization goals"""
|
|
81
|
+
"""Test audience estimation with different optimization goals (parameter is preserved in response)"""
|
|
84
82
|
mock_response = {
|
|
85
83
|
"data": [
|
|
86
84
|
{
|
|
@@ -98,19 +96,13 @@ class TestEstimateAudienceSize:
|
|
|
98
96
|
"geo_locations": {"countries": ["US"]}
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
("LINK_CLICKS", "TRAFFIC"),
|
|
104
|
-
("LANDING_PAGE_VIEWS", "TRAFFIC"),
|
|
105
|
-
("CONVERSIONS", "CONVERSIONS"),
|
|
106
|
-
("APP_INSTALLS", "APP_INSTALLS"),
|
|
107
|
-
("UNKNOWN_GOAL", "REACH") # Should fallback to REACH
|
|
108
|
-
]
|
|
99
|
+
# Test different optimization goals - they should all use the same reachestimate endpoint
|
|
100
|
+
test_goals = ["REACH", "LINK_CLICKS", "LANDING_PAGE_VIEWS", "CONVERSIONS", "APP_INSTALLS"]
|
|
109
101
|
|
|
110
102
|
with patch('meta_ads_mcp.core.targeting.make_api_request', new_callable=AsyncMock) as mock_api:
|
|
111
103
|
mock_api.return_value = mock_response
|
|
112
104
|
|
|
113
|
-
for optimization_goal
|
|
105
|
+
for optimization_goal in test_goals:
|
|
114
106
|
mock_api.reset_mock()
|
|
115
107
|
|
|
116
108
|
result = await estimate_audience_size(
|
|
@@ -120,16 +112,14 @@ class TestEstimateAudienceSize:
|
|
|
120
112
|
optimization_goal=optimization_goal
|
|
121
113
|
)
|
|
122
114
|
|
|
123
|
-
# Verify
|
|
115
|
+
# Verify API call uses reachestimate endpoint with simplified parameters
|
|
124
116
|
mock_api.assert_called_once_with(
|
|
125
|
-
"act_123456789/
|
|
117
|
+
"act_123456789/reachestimate",
|
|
126
118
|
"test_token",
|
|
127
119
|
{
|
|
128
|
-
"
|
|
129
|
-
"optimization_goal": optimization_goal,
|
|
130
|
-
"objective": expected_objective
|
|
120
|
+
"targeting_spec": targeting_spec
|
|
131
121
|
},
|
|
132
|
-
method="
|
|
122
|
+
method="GET"
|
|
133
123
|
)
|
|
134
124
|
|
|
135
125
|
result_data = json.loads(result)
|
|
@@ -316,7 +306,7 @@ class TestEstimateAudienceSize:
|
|
|
316
306
|
|
|
317
307
|
@pytest.mark.asyncio
|
|
318
308
|
async def test_api_error_handling(self):
|
|
319
|
-
"""Test handling of API errors from
|
|
309
|
+
"""Test handling of API errors from reachestimate"""
|
|
320
310
|
targeting_spec = {
|
|
321
311
|
"age_min": 25,
|
|
322
312
|
"age_max": 65,
|
|
@@ -338,12 +328,12 @@ class TestEstimateAudienceSize:
|
|
|
338
328
|
assert "data" in result_data
|
|
339
329
|
nested_data = json.loads(result_data["data"])
|
|
340
330
|
assert "error" in nested_data
|
|
341
|
-
assert "Failed to get audience estimation" in nested_data["error"]
|
|
331
|
+
assert "Failed to get audience estimation from reachestimate endpoint" in nested_data["error"]
|
|
342
332
|
assert "details" in nested_data
|
|
343
333
|
|
|
344
334
|
@pytest.mark.asyncio
|
|
345
335
|
async def test_empty_api_response(self):
|
|
346
|
-
"""Test handling of empty response from
|
|
336
|
+
"""Test handling of empty response from reachestimate API"""
|
|
347
337
|
targeting_spec = {
|
|
348
338
|
"age_min": 25,
|
|
349
339
|
"age_max": 65,
|
|
@@ -614,7 +614,7 @@ class AudienceEstimationTester:
|
|
|
614
614
|
print(f"\n✅ Audience estimation tests: SUCCESS ({passed_tests}/{total_tests} passed)")
|
|
615
615
|
print(" • Comprehensive audience estimation is working")
|
|
616
616
|
print(" • Backwards compatibility is maintained")
|
|
617
|
-
print(" • Meta
|
|
617
|
+
print(" • Meta reachestimate API integration is functional")
|
|
618
618
|
return True
|
|
619
619
|
else:
|
|
620
620
|
print(f"\n❌ Audience estimation tests: FAILED ({passed_tests}/{total_tests} passed)")
|
|
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
|