meta-ads-mcp 0.3.10__tar.gz → 0.4.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.
Files changed (51) hide show
  1. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/PKG-INFO +11 -20
  2. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/README.md +5 -16
  3. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/__init__.py +1 -3
  4. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/__init__.py +2 -2
  5. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/adsets.py +15 -6
  6. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/auth.py +29 -17
  7. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/authentication.py +17 -8
  8. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/callback_server.py +9 -0
  9. meta_ads_mcp-0.4.1/meta_ads_mcp/core/duplication.py +411 -0
  10. meta_ads_mcp-0.4.1/meta_ads_mcp/core/insights.py +62 -0
  11. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/pyproject.toml +6 -4
  12. meta_ads_mcp-0.4.1/tests/README_REGRESSION_TESTS.md +185 -0
  13. meta_ads_mcp-0.4.1/tests/test_duplication.py +136 -0
  14. meta_ads_mcp-0.4.1/tests/test_duplication_regression.py +805 -0
  15. meta_ads_mcp-0.3.10/meta_ads_mcp/core/insights.py +0 -429
  16. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/.github/workflows/publish.yml +0 -0
  17. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/.github/workflows/test.yml +0 -0
  18. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/.gitignore +0 -0
  19. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/CUSTOM_META_APP.md +0 -0
  20. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/Dockerfile +0 -0
  21. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/LICENSE +0 -0
  22. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/LOCAL_INSTALLATION.md +0 -0
  23. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/META_API_NOTES.md +0 -0
  24. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/RELEASE.md +0 -0
  25. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/STREAMABLE_HTTP_SETUP.md +0 -0
  26. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/examples/README.md +0 -0
  27. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/examples/example_http_client.py +0 -0
  28. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/future_improvements.md +0 -0
  29. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/images/meta-ads-example.png +0 -0
  30. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_auth.sh +0 -0
  31. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/__main__.py +0 -0
  32. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/accounts.py +0 -0
  33. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/ads.py +0 -0
  34. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/ads_library.py +0 -0
  35. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/api.py +0 -0
  36. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/budget_schedules.py +0 -0
  37. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/campaigns.py +0 -0
  38. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/http_auth_integration.py +0 -0
  39. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
  40. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/reports.py +0 -0
  41. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/resources.py +0 -0
  42. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/server.py +0 -0
  43. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/meta_ads_mcp/core/utils.py +0 -0
  44. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/requirements.txt +0 -0
  45. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/setup.py +0 -0
  46. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/smithery.yaml +0 -0
  47. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/tests/README.md +0 -0
  48. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/tests/__init__.py +0 -0
  49. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/tests/conftest.py +0 -0
  50. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/tests/test_http_transport.py +0 -0
  51. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.1}/tests/test_openai.py +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.3.10
4
- Summary: Model Calling Protocol (MCP) plugin for interacting with Meta Ads API
3
+ Version: 0.4.1
4
+ Summary: Model Context Protocol (MCP) plugin for interacting with Meta Ads API
5
5
  Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
6
6
  Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
7
7
  Author-email: Yves Junqueira <yves.junqueira@gmail.com>
8
- License: MIT
8
+ License: Apache-2.0
9
9
  License-File: LICENSE
10
10
  Keywords: ads,api,claude,facebook,mcp,meta
11
- Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Requires-Python: >=3.10
@@ -16,6 +16,8 @@ Requires-Dist: httpx>=0.26.0
16
16
  Requires-Dist: mcp[cli]>=1.10.1
17
17
  Requires-Dist: pathlib>=1.0.1
18
18
  Requires-Dist: pillow>=10.0.0
19
+ Requires-Dist: pytest-asyncio>=1.0.0
20
+ Requires-Dist: pytest>=8.4.1
19
21
  Requires-Dist: python-dateutil>=2.8.2
20
22
  Requires-Dist: python-dotenv>=1.1.0
21
23
  Requires-Dist: requests>=2.32.3
@@ -63,17 +65,14 @@ That's it! You can now ask Claude to analyze your Meta ad campaigns, get perform
63
65
 
64
66
  ### For Cursor Users
65
67
 
66
- Add this to your `~/.cursor/mcp.json`:
68
+ Add the following to your `~/.cursor/mcp.json`. Once you enable the remote MCP, click on "Needs login" to finish the login process.
69
+
67
70
 
68
71
  ```json
69
72
  {
70
73
  "mcpServers": {
71
74
  "meta-ads-remote": {
72
- "command": "npx",
73
- "args": [
74
- "mcp-remote",
75
- "https://mcp.pipeboard.co/meta-ads-mcp"
76
- ]
75
+ "url": "https://mcp.pipeboard.co/meta-ads-mcp"
77
76
  }
78
77
  }
79
78
  }
@@ -313,21 +312,13 @@ For local installation configuration, authentication options, and advanced techn
313
312
  - `level`: Level of aggregation (ad, adset, campaign, account)
314
313
  - Returns: Performance metrics for the specified object
315
314
 
316
- 20. `mcp_meta_ads_debug_image_download`
317
- - Debug image download issues and report detailed diagnostics
318
- - Inputs:
319
- - `access_token` (optional): Meta API access token (will use cached token if not provided)
320
- - `url`: Direct image URL to test (optional)
321
- - `ad_id`: Meta Ads ad ID (optional, used if url is not provided)
322
- - Returns: Diagnostic information about image download attempts
323
-
324
- 21. `mcp_meta_ads_get_login_link`
315
+ 20. `mcp_meta_ads_get_login_link`
325
316
  - Get a clickable login link for Meta Ads authentication
326
317
  - Inputs:
327
318
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
328
319
  - Returns: A clickable resource link for Meta authentication
329
320
 
330
- 22. `mcp_meta-ads_create_budget_schedule`
321
+ 21. `mcp_meta-ads_create_budget_schedule`
331
322
  - Create a budget schedule for a Meta Ads campaign.
332
323
  - Inputs:
333
324
  - `campaign_id`: Meta Ads campaign ID.
@@ -40,17 +40,14 @@ That's it! You can now ask Claude to analyze your Meta ad campaigns, get perform
40
40
 
41
41
  ### For Cursor Users
42
42
 
43
- Add this to your `~/.cursor/mcp.json`:
43
+ Add the following to your `~/.cursor/mcp.json`. Once you enable the remote MCP, click on "Needs login" to finish the login process.
44
+
44
45
 
45
46
  ```json
46
47
  {
47
48
  "mcpServers": {
48
49
  "meta-ads-remote": {
49
- "command": "npx",
50
- "args": [
51
- "mcp-remote",
52
- "https://mcp.pipeboard.co/meta-ads-mcp"
53
- ]
50
+ "url": "https://mcp.pipeboard.co/meta-ads-mcp"
54
51
  }
55
52
  }
56
53
  }
@@ -290,21 +287,13 @@ For local installation configuration, authentication options, and advanced techn
290
287
  - `level`: Level of aggregation (ad, adset, campaign, account)
291
288
  - Returns: Performance metrics for the specified object
292
289
 
293
- 20. `mcp_meta_ads_debug_image_download`
294
- - Debug image download issues and report detailed diagnostics
295
- - Inputs:
296
- - `access_token` (optional): Meta API access token (will use cached token if not provided)
297
- - `url`: Direct image URL to test (optional)
298
- - `ad_id`: Meta Ads ad ID (optional, used if url is not provided)
299
- - Returns: Diagnostic information about image download attempts
300
-
301
- 21. `mcp_meta_ads_get_login_link`
290
+ 20. `mcp_meta_ads_get_login_link`
302
291
  - Get a clickable login link for Meta Ads authentication
303
292
  - Inputs:
304
293
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
305
294
  - Returns: A clickable resource link for Meta authentication
306
295
 
307
- 22. `mcp_meta-ads_create_budget_schedule`
296
+ 21. `mcp_meta-ads_create_budget_schedule`
308
297
  - Create a budget schedule for a Meta Ads campaign.
309
298
  - Inputs:
310
299
  - `campaign_id`: Meta Ads campaign ID.
@@ -7,7 +7,7 @@ with the Claude LLM.
7
7
 
8
8
  from meta_ads_mcp.core.server import main
9
9
 
10
- __version__ = "0.3.10"
10
+ __version__ = "0.4.1"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -24,7 +24,6 @@ __all__ = [
24
24
  'get_ad_image',
25
25
  'update_ad',
26
26
  'get_insights',
27
- 'debug_image_download',
28
27
  'get_login_link',
29
28
  'login_cli',
30
29
  'main'
@@ -46,7 +45,6 @@ from .core import (
46
45
  get_ad_image,
47
46
  update_ad,
48
47
  get_insights,
49
- debug_image_download,
50
48
  get_login_link,
51
49
  login_cli,
52
50
  main
@@ -5,13 +5,14 @@ from .accounts import get_ad_accounts, get_account_info
5
5
  from .campaigns import get_campaigns, get_campaign_details, create_campaign
6
6
  from .adsets import get_adsets, get_adset_details, update_adset
7
7
  from .ads import get_ads, get_ad_details, get_ad_creatives, get_ad_image, update_ad
8
- from .insights import get_insights, debug_image_download
8
+ from .insights import get_insights
9
9
  from .authentication import get_login_link
10
10
  from .server import login_cli, main
11
11
  from .auth import login
12
12
  from .ads_library import search_ads_archive
13
13
  from .budget_schedules import create_budget_schedule
14
14
  from . import reports # Import module to register conditional tools
15
+ from . import duplication # Import module to register conditional duplication tools
15
16
 
16
17
  __all__ = [
17
18
  'mcp_server',
@@ -29,7 +30,6 @@ __all__ = [
29
30
  'get_ad_image',
30
31
  'update_ad',
31
32
  'get_insights',
32
- 'debug_image_download',
33
33
  'get_login_link',
34
34
  'login_cli',
35
35
  'login',
@@ -270,12 +270,21 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
270
270
  current_details = json.loads(current_details_json)
271
271
 
272
272
  # Start the callback server if not already running
273
- port = start_callback_server()
274
-
275
- # Generate confirmation URL with properly encoded parameters
276
- changes_json = json.dumps(changes)
277
- encoded_changes = urllib.parse.quote(changes_json)
278
- confirmation_url = f"http://localhost:{port}/confirm-update?adset_id={adset_id}&token={access_token}&changes={encoded_changes}"
273
+ try:
274
+ port = start_callback_server()
275
+
276
+ # Generate confirmation URL with properly encoded parameters
277
+ changes_json = json.dumps(changes)
278
+ encoded_changes = urllib.parse.quote(changes_json)
279
+ confirmation_url = f"http://localhost:{port}/confirm-update?adset_id={adset_id}&token={access_token}&changes={encoded_changes}"
280
+ except Exception as e:
281
+ return json.dumps({
282
+ "error": "Callback server disabled",
283
+ "message": f"Cannot create confirmation URL: {str(e)}",
284
+ "suggestion": "Manual update confirmation not available when META_ADS_DISABLE_CALLBACK_SERVER is set",
285
+ "adset_id": adset_id,
286
+ "proposed_changes": changes
287
+ }, indent=2)
279
288
 
280
289
  # Reset the update confirmation
281
290
  update_confirmation.clear()
@@ -216,22 +216,27 @@ class AuthManager:
216
216
  return self.token_info.access_token
217
217
 
218
218
  # Start the callback server if not already running
219
- port = start_callback_server()
220
-
221
- # Update redirect URI with the actual port
222
- self.redirect_uri = f"http://localhost:{port}/callback"
223
-
224
- # Generate the auth URL
225
- auth_url = self.get_auth_url()
226
-
227
- # Open browser with auth URL
228
- logger.info(f"Opening browser with URL: {auth_url}")
229
- webbrowser.open(auth_url)
230
-
231
- # We don't wait for the token here anymore
232
- # The token will be processed by the callback server
233
- # Just return None to indicate we've started the flow
234
- return None
219
+ try:
220
+ port = start_callback_server()
221
+
222
+ # Update redirect URI with the actual port
223
+ self.redirect_uri = f"http://localhost:{port}/callback"
224
+
225
+ # Generate the auth URL
226
+ auth_url = self.get_auth_url()
227
+
228
+ # Open browser with auth URL
229
+ logger.info(f"Opening browser with URL: {auth_url}")
230
+ webbrowser.open(auth_url)
231
+
232
+ # We don't wait for the token here anymore
233
+ # The token will be processed by the callback server
234
+ # Just return None to indicate we've started the flow
235
+ return None
236
+ except Exception as e:
237
+ logger.error(f"Failed to start callback server: {e}")
238
+ logger.info("Callback server disabled. OAuth authentication flow cannot be used.")
239
+ return None
235
240
 
236
241
  def get_access_token(self) -> Optional[str]:
237
242
  """
@@ -477,7 +482,14 @@ def login():
477
482
 
478
483
  try:
479
484
  # Start the callback server first
480
- port = start_callback_server()
485
+ try:
486
+ port = start_callback_server()
487
+ except Exception as callback_error:
488
+ print(f"Error: {callback_error}")
489
+ print("Callback server is disabled. Please use alternative authentication methods:")
490
+ print("- Set PIPEBOARD_API_TOKEN environment variable for Pipeboard authentication")
491
+ print("- Or provide a direct META_ACCESS_TOKEN environment variable")
492
+ return
481
493
 
482
494
  # Get the auth URL and open the browser
483
495
  auth_url = auth_manager.get_auth_url()
@@ -90,14 +90,23 @@ async def get_login_link(access_token: str = None) -> str:
90
90
  # IMPORTANT: Start the callback server first by calling our helper function
91
91
  # This ensures the server is ready before we provide the URL to the user
92
92
  logger.info("Starting callback server for authentication")
93
- port = start_callback_server()
94
- logger.info(f"Callback server started on port {port}")
95
-
96
- # Generate direct login URL
97
- auth_manager.redirect_uri = f"http://localhost:{port}/callback" # Ensure port is set correctly
98
- logger.info(f"Setting redirect URI to {auth_manager.redirect_uri}")
99
- login_url = auth_manager.get_auth_url()
100
- logger.info(f"Generated login URL: {login_url}")
93
+ try:
94
+ port = start_callback_server()
95
+ logger.info(f"Callback server started on port {port}")
96
+
97
+ # Generate direct login URL
98
+ auth_manager.redirect_uri = f"http://localhost:{port}/callback" # Ensure port is set correctly
99
+ logger.info(f"Setting redirect URI to {auth_manager.redirect_uri}")
100
+ login_url = auth_manager.get_auth_url()
101
+ logger.info(f"Generated login URL: {login_url}")
102
+ except Exception as e:
103
+ logger.error(f"Failed to start callback server: {e}")
104
+ return json.dumps({
105
+ "error": "Callback server disabled",
106
+ "message": str(e),
107
+ "suggestion": "Use Pipeboard authentication (set PIPEBOARD_API_TOKEN) or provide a direct access token",
108
+ "authentication_method": "meta_oauth_disabled"
109
+ }, indent=2)
101
110
 
102
111
  # Check if we can exchange for long-lived tokens
103
112
  token_exchange_supported = bool(META_APP_SECRET)
@@ -917,7 +917,16 @@ def start_callback_server() -> int:
917
917
 
918
918
  Returns:
919
919
  Port number the server is running on
920
+
921
+ Raises:
922
+ Exception: If callback server is disabled via META_ADS_DISABLE_CALLBACK_SERVER environment variable
920
923
  """
924
+ # Check if callback server is disabled via environment variable
925
+ if os.environ.get("META_ADS_DISABLE_CALLBACK_SERVER"):
926
+ logger.info("Callback server disabled via META_ADS_DISABLE_CALLBACK_SERVER environment variable")
927
+ print("Callback server is disabled. OAuth authentication flow cannot be used.")
928
+ raise Exception("Callback server disabled via META_ADS_DISABLE_CALLBACK_SERVER environment variable. Use alternative authentication methods.")
929
+
921
930
  global callback_server_thread, callback_server_running, callback_server_port, callback_server_instance, server_shutdown_timer
922
931
 
923
932
  with callback_server_lock: