meta-ads-mcp 0.3.10__py3-none-any.whl → 0.4.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -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.0"
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,7 +5,7 @@ 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
@@ -29,7 +29,6 @@ __all__ = [
29
29
  'get_ad_image',
30
30
  'update_ad',
31
31
  'get_insights',
32
- 'debug_image_download',
33
32
  'get_login_link',
34
33
  'login_cli',
35
34
  'login',
@@ -56,374 +56,7 @@ async def get_insights(access_token: str = None, object_id: str = None,
56
56
  return json.dumps(data, indent=2)
57
57
 
58
58
 
59
- @mcp_server.tool()
60
- @meta_api_tool
61
- async def debug_image_download(access_token: str = None, url: str = "", ad_id: str = "") -> str:
62
- """
63
- Debug image download issues and report detailed diagnostics.
64
-
65
- Args:
66
- access_token: Meta API access token (optional - will use cached token if not provided)
67
- url: Direct image URL to test (optional)
68
- ad_id: Meta Ads ad ID (optional, used if url is not provided)
69
- """
70
- results = {
71
- "diagnostics": {
72
- "timestamp": str(datetime.datetime.now()),
73
- "methods_tried": [],
74
- "request_details": [],
75
- "network_info": {}
76
- }
77
- }
78
-
79
- # If no URL provided but ad_id is, get URL from ad creative
80
- if not url and ad_id:
81
- print(f"Getting image URL from ad creative for ad {ad_id}")
82
- # Get the creative details
83
- from .ads import get_ad_creatives
84
- creative_json = await get_ad_creatives(ad_id=ad_id, access_token=access_token)
85
- creative_data = json.loads(creative_json)
86
- results["creative_data"] = creative_data
87
-
88
- # Look for image URL in the creative
89
- if "full_image_url" in creative_data:
90
- url = creative_data.get("full_image_url")
91
- elif "thumbnail_url" in creative_data:
92
- url = creative_data.get("thumbnail_url")
93
-
94
- if not url:
95
- return json.dumps({
96
- "error": "No image URL provided or found in ad creative",
97
- "results": results
98
- }, indent=2)
99
-
100
- results["image_url"] = url
101
-
102
- # Try to get network information to help debug
103
- try:
104
- import socket
105
- from urllib.parse import urlparse
106
- hostname = urlparse(url).netloc
107
- ip_address = socket.gethostbyname(hostname)
108
- results["diagnostics"]["network_info"] = {
109
- "hostname": hostname,
110
- "ip_address": ip_address,
111
- "is_facebook_cdn": "fbcdn" in hostname
112
- }
113
- except Exception as e:
114
- results["diagnostics"]["network_info"] = {
115
- "error": str(e)
116
- }
117
-
118
- # Method 1: Basic download
119
- method_result = {
120
- "method": "Basic download with standard headers",
121
- "success": False
122
- }
123
- results["diagnostics"]["methods_tried"].append(method_result)
124
-
125
- try:
126
- headers = {
127
- "User-Agent": "curl/8.4.0"
128
- }
129
- import httpx
130
- async with httpx.AsyncClient(follow_redirects=True) as client:
131
- response = await client.get(url, headers=headers, timeout=30.0)
132
- method_result["status_code"] = response.status_code
133
- method_result["headers"] = dict(response.headers)
134
-
135
- if response.status_code == 200:
136
- method_result["success"] = True
137
- method_result["content_length"] = len(response.content)
138
- method_result["content_type"] = response.headers.get("content-type")
139
-
140
- # Save this successful result
141
- results["image_data"] = {
142
- "length": len(response.content),
143
- "type": response.headers.get("content-type"),
144
- "base64_sample": base64.b64encode(response.content[:100]).decode("utf-8") + "..." if response.content else None
145
- }
146
- except Exception as e:
147
- method_result["error"] = str(e)
148
-
149
- # Method 2: Browser emulation
150
- method_result = {
151
- "method": "Browser emulation with cookies",
152
- "success": False
153
- }
154
- results["diagnostics"]["methods_tried"].append(method_result)
155
-
156
- try:
157
- headers = {
158
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
159
- "Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
160
- "Accept-Language": "en-US,en;q=0.9",
161
- "Referer": "https://www.facebook.com/",
162
- "Cookie": "presence=EDvF3EtimeF1697900316EuserFA21B00112233445566AA0EstateFDutF0CEchF_7bCC"
163
- }
164
-
165
- import httpx
166
- async with httpx.AsyncClient(follow_redirects=True) as client:
167
- response = await client.get(url, headers=headers, timeout=30.0)
168
- method_result["status_code"] = response.status_code
169
- method_result["headers"] = dict(response.headers)
170
-
171
- if response.status_code == 200:
172
- method_result["success"] = True
173
- method_result["content_length"] = len(response.content)
174
- method_result["content_type"] = response.headers.get("content-type")
175
-
176
- # If first method didn't succeed, save this successful result
177
- if "image_data" not in results:
178
- results["image_data"] = {
179
- "length": len(response.content),
180
- "type": response.headers.get("content-type"),
181
- "base64_sample": base64.b64encode(response.content[:100]).decode("utf-8") + "..." if response.content else None
182
- }
183
- except Exception as e:
184
- method_result["error"] = str(e)
185
-
186
- # Method 3: Graph API direct access (if applicable)
187
- if "fbcdn" in url or "facebook" in url:
188
- method_result = {
189
- "method": "Graph API direct access",
190
- "success": False
191
- }
192
- results["diagnostics"]["methods_tried"].append(method_result)
193
-
194
- try:
195
- # Try to reconstruct the attachment ID from URL if possible
196
- from urllib.parse import urlparse
197
- url_parts = urlparse(url).path.split("/")
198
- potential_ids = [part for part in url_parts if part.isdigit() and len(part) > 10]
199
-
200
- if potential_ids:
201
- attachment_id = potential_ids[0]
202
- endpoint = f"{attachment_id}?fields=url,width,height"
203
- api_result = await make_api_request(endpoint, access_token)
204
-
205
- method_result["api_response"] = api_result
206
-
207
- if "url" in api_result:
208
- graph_url = api_result["url"]
209
- method_result["graph_url"] = graph_url
210
-
211
- # Try to download from this Graph API URL
212
- import httpx
213
- async with httpx.AsyncClient() as client:
214
- response = await client.get(graph_url, timeout=30.0)
215
-
216
- method_result["status_code"] = response.status_code
217
- if response.status_code == 200:
218
- method_result["success"] = True
219
- method_result["content_length"] = len(response.content)
220
-
221
- # If previous methods didn't succeed, save this successful result
222
- if "image_data" not in results:
223
- results["image_data"] = {
224
- "length": len(response.content),
225
- "type": response.headers.get("content-type"),
226
- "base64_sample": base64.b64encode(response.content[:100]).decode("utf-8") + "..." if response.content else None
227
- }
228
- except Exception as e:
229
- method_result["error"] = str(e)
230
-
231
- # Generate a recommendation based on what we found
232
- if "image_data" in results:
233
- results["recommendation"] = "At least one download method succeeded. Consider implementing the successful method in the main code."
234
- else:
235
- # Check if the error appears to be access-related
236
- access_errors = False
237
- for method in results["diagnostics"]["methods_tried"]:
238
- if method.get("status_code") in [401, 403, 503]:
239
- access_errors = True
240
-
241
- if access_errors:
242
- results["recommendation"] = "Authentication or authorization errors detected. Images may require direct Facebook authentication not possible via API."
243
- else:
244
- results["recommendation"] = "Network or other technical errors detected. Check URL expiration or CDN restrictions."
245
-
246
- return json.dumps(results, indent=2)
247
59
 
248
60
 
249
- @mcp_server.tool()
250
- @meta_api_tool
251
- async def save_ad_image_via_api(access_token: str = None, ad_id: str = None) -> str:
252
- """
253
- Try to save an ad image by using the Marketing API's attachment endpoints.
254
- This is an alternative approach when direct image download fails.
255
-
256
- Args:
257
- access_token: Meta API access token (optional - will use cached token if not provided)
258
- ad_id: Meta Ads ad ID
259
- """
260
- if not ad_id:
261
- return json.dumps({"error": "No ad ID provided"}, indent=2)
262
-
263
- # First get the ad's creative ID
264
- endpoint = f"{ad_id}"
265
- params = {
266
- "fields": "creative,account_id"
267
- }
268
-
269
- ad_data = await make_api_request(endpoint, access_token, params)
270
-
271
- if "error" in ad_data:
272
- return json.dumps({
273
- "error": "Could not get ad data",
274
- "details": ad_data
275
- }, indent=2)
276
-
277
- if "creative" not in ad_data or "id" not in ad_data["creative"]:
278
- return json.dumps({
279
- "error": "No creative ID found for this ad",
280
- "ad_data": ad_data
281
- }, indent=2)
282
-
283
- creative_id = ad_data["creative"]["id"]
284
- account_id = ad_data.get("account_id", "")
285
-
286
- # Now get the creative object
287
- creative_endpoint = f"{creative_id}"
288
- creative_params = {
289
- "fields": "id,name,thumbnail_url,image_hash,asset_feed_spec"
290
- }
291
-
292
- creative_data = await make_api_request(creative_endpoint, access_token, creative_params)
293
-
294
- if "error" in creative_data:
295
- return json.dumps({
296
- "error": "Could not get creative data",
297
- "details": creative_data
298
- }, indent=2)
299
-
300
- # Approach 1: Try to get image through adimages endpoint if we have image_hash
301
- image_hash = None
302
- if "image_hash" in creative_data:
303
- image_hash = creative_data["image_hash"]
304
- elif "asset_feed_spec" in creative_data and "images" in creative_data["asset_feed_spec"] and len(creative_data["asset_feed_spec"]["images"]) > 0:
305
- image_hash = creative_data["asset_feed_spec"]["images"][0].get("hash")
306
-
307
- result = {
308
- "ad_id": ad_id,
309
- "creative_id": creative_id,
310
- "attempts": []
311
- }
312
-
313
- if image_hash and account_id:
314
- attempt = {
315
- "method": "adimages endpoint with hash",
316
- "success": False
317
- }
318
- result["attempts"].append(attempt)
319
-
320
- try:
321
- image_endpoint = f"act_{account_id}/adimages"
322
- image_params = {
323
- "hashes": [image_hash]
324
- }
325
- image_data = await make_api_request(image_endpoint, access_token, image_params)
326
- attempt["response"] = image_data
327
-
328
- if "data" in image_data and len(image_data["data"]) > 0 and "url" in image_data["data"][0]:
329
- url = image_data["data"][0]["url"]
330
- attempt["url"] = url
331
-
332
- # Try to download the image
333
- image_bytes = await download_image(url)
334
- if image_bytes:
335
- attempt["success"] = True
336
- attempt["image_size"] = len(image_bytes)
337
-
338
- # Save the image
339
- resource_id = f"ad_creative_{ad_id}_method1"
340
- resource_info = create_resource_from_image(
341
- image_bytes,
342
- resource_id,
343
- f"Ad Creative for {ad_id} (Method 1)"
344
- )
345
-
346
- # Return success with resource info
347
- result.update(resource_info)
348
- result["success"] = True
349
- base64_sample = base64.b64encode(image_bytes[:100]).decode("utf-8") + "..."
350
- result["base64_sample"] = base64_sample
351
- except Exception as e:
352
- attempt["error"] = str(e)
353
-
354
- # Approach 2: Try directly with the thumbnails endpoint
355
- attempt = {
356
- "method": "thumbnails endpoint on creative",
357
- "success": False
358
- }
359
- result["attempts"].append(attempt)
360
-
361
- try:
362
- thumbnails_endpoint = f"{creative_id}/thumbnails"
363
- thumbnails_params = {}
364
- thumbnails_data = await make_api_request(thumbnails_endpoint, access_token, thumbnails_params)
365
- attempt["response"] = thumbnails_data
366
-
367
- if "data" in thumbnails_data and len(thumbnails_data["data"]) > 0:
368
- for thumbnail in thumbnails_data["data"]:
369
- if "uri" in thumbnail:
370
- url = thumbnail["uri"]
371
- attempt["url"] = url
372
-
373
- # Try to download the image
374
- image_bytes = await download_image(url)
375
- if image_bytes:
376
- attempt["success"] = True
377
- attempt["image_size"] = len(image_bytes)
378
-
379
- # Save the image if method 1 didn't already succeed
380
- if "success" not in result or not result["success"]:
381
- resource_id = f"ad_creative_{ad_id}_method2"
382
- resource_info = create_resource_from_image(
383
- image_bytes,
384
- resource_id,
385
- f"Ad Creative for {ad_id} (Method 2)"
386
- )
387
-
388
- # Return success with resource info
389
- result.update(resource_info)
390
- result["success"] = True
391
- base64_sample = base64.b64encode(image_bytes[:100]).decode("utf-8") + "..."
392
- result["base64_sample"] = base64_sample
393
-
394
- # No need to try more thumbnails if we succeeded
395
- break
396
- except Exception as e:
397
- attempt["error"] = str(e)
398
-
399
- # Approach 3: Try using the preview shareable link as an alternate source
400
- attempt = {
401
- "method": "preview_shareable_link",
402
- "success": False
403
- }
404
- result["attempts"].append(attempt)
405
-
406
- try:
407
- # Get ad details with preview link
408
- ad_preview_endpoint = f"{ad_id}"
409
- ad_preview_params = {
410
- "fields": "preview_shareable_link"
411
- }
412
- ad_preview_data = await make_api_request(ad_preview_endpoint, access_token, ad_preview_params)
413
-
414
- if "preview_shareable_link" in ad_preview_data:
415
- preview_link = ad_preview_data["preview_shareable_link"]
416
- attempt["preview_link"] = preview_link
417
-
418
- # We can't directly download the preview image, but let's note it for manual inspection
419
- attempt["note"] = "Preview link available for manual inspection in browser"
420
- except Exception as e:
421
- attempt["error"] = str(e)
422
-
423
- # Overall result
424
- if "success" in result and result["success"]:
425
- result["message"] = "Successfully retrieved ad image through one of the API methods"
426
- else:
427
- result["message"] = "Failed to retrieve ad image through any API method"
428
-
429
- return json.dumps(result, indent=2)
61
+
62
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.3.10
3
+ Version: 0.4.0
4
4
  Summary: Model Calling 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
@@ -313,21 +313,13 @@ For local installation configuration, authentication options, and advanced techn
313
313
  - `level`: Level of aggregation (ad, adset, campaign, account)
314
314
  - Returns: Performance metrics for the specified object
315
315
 
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`
316
+ 20. `mcp_meta_ads_get_login_link`
325
317
  - Get a clickable login link for Meta Ads authentication
326
318
  - Inputs:
327
319
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
328
320
  - Returns: A clickable resource link for Meta authentication
329
321
 
330
- 22. `mcp_meta-ads_create_budget_schedule`
322
+ 21. `mcp_meta-ads_create_budget_schedule`
331
323
  - Create a budget schedule for a Meta Ads campaign.
332
324
  - Inputs:
333
325
  - `campaign_id`: Meta Ads campaign ID.
@@ -1,6 +1,6 @@
1
- meta_ads_mcp/__init__.py,sha256=eWjCQD9tlMl8HRDeUmrjlU0YxKfIjzyI00LB6SPO_VI,1237
1
+ meta_ads_mcp/__init__.py,sha256=Ive8lkiVBTI71DtD75td68TiZ597_Br6xiYdVIbJaw0,1182
2
2
  meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
- meta_ads_mcp/core/__init__.py,sha256=J9vO4rweKb9J-os2maiUy2R4gZGUH4bDSszLDqDCY6w,1174
3
+ meta_ads_mcp/core/__init__.py,sha256=TQSAEn_c_n5ShXcgPXbzDNfIfiPUgJAGe3VIUvSyvjo,1124
4
4
  meta_ads_mcp/core/accounts.py,sha256=Nmp7lPxO9wmq25jWV7_H0LIqnEbBhpCVBlLGW2HUaq0,2277
5
5
  meta_ads_mcp/core/ads.py,sha256=b_81GlGHIM4jISvuDZmHNyc6uW7uD3ovX68ezBci9MM,29747
6
6
  meta_ads_mcp/core/ads_library.py,sha256=onStn9UkRqYDC60gOPS-iKDtP1plz6DygUb7hUZ0Jw8,2807
@@ -12,14 +12,14 @@ meta_ads_mcp/core/budget_schedules.py,sha256=UxseExsvKAiPwfDCY9aycT4kys4xqeNytyq
12
12
  meta_ads_mcp/core/callback_server.py,sha256=AUymElaVwHqFyqB2wgqf6A68KsqwtKoYmY-7JZZt8Ks,43286
13
13
  meta_ads_mcp/core/campaigns.py,sha256=Fd477GsD1Gx08Ve0uXUCvr4fC-xQCeVHPBwRVaeRQKk,10965
14
14
  meta_ads_mcp/core/http_auth_integration.py,sha256=ZJHuxK1Kwtr9gvwfC5HZOLH5MW-HnDDKqJc4xuG5yVE,10060
15
- meta_ads_mcp/core/insights.py,sha256=XAm4uu83gWp84PEGqAJ3GFIqlvg7prh6MdD71JfvBCo,18072
15
+ meta_ads_mcp/core/insights.py,sha256=U7KYdWQpGcdykE1WUtdJdYR3VTwKrXUzIzCREwWbf48,2599
16
16
  meta_ads_mcp/core/pipeboard_auth.py,sha256=VvbxEB8ZOhnMccLU7HI1HgaPWHCl5NGrzZCm-zzHze4,22798
17
17
  meta_ads_mcp/core/reports.py,sha256=Dv3hfsPOR7IZ9WrYrKd_6SNgZl-USIphg7knva3UYAw,5747
18
18
  meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0w,1236
19
19
  meta_ads_mcp/core/server.py,sha256=mmhtcyB7h1aO6jK4njLztPdAebPDmc3mhA7DksR1nlY,17583
20
20
  meta_ads_mcp/core/utils.py,sha256=DsizDYuJnWUpkbShV1y5Qe8t47Qf59aPZ6O9v0hzdkY,6705
21
- meta_ads_mcp-0.3.10.dist-info/METADATA,sha256=FVmGFp-HjIv-9Sev49l29O8Uk69aKVh20wCEAZ1uXlQ,17535
22
- meta_ads_mcp-0.3.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- meta_ads_mcp-0.3.10.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
24
- meta_ads_mcp-0.3.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
- meta_ads_mcp-0.3.10.dist-info/RECORD,,
21
+ meta_ads_mcp-0.4.0.dist-info/METADATA,sha256=qYEgWGno8pVxWx7qV9vK3GZwDsJhhkRTfX6dWaMeZ7M,17125
22
+ meta_ads_mcp-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ meta_ads_mcp-0.4.0.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
24
+ meta_ads_mcp-0.4.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
+ meta_ads_mcp-0.4.0.dist-info/RECORD,,