meta-ads-mcp 0.10.9__py3-none-any.whl → 0.11.1__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
@@ -6,7 +6,7 @@ This package provides a Meta Ads MCP integration
6
6
 
7
7
  from meta_ads_mcp.core.server import main
8
8
 
9
- __version__ = "0.10.9"
9
+ __version__ = "0.11.1"
10
10
 
11
11
  __all__ = [
12
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(access_token: str = None, account_id: str = None) -> str:
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 {
meta_ads_mcp/core/ads.py CHANGED
@@ -14,29 +14,27 @@ from .utils import download_image, try_multiple_download_methods, ad_creative_im
14
14
  from .server import mcp_server
15
15
 
16
16
 
17
+ # Only register the save_ad_image_locally function if explicitly enabled via environment variable
18
+ ENABLE_SAVE_AD_IMAGE_LOCALLY = bool(os.environ.get("META_ADS_ENABLE_SAVE_AD_IMAGE_LOCALLY", ""))
19
+
20
+
17
21
  @mcp_server.tool()
18
22
  @meta_api_tool
19
- async def get_ads(access_token: str = None, account_id: str = None, limit: int = 10,
23
+ async def get_ads(account_id: str, access_token: Optional[str] = None, limit: int = 10,
20
24
  campaign_id: str = "", adset_id: str = "") -> str:
21
25
  """
22
26
  Get ads for a Meta Ads account with optional filtering.
23
27
 
24
28
  Args:
25
- access_token: Meta API access token (optional - will use cached token if not provided)
26
29
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
30
+ access_token: Meta API access token (optional - will use cached token if not provided)
27
31
  limit: Maximum number of ads to return (default: 10)
28
32
  campaign_id: Optional campaign ID to filter by
29
33
  adset_id: Optional ad set ID to filter by
30
34
  """
31
- # If no account ID is specified, try to get the first one for the user
35
+ # Require explicit account_id
32
36
  if not account_id:
33
- accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
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)
37
+ return json.dumps({"error": "No account ID specified"}, indent=2)
40
38
 
41
39
  # Prioritize adset_id over campaign_id - use adset-specific endpoint
42
40
  if adset_id:
@@ -67,13 +65,13 @@ async def get_ads(access_token: str = None, account_id: str = None, limit: int =
67
65
 
68
66
  @mcp_server.tool()
69
67
  @meta_api_tool
70
- async def get_ad_details(access_token: str = None, ad_id: str = None) -> str:
68
+ async def get_ad_details(ad_id: str, access_token: Optional[str] = None) -> str:
71
69
  """
72
70
  Get detailed information about a specific ad.
73
71
 
74
72
  Args:
75
- access_token: Meta API access token (optional - will use cached token if not provided)
76
73
  ad_id: Meta Ads ad ID
74
+ access_token: Meta API access token (optional - will use cached token if not provided)
77
75
  """
78
76
  if not ad_id:
79
77
  return json.dumps({"error": "No ad ID provided"}, indent=2)
@@ -91,14 +89,14 @@ async def get_ad_details(access_token: str = None, ad_id: str = None) -> str:
91
89
  @mcp_server.tool()
92
90
  @meta_api_tool
93
91
  async def create_ad(
94
- account_id: str = None,
95
- name: str = None,
96
- adset_id: str = None,
97
- creative_id: str = None,
92
+ account_id: str,
93
+ name: str,
94
+ adset_id: str,
95
+ creative_id: str,
98
96
  status: str = "PAUSED",
99
- bid_amount = None,
97
+ bid_amount: Optional[int] = None,
100
98
  tracking_specs: Optional[List[Dict[str, Any]]] = None,
101
- access_token: str = None
99
+ access_token: Optional[str] = None
102
100
  ) -> str:
103
101
  """
104
102
  Create a new ad with an existing creative.
@@ -158,13 +156,13 @@ async def create_ad(
158
156
 
159
157
  @mcp_server.tool()
160
158
  @meta_api_tool
161
- async def get_ad_creatives(access_token: str = None, ad_id: str = None) -> str:
159
+ async def get_ad_creatives(ad_id: str, access_token: Optional[str] = None) -> str:
162
160
  """
163
161
  Get creative details for a specific ad. Best if combined with get_ad_image to get the full image.
164
162
 
165
163
  Args:
166
- access_token: Meta API access token (optional - will use cached token if not provided)
167
164
  ad_id: Meta Ads ad ID
165
+ access_token: Meta API access token (optional - will use cached token if not provided)
168
166
  """
169
167
  if not ad_id:
170
168
  return json.dumps({"error": "No ad ID provided"}, indent=2)
@@ -186,13 +184,13 @@ async def get_ad_creatives(access_token: str = None, ad_id: str = None) -> str:
186
184
 
187
185
  @mcp_server.tool()
188
186
  @meta_api_tool
189
- async def get_ad_image(access_token: str = None, ad_id: str = None) -> Image:
187
+ async def get_ad_image(ad_id: str, access_token: Optional[str] = None) -> Image:
190
188
  """
191
189
  Get, download, and visualize a Meta ad image in one step. Useful to see the image in the LLM.
192
190
 
193
191
  Args:
194
- access_token: Meta API access token (optional - will use cached token if not provided)
195
192
  ad_id: Meta Ads ad ID
193
+ access_token: Meta API access token (optional - will use cached token if not provided)
196
194
 
197
195
  Returns:
198
196
  The ad image ready for direct visual analysis
@@ -386,141 +384,142 @@ async def get_ad_image(access_token: str = None, ad_id: str = None) -> Image:
386
384
  return f"Error processing image: {str(e)}"
387
385
 
388
386
 
389
- @mcp_server.tool()
390
- @meta_api_tool
391
- async def save_ad_image_locally(access_token: str = None, ad_id: str = None, output_dir: str = "ad_images") -> str:
392
- """
393
- Get, download, and save a Meta ad image locally, returning the file path.
394
-
395
- Args:
396
- access_token: Meta API access token (optional - will use cached token if not provided)
397
- ad_id: Meta Ads ad ID
398
- output_dir: Directory to save the image file (default: 'ad_images')
399
-
400
- Returns:
401
- The file path to the saved image, or an error message string.
402
- """
403
- if not ad_id:
404
- return json.dumps({"error": "No ad ID provided"}, indent=2)
387
+ if ENABLE_SAVE_AD_IMAGE_LOCALLY:
388
+ @mcp_server.tool()
389
+ @meta_api_tool
390
+ async def save_ad_image_locally(ad_id: str, access_token: Optional[str] = None, output_dir: str = "ad_images") -> str:
391
+ """
392
+ Get, download, and save a Meta ad image locally, returning the file path.
405
393
 
406
- print(f"Attempting to get and save creative image for ad {ad_id}")
407
-
408
- # First, get creative and account IDs
409
- ad_endpoint = f"{ad_id}"
410
- ad_params = {
411
- "fields": "creative{id},account_id"
412
- }
413
-
414
- ad_data = await make_api_request(ad_endpoint, access_token, ad_params)
415
-
416
- if "error" in ad_data:
417
- return json.dumps({"error": f"Could not get ad data - {json.dumps(ad_data)}"}, indent=2)
418
-
419
- account_id = ad_data.get("account_id")
420
- if not account_id:
421
- return json.dumps({"error": "No account ID found for ad"}, indent=2)
422
-
423
- if "creative" not in ad_data:
424
- return json.dumps({"error": "No creative found for this ad"}, indent=2)
394
+ Args:
395
+ ad_id: Meta Ads ad ID
396
+ access_token: Meta API access token (optional - will use cached token if not provided)
397
+ output_dir: Directory to save the image file (default: 'ad_images')
425
398
 
426
- creative_data = ad_data.get("creative", {})
427
- creative_id = creative_data.get("id")
428
- if not creative_id:
429
- return json.dumps({"error": "No creative ID found"}, indent=2)
430
-
431
- # Get creative details to find image hash
432
- creative_endpoint = f"{creative_id}"
433
- creative_params = {
434
- "fields": "id,name,image_hash,asset_feed_spec"
435
- }
436
- creative_details = await make_api_request(creative_endpoint, access_token, creative_params)
437
-
438
- image_hashes = []
439
- if "image_hash" in creative_details:
440
- image_hashes.append(creative_details["image_hash"])
441
- if "asset_feed_spec" in creative_details and "images" in creative_details["asset_feed_spec"]:
442
- for image in creative_details["asset_feed_spec"]["images"]:
443
- if "hash" in image:
444
- image_hashes.append(image["hash"])
445
-
446
- if not image_hashes:
447
- # Fallback attempt (as in get_ad_image)
448
- creative_json = await get_ad_creatives(ad_id=ad_id, access_token=access_token) # Ensure ad_id is passed correctly
449
- creative_data_list = json.loads(creative_json)
450
- if 'data' in creative_data_list and creative_data_list['data']:
451
- first_creative = creative_data_list['data'][0]
452
- if 'object_story_spec' in first_creative and 'link_data' in first_creative['object_story_spec'] and 'image_hash' in first_creative['object_story_spec']['link_data']:
453
- image_hashes.append(first_creative['object_story_spec']['link_data']['image_hash'])
454
- elif 'image_hash' in first_creative: # Check direct hash on creative data
455
- image_hashes.append(first_creative['image_hash'])
399
+ Returns:
400
+ The file path to the saved image, or an error message string.
401
+ """
402
+ if not ad_id:
403
+ return json.dumps({"error": "No ad ID provided"}, indent=2)
404
+
405
+ print(f"Attempting to get and save creative image for ad {ad_id}")
406
+
407
+ # First, get creative and account IDs
408
+ ad_endpoint = f"{ad_id}"
409
+ ad_params = {
410
+ "fields": "creative{id},account_id"
411
+ }
412
+
413
+ ad_data = await make_api_request(ad_endpoint, access_token, ad_params)
414
+
415
+ if "error" in ad_data:
416
+ return json.dumps({"error": f"Could not get ad data - {json.dumps(ad_data)}"}, indent=2)
417
+
418
+ account_id = ad_data.get("account_id")
419
+ if not account_id:
420
+ return json.dumps({"error": "No account ID found for ad"}, indent=2)
421
+
422
+ if "creative" not in ad_data:
423
+ return json.dumps({"error": "No creative found for this ad"}, indent=2)
424
+
425
+ creative_data = ad_data.get("creative", {})
426
+ creative_id = creative_data.get("id")
427
+ if not creative_id:
428
+ return json.dumps({"error": "No creative ID found"}, indent=2)
429
+
430
+ # Get creative details to find image hash
431
+ creative_endpoint = f"{creative_id}"
432
+ creative_params = {
433
+ "fields": "id,name,image_hash,asset_feed_spec"
434
+ }
435
+ creative_details = await make_api_request(creative_endpoint, access_token, creative_params)
436
+
437
+ image_hashes = []
438
+ if "image_hash" in creative_details:
439
+ image_hashes.append(creative_details["image_hash"])
440
+ if "asset_feed_spec" in creative_details and "images" in creative_details["asset_feed_spec"]:
441
+ for image in creative_details["asset_feed_spec"]["images"]:
442
+ if "hash" in image:
443
+ image_hashes.append(image["hash"])
444
+
445
+ if not image_hashes:
446
+ # Fallback attempt (as in get_ad_image)
447
+ creative_json = await get_ad_creatives(ad_id=ad_id, access_token=access_token) # Ensure ad_id is passed correctly
448
+ creative_data_list = json.loads(creative_json)
449
+ if 'data' in creative_data_list and creative_data_list['data']:
450
+ first_creative = creative_data_list['data'][0]
451
+ if 'object_story_spec' in first_creative and 'link_data' in first_creative['object_story_spec'] and 'image_hash' in first_creative['object_story_spec']['link_data']:
452
+ image_hashes.append(first_creative['object_story_spec']['link_data']['image_hash'])
453
+ elif 'image_hash' in first_creative: # Check direct hash on creative data
454
+ image_hashes.append(first_creative['image_hash'])
456
455
 
457
456
 
458
- if not image_hashes:
459
- return json.dumps({"error": "No image hashes found in creative or fallback"}, indent=2)
457
+ if not image_hashes:
458
+ return json.dumps({"error": "No image hashes found in creative or fallback"}, indent=2)
460
459
 
461
- print(f"Found image hashes: {image_hashes}")
462
-
463
- # Fetch image data using the first hash
464
- image_endpoint = f"act_{account_id}/adimages"
465
- hashes_str = f'["{image_hashes[0]}"]'
466
- image_params = {
467
- "fields": "hash,url,width,height,name,status",
468
- "hashes": hashes_str
469
- }
470
-
471
- print(f"Requesting image data with params: {image_params}")
472
- image_data = await make_api_request(image_endpoint, access_token, image_params)
473
-
474
- if "error" in image_data:
475
- return json.dumps({"error": f"Failed to get image data - {json.dumps(image_data)}"}, indent=2)
476
-
477
- if "data" not in image_data or not image_data["data"]:
478
- return json.dumps({"error": "No image data returned from API"}, indent=2)
460
+ print(f"Found image hashes: {image_hashes}")
479
461
 
480
- first_image = image_data["data"][0]
481
- image_url = first_image.get("url")
482
-
483
- if not image_url:
484
- return json.dumps({"error": "No valid image URL found in API response"}, indent=2)
462
+ # Fetch image data using the first hash
463
+ image_endpoint = f"act_{account_id}/adimages"
464
+ hashes_str = f'["{image_hashes[0]}"]'
465
+ image_params = {
466
+ "fields": "hash,url,width,height,name,status",
467
+ "hashes": hashes_str
468
+ }
485
469
 
486
- print(f"Downloading image from URL: {image_url}")
487
-
488
- # Download and Save Image
489
- image_bytes = await download_image(image_url)
490
-
491
- if not image_bytes:
492
- return json.dumps({"error": "Failed to download image"}, indent=2)
470
+ print(f"Requesting image data with params: {image_params}")
471
+ image_data = await make_api_request(image_endpoint, access_token, image_params)
493
472
 
494
- try:
495
- # Ensure output directory exists
496
- if not os.path.exists(output_dir):
497
- os.makedirs(output_dir)
473
+ if "error" in image_data:
474
+ return json.dumps({"error": f"Failed to get image data - {json.dumps(image_data)}"}, indent=2)
475
+
476
+ if "data" not in image_data or not image_data["data"]:
477
+ return json.dumps({"error": "No image data returned from API"}, indent=2)
498
478
 
499
- # Create a filename (e.g., using ad_id and image hash)
500
- file_extension = ".jpg" # Default extension, could try to infer from headers later
501
- filename = f"{ad_id}_{image_hashes[0]}{file_extension}"
502
- filepath = os.path.join(output_dir, filename)
503
-
504
- # Save the image bytes to the file
505
- with open(filepath, "wb") as f:
506
- f.write(image_bytes)
479
+ first_image = image_data["data"][0]
480
+ image_url = first_image.get("url")
481
+
482
+ if not image_url:
483
+ return json.dumps({"error": "No valid image URL found in API response"}, indent=2)
507
484
 
508
- print(f"Image saved successfully to: {filepath}")
509
- return json.dumps({"filepath": filepath}, indent=2) # Return JSON with filepath
485
+ print(f"Downloading image from URL: {image_url}")
486
+
487
+ # Download and Save Image
488
+ image_bytes = await download_image(image_url)
489
+
490
+ if not image_bytes:
491
+ return json.dumps({"error": "Failed to download image"}, indent=2)
492
+
493
+ try:
494
+ # Ensure output directory exists
495
+ if not os.path.exists(output_dir):
496
+ os.makedirs(output_dir)
497
+
498
+ # Create a filename (e.g., using ad_id and image hash)
499
+ file_extension = ".jpg" # Default extension, could try to infer from headers later
500
+ filename = f"{ad_id}_{image_hashes[0]}{file_extension}"
501
+ filepath = os.path.join(output_dir, filename)
502
+
503
+ # Save the image bytes to the file
504
+ with open(filepath, "wb") as f:
505
+ f.write(image_bytes)
506
+
507
+ print(f"Image saved successfully to: {filepath}")
508
+ return json.dumps({"filepath": filepath}, indent=2) # Return JSON with filepath
510
509
 
511
- except Exception as e:
512
- return json.dumps({"error": f"Failed to save image: {str(e)}"}, indent=2)
510
+ except Exception as e:
511
+ return json.dumps({"error": f"Failed to save image: {str(e)}"}, indent=2)
513
512
 
514
513
 
515
514
  @mcp_server.tool()
516
515
  @meta_api_tool
517
516
  async def update_ad(
518
517
  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
518
+ status: Optional[str] = None,
519
+ bid_amount: Optional[int] = None,
520
+ tracking_specs: Optional[List[Dict[str, Any]]] = None,
521
+ creative_id: Optional[str] = None,
522
+ access_token: Optional[str] = None
524
523
  ) -> str:
525
524
  """
526
525
  Update an ad with new settings.
@@ -562,18 +561,18 @@ async def update_ad(
562
561
  @mcp_server.tool()
563
562
  @meta_api_tool
564
563
  async def upload_ad_image(
565
- access_token: str = None,
566
- account_id: str = None,
567
- file: str = None,
568
- image_url: str = None,
569
- name: str = None
564
+ account_id: str,
565
+ access_token: Optional[str] = None,
566
+ file: Optional[str] = None,
567
+ image_url: Optional[str] = None,
568
+ name: Optional[str] = None
570
569
  ) -> str:
571
570
  """
572
571
  Upload an image to use in Meta Ads creatives.
573
572
 
574
573
  Args:
575
- access_token: Meta API access token (optional - will use cached token if not provided)
576
574
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
575
+ access_token: Meta API access token (optional - will use cached token if not provided)
577
576
  file: Data URL or raw base64 string of the image (e.g., "data:image/png;base64,iVBORw0KG...")
578
577
  image_url: Direct URL to an image to fetch and upload
579
578
  name: Optional name for the image (default: filename)
@@ -731,29 +730,29 @@ async def upload_ad_image(
731
730
  @mcp_server.tool()
732
731
  @meta_api_tool
733
732
  async def create_ad_creative(
734
- access_token: str = None,
735
- account_id: str = None,
736
- name: str = None,
737
- image_hash: str = None,
738
- page_id: str = None,
739
- link_url: str = None,
740
- message: str = None,
741
- headline: str = None,
742
- headlines: List[str] = None,
743
- description: str = None,
744
- descriptions: List[str] = None,
745
- dynamic_creative_spec: Dict[str, Any] = None,
746
- call_to_action_type: str = None,
747
- instagram_actor_id: str = None
733
+ account_id: str,
734
+ image_hash: str,
735
+ access_token: Optional[str] = None,
736
+ name: Optional[str] = None,
737
+ page_id: Optional[str] = None,
738
+ link_url: Optional[str] = None,
739
+ message: Optional[str] = None,
740
+ headline: Optional[str] = None,
741
+ headlines: Optional[List[str]] = None,
742
+ description: Optional[str] = None,
743
+ descriptions: Optional[List[str]] = None,
744
+ dynamic_creative_spec: Optional[Dict[str, Any]] = None,
745
+ call_to_action_type: Optional[str] = None,
746
+ instagram_actor_id: Optional[str] = None
748
747
  ) -> str:
749
748
  """
750
749
  Create a new ad creative using an uploaded image hash.
751
750
 
752
751
  Args:
753
- access_token: Meta API access token (optional - will use cached token if not provided)
754
752
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
755
- name: Creative name
756
753
  image_hash: Hash of the uploaded image
754
+ access_token: Meta API access token (optional - will use cached token if not provided)
755
+ name: Creative name
757
756
  page_id: Facebook Page ID to be used for the ad
758
757
  link_url: Destination URL for the ad
759
758
  message: Ad copy/text
@@ -943,23 +942,23 @@ async def create_ad_creative(
943
942
  @mcp_server.tool()
944
943
  @meta_api_tool
945
944
  async def update_ad_creative(
946
- access_token: str = None,
947
- creative_id: str = None,
948
- name: str = None,
949
- message: str = None,
950
- headline: str = None,
951
- headlines: List[str] = None,
952
- description: str = None,
953
- descriptions: List[str] = None,
954
- dynamic_creative_spec: Dict[str, Any] = None,
955
- call_to_action_type: str = None
945
+ creative_id: str,
946
+ access_token: Optional[str] = None,
947
+ name: Optional[str] = None,
948
+ message: Optional[str] = None,
949
+ headline: Optional[str] = None,
950
+ headlines: Optional[List[str]] = None,
951
+ description: Optional[str] = None,
952
+ descriptions: Optional[List[str]] = None,
953
+ dynamic_creative_spec: Optional[Dict[str, Any]] = None,
954
+ call_to_action_type: Optional[str] = None
956
955
  ) -> str:
957
956
  """
958
957
  Update an existing ad creative with new content or settings.
959
958
 
960
959
  Args:
961
- access_token: Meta API access token (optional - will use cached token if not provided)
962
960
  creative_id: Meta Ads creative ID to update
961
+ access_token: Meta API access token (optional - will use cached token if not provided)
963
962
  name: New creative name
964
963
  message: New ad copy/text
965
964
  headline: Single headline for simple ads (cannot be used with headlines)
@@ -1252,13 +1251,13 @@ async def _search_pages_by_name_core(access_token: str, account_id: str, search_
1252
1251
 
1253
1252
  @mcp_server.tool()
1254
1253
  @meta_api_tool
1255
- async def search_pages_by_name(access_token: str = None, account_id: str = None, search_term: str = None) -> str:
1254
+ async def search_pages_by_name(account_id: str, access_token: Optional[str] = None, search_term: Optional[str] = None) -> str:
1256
1255
  """
1257
1256
  Search for pages by name within an account.
1258
1257
 
1259
1258
  Args:
1260
- access_token: Meta API access token (optional - will use cached token if not provided)
1261
1259
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
1260
+ access_token: Meta API access token (optional - will use cached token if not provided)
1262
1261
  search_term: Search term to find pages by name (optional - returns all pages if not provided)
1263
1262
 
1264
1263
  Returns:
@@ -1275,13 +1274,13 @@ async def search_pages_by_name(access_token: str = None, account_id: str = None,
1275
1274
 
1276
1275
  @mcp_server.tool()
1277
1276
  @meta_api_tool
1278
- async def get_account_pages(access_token: str = None, account_id: str = None) -> str:
1277
+ async def get_account_pages(account_id: str, access_token: Optional[str] = None) -> str:
1279
1278
  """
1280
1279
  Get pages associated with a Meta Ads account.
1281
1280
 
1282
1281
  Args:
1283
- access_token: Meta API access token (optional - will use cached token if not provided)
1284
1282
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
1283
+ access_token: Meta API access token (optional - will use cached token if not provided)
1285
1284
 
1286
1285
  Returns:
1287
1286
  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
- access_token: str = None,
18
- search_terms: str = None,
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(access_token: str = None, account_id: str = None, limit: int = 10, campaign_id: str = "") -> str:
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
- # If no account ID is specified, try to get the first one for the user
22
+ # Require explicit account_id
23
23
  if not account_id:
24
- accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
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(access_token: str = None, adset_id: str = None) -> str:
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 (required)
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 = None,
94
- campaign_id: str = None,
95
- name: str = None,
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
- optimization_goal: str = None,
101
- billing_event: str = None,
102
- bid_amount = None,
103
- bid_strategy: str = None,
104
- start_time: str = None,
105
- end_time: str = None,
106
- dsa_beneficiary: str = None,
107
- promoted_object: Dict[str, Any] = None,
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
 
meta_ads_mcp/core/api.py CHANGED
@@ -316,6 +316,6 @@ def meta_api_tool(func):
316
316
  return result
317
317
  except Exception as e:
318
318
  logger.error(f"Error in {func.__name__}: {str(e)}")
319
- return {"error": str(e)}
319
+ return json.dumps({"error": str(e)}, indent=2)
320
320
 
321
321
  return wrapper
meta_ads_mcp/core/auth.py CHANGED
@@ -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
 
@@ -16,7 +16,7 @@ async def create_budget_schedule(
16
16
  budget_value_type: str,
17
17
  time_start: int,
18
18
  time_end: int,
19
- access_token: str = None
19
+ access_token: Optional[str] = None
20
20
  ) -> str:
21
21
  """
22
22
  Create a budget schedule for a Meta Ads campaign.
@@ -9,7 +9,7 @@ from .server import mcp_server
9
9
 
10
10
  @mcp_server.tool()
11
11
  @meta_api_tool
12
- async def get_campaigns(access_token: str = None, account_id: str = None, limit: int = 10, status_filter: str = "", after: str = "") -> str:
12
+ async def get_campaigns(account_id: str, access_token: Optional[str] = None, limit: int = 10, status_filter: str = "", after: str = "") -> str:
13
13
  """
14
14
  Get campaigns for a Meta Ads account with optional filtering.
15
15
 
@@ -20,23 +20,17 @@ async def get_campaigns(access_token: str = None, account_id: str = None, limit:
20
20
  in the API call (currently not exposed by this tool's parameters).
21
21
 
22
22
  Args:
23
- access_token: Meta API access token (optional - will use cached token if not provided)
24
23
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
24
+ access_token: Meta API access token (optional - will use cached token if not provided)
25
25
  limit: Maximum number of campaigns to return (default: 10)
26
26
  status_filter: Filter by effective status (e.g., 'ACTIVE', 'PAUSED', 'ARCHIVED').
27
27
  Maps to the 'effective_status' API parameter, which expects an array
28
28
  (this function handles the required JSON formatting). Leave empty for all statuses.
29
29
  after: Pagination cursor to get the next set of results
30
30
  """
31
- # If no account ID is specified, try to get the first one for the user
31
+ # Require explicit account_id
32
32
  if not account_id:
33
- accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
34
- accounts_data = json.loads(accounts_json)
35
-
36
- if "data" in accounts_data and accounts_data["data"]:
37
- account_id = accounts_data["data"][0]["id"]
38
- else:
39
- return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
33
+ return json.dumps({"error": "No account ID specified"}, indent=2)
40
34
 
41
35
  endpoint = f"{account_id}/campaigns"
42
36
  params = {
@@ -58,7 +52,7 @@ async def get_campaigns(access_token: str = None, account_id: str = None, limit:
58
52
 
59
53
  @mcp_server.tool()
60
54
  @meta_api_tool
61
- async def get_campaign_details(access_token: str = None, campaign_id: str = None) -> str:
55
+ async def get_campaign_details(campaign_id: str, access_token: Optional[str] = None) -> str:
62
56
  """
63
57
  Get detailed information about a specific campaign.
64
58
 
@@ -67,8 +61,8 @@ async def get_campaign_details(access_token: str = None, campaign_id: str = None
67
61
  that could be added to the 'fields' parameter in the code if needed.
68
62
 
69
63
  Args:
70
- access_token: Meta API access token (optional - will use cached token if not provided)
71
64
  campaign_id: Meta Ads campaign ID
65
+ access_token: Meta API access token (optional - will use cached token if not provided)
72
66
  """
73
67
  if not campaign_id:
74
68
  return json.dumps({"error": "No campaign ID provided"}, indent=2)
@@ -86,19 +80,19 @@ async def get_campaign_details(access_token: str = None, campaign_id: str = None
86
80
  @mcp_server.tool()
87
81
  @meta_api_tool
88
82
  async def create_campaign(
89
- access_token: str = None,
90
- account_id: str = None,
91
- name: str = None,
92
- objective: str = None,
83
+ account_id: str,
84
+ name: str,
85
+ objective: str,
86
+ access_token: Optional[str] = None,
93
87
  status: str = "PAUSED",
94
- special_ad_categories: List[str] = None,
95
- daily_budget = None,
96
- lifetime_budget = None,
97
- buying_type: str = None,
98
- bid_strategy: str = None,
99
- bid_cap = None,
100
- spend_cap = None,
101
- campaign_budget_optimization: bool = None,
88
+ special_ad_categories: Optional[List[str]] = None,
89
+ daily_budget: Optional[int] = None,
90
+ lifetime_budget: Optional[int] = None,
91
+ buying_type: Optional[str] = None,
92
+ bid_strategy: Optional[str] = None,
93
+ bid_cap: Optional[int] = None,
94
+ spend_cap: Optional[int] = None,
95
+ campaign_budget_optimization: Optional[bool] = None,
102
96
  ab_test_control_setups: Optional[List[Dict[str, Any]]] = None,
103
97
  use_adset_level_budgets: bool = False
104
98
  ) -> str:
@@ -106,7 +100,6 @@ async def create_campaign(
106
100
  Create a new campaign in a Meta Ads account.
107
101
 
108
102
  Args:
109
- access_token: Meta API access token (optional - will use cached token if not provided)
110
103
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
111
104
  name: Campaign name
112
105
  objective: Campaign objective (ODAX, outcome-based). Must be one of:
@@ -116,6 +109,7 @@ async def create_campaign(
116
109
  CONVERSIONS, APP_INSTALLS, etc. are not valid for new
117
110
  campaigns and will cause a 400 error. Use the outcome-based
118
111
  values above (e.g., BRAND_AWARENESS → OUTCOME_AWARENESS).
112
+ access_token: Meta API access token (optional - will use cached token if not provided)
119
113
  status: Initial campaign status (default: PAUSED)
120
114
  special_ad_categories: List of special ad categories if applicable
121
115
  daily_budget: Daily budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False)
@@ -204,26 +198,26 @@ async def create_campaign(
204
198
  @mcp_server.tool()
205
199
  @meta_api_tool
206
200
  async def update_campaign(
207
- access_token: str = None,
208
- campaign_id: str = None,
209
- name: str = None,
210
- status: str = None,
211
- special_ad_categories: List[str] = None,
212
- daily_budget = None,
213
- lifetime_budget = None,
214
- bid_strategy: str = None,
215
- bid_cap = None,
216
- spend_cap = None,
217
- campaign_budget_optimization: bool = None,
218
- objective: str = None, # Add objective if it's updatable
219
- use_adset_level_budgets: bool = None, # Add other updatable fields as needed based on API docs
201
+ campaign_id: str,
202
+ access_token: Optional[str] = None,
203
+ name: Optional[str] = None,
204
+ status: Optional[str] = None,
205
+ special_ad_categories: Optional[List[str]] = None,
206
+ daily_budget: Optional[int] = None,
207
+ lifetime_budget: Optional[int] = None,
208
+ bid_strategy: Optional[str] = None,
209
+ bid_cap: Optional[int] = None,
210
+ spend_cap: Optional[int] = None,
211
+ campaign_budget_optimization: Optional[bool] = None,
212
+ objective: Optional[str] = None, # Add objective if it's updatable
213
+ use_adset_level_budgets: Optional[bool] = None, # Add other updatable fields as needed based on API docs
220
214
  ) -> str:
221
215
  """
222
216
  Update an existing campaign in a Meta Ads account.
223
217
 
224
218
  Args:
219
+ campaign_id: Meta Ads campaign ID
225
220
  access_token: Meta API access token (optional - will use cached token if not provided)
226
- campaign_id: Meta Ads campaign ID (required)
227
221
  name: New campaign name
228
222
  status: New campaign status (e.g., 'ACTIVE', 'PAUSED')
229
223
  special_ad_categories: List of special ad categories if applicable
@@ -18,7 +18,7 @@ if ENABLE_DUPLICATION:
18
18
  @meta_api_tool
19
19
  async def duplicate_campaign(
20
20
  campaign_id: str,
21
- access_token: str = None,
21
+ access_token: Optional[str] = None,
22
22
  name_suffix: Optional[str] = " - Copy",
23
23
  include_ad_sets: bool = True,
24
24
  include_ads: bool = True,
@@ -61,7 +61,7 @@ if ENABLE_DUPLICATION:
61
61
  @meta_api_tool
62
62
  async def duplicate_adset(
63
63
  adset_id: str,
64
- access_token: str = None,
64
+ access_token: Optional[str] = None,
65
65
  target_campaign_id: Optional[str] = None,
66
66
  name_suffix: Optional[str] = " - Copy",
67
67
  include_ads: bool = True,
@@ -104,7 +104,7 @@ if ENABLE_DUPLICATION:
104
104
  @meta_api_tool
105
105
  async def duplicate_ad(
106
106
  ad_id: str,
107
- access_token: str = None,
107
+ access_token: Optional[str] = None,
108
108
  target_adset_id: Optional[str] = None,
109
109
  name_suffix: Optional[str] = " - Copy",
110
110
  duplicate_creative: bool = True,
@@ -141,7 +141,7 @@ if ENABLE_DUPLICATION:
141
141
  @meta_api_tool
142
142
  async def duplicate_creative(
143
143
  creative_id: str,
144
- access_token: str = None,
144
+ access_token: Optional[str] = None,
145
145
  name_suffix: Optional[str] = " - Copy",
146
146
  new_primary_text: Optional[str] = None,
147
147
  new_headline: Optional[str] = None,
@@ -11,15 +11,15 @@ import datetime
11
11
 
12
12
  @mcp_server.tool()
13
13
  @meta_api_tool
14
- async def get_insights(access_token: str = None, object_id: str = None,
14
+ async def get_insights(object_id: str, access_token: Optional[str] = None,
15
15
  time_range: Union[str, Dict[str, str]] = "maximum", breakdown: str = "",
16
16
  level: str = "ad") -> str:
17
17
  """
18
18
  Get performance insights for a campaign, ad set, ad or account.
19
19
 
20
20
  Args:
21
- access_token: Meta API access token (optional - will use cached token if not provided)
22
21
  object_id: ID of the campaign, ad set, ad or account
22
+ access_token: Meta API access token (optional - will use cached token if not provided)
23
23
  time_range: Either a preset time range string or a dictionary with "since" and "until" dates in YYYY-MM-DD format
24
24
  Preset options: today, yesterday, this_month, last_month, this_quarter, maximum, data_maximum,
25
25
  last_3d, last_7d, last_14d, last_28d, last_30d, last_90d, last_week_mon_sun,
@@ -310,8 +310,8 @@ _data_manager = MetaAdsDataManager()
310
310
  @mcp_server.tool()
311
311
  @meta_api_tool
312
312
  async def search(
313
- access_token: str = None,
314
- query: str = None
313
+ query: str,
314
+ access_token: Optional[str] = None
315
315
  ) -> str:
316
316
  """
317
317
  Search through Meta Ads data and return matching record IDs.
@@ -321,8 +321,8 @@ async def search(
321
321
  based on the provided query.
322
322
 
323
323
  Args:
324
- access_token: Meta API access token (optional - will use cached token if not provided)
325
324
  query: Search query string to find relevant Meta Ads records
325
+ access_token: Meta API access token (optional - will use cached token if not provided)
326
326
 
327
327
  Returns:
328
328
  JSON response with list of matching record IDs
@@ -367,7 +367,7 @@ async def search(
367
367
 
368
368
  @mcp_server.tool()
369
369
  async def fetch(
370
- id: str = None
370
+ id: str
371
371
  ) -> str:
372
372
  """
373
373
  Fetch complete record data by ID.
@@ -21,7 +21,7 @@ logger.info(f"Pipeboard API base URL: {PIPEBOARD_API_BASE}")
21
21
 
22
22
  class TokenInfo:
23
23
  """Stores token information including expiration"""
24
- def __init__(self, access_token: str, expires_at: str = None, token_type: str = None):
24
+ def __init__(self, access_token: str, expires_at: Optional[str] = None, token_type: Optional[str] = None):
25
25
  self.access_token = access_token
26
26
  self.expires_at = expires_at
27
27
  self.token_type = token_type
@@ -13,8 +13,8 @@ ENABLE_REPORT_GENERATION = bool(os.environ.get("META_ADS_ENABLE_REPORTS", ""))
13
13
  if ENABLE_REPORT_GENERATION:
14
14
  @mcp_server.tool()
15
15
  async def generate_report(
16
- access_token: str = None,
17
- account_id: str = None,
16
+ account_id: str,
17
+ access_token: Optional[str] = None,
18
18
  report_type: str = "account",
19
19
  time_range: str = "last_30d",
20
20
  campaign_ids: Optional[List[str]] = None,
@@ -30,8 +30,8 @@ if ENABLE_REPORT_GENERATION:
30
30
  **This is a premium feature available with Pipeboard Pro.**
31
31
 
32
32
  Args:
33
- access_token: Meta API access token (optional - will use cached token if not provided)
34
33
  account_id: Meta Ads account ID (format: act_XXXXXXXXX)
34
+ access_token: Meta API access token (optional - will use cached token if not provided)
35
35
  report_type: Type of report to generate (account, campaign, comparison)
36
36
  time_range: Time period for the report (e.g., 'last_30d', 'last_7d', 'this_month')
37
37
  campaign_ids: Specific campaign IDs (required for campaign/comparison reports)
@@ -8,13 +8,13 @@ from .server import mcp_server
8
8
 
9
9
  @mcp_server.tool()
10
10
  @meta_api_tool
11
- async def search_interests(access_token: str = None, query: str = None, limit: int = 25) -> str:
11
+ async def search_interests(query: str, access_token: Optional[str] = None, limit: int = 25) -> str:
12
12
  """
13
13
  Search for interest targeting options by keyword.
14
14
 
15
15
  Args:
16
- access_token: Meta API access token (optional - will use cached token if not provided)
17
16
  query: Search term for interests (e.g., "baseball", "cooking", "travel")
17
+ access_token: Meta API access token (optional - will use cached token if not provided)
18
18
  limit: Maximum number of results to return (default: 25)
19
19
 
20
20
  Returns:
@@ -37,13 +37,13 @@ async def search_interests(access_token: str = None, query: str = None, limit: i
37
37
 
38
38
  @mcp_server.tool()
39
39
  @meta_api_tool
40
- async def get_interest_suggestions(access_token: str = None, interest_list: List[str] = None, limit: int = 25) -> str:
40
+ async def get_interest_suggestions(interest_list: List[str], access_token: Optional[str] = None, limit: int = 25) -> str:
41
41
  """
42
42
  Get interest suggestions based on existing interests.
43
43
 
44
44
  Args:
45
- access_token: Meta API access token (optional - will use cached token if not provided)
46
45
  interest_list: List of interest names to get suggestions for (e.g., ["Basketball", "Soccer"])
46
+ access_token: Meta API access token (optional - will use cached token if not provided)
47
47
  limit: Maximum number of suggestions to return (default: 25)
48
48
 
49
49
  Returns:
@@ -67,13 +67,13 @@ async def get_interest_suggestions(access_token: str = None, interest_list: List
67
67
  @mcp_server.tool()
68
68
  @meta_api_tool
69
69
  async def estimate_audience_size(
70
- access_token: str = None,
71
- account_id: str = None,
72
- targeting: Dict[str, Any] = None,
70
+ access_token: Optional[str] = None,
71
+ account_id: Optional[str] = None,
72
+ targeting: Optional[Dict[str, Any]] = None,
73
73
  optimization_goal: str = "REACH",
74
74
  # Backwards compatibility for simple interest validation
75
- interest_list: List[str] = None,
76
- interest_fbid_list: List[str] = None
75
+ interest_list: Optional[List[str]] = None,
76
+ interest_fbid_list: Optional[List[str]] = None
77
77
  ) -> str:
78
78
  """
79
79
  Estimate audience size for targeting specifications using Meta's delivery_estimate API.
@@ -247,7 +247,7 @@ async def estimate_audience_size(
247
247
 
248
248
  @mcp_server.tool()
249
249
  @meta_api_tool
250
- async def search_behaviors(access_token: str = None, limit: int = 50) -> str:
250
+ async def search_behaviors(access_token: Optional[str] = None, limit: int = 50) -> str:
251
251
  """
252
252
  Get all available behavior targeting options.
253
253
 
@@ -272,7 +272,7 @@ async def search_behaviors(access_token: str = None, limit: int = 50) -> str:
272
272
 
273
273
  @mcp_server.tool()
274
274
  @meta_api_tool
275
- async def search_demographics(access_token: str = None, demographic_class: str = "demographics", limit: int = 50) -> str:
275
+ async def search_demographics(access_token: Optional[str] = None, demographic_class: str = "demographics", limit: int = 50) -> str:
276
276
  """
277
277
  Get demographic targeting options.
278
278
 
@@ -299,14 +299,14 @@ async def search_demographics(access_token: str = None, demographic_class: str =
299
299
 
300
300
  @mcp_server.tool()
301
301
  @meta_api_tool
302
- async def search_geo_locations(access_token: str = None, query: str = None,
303
- location_types: List[str] = None, limit: int = 25) -> str:
302
+ async def search_geo_locations(query: str, access_token: Optional[str] = None,
303
+ location_types: Optional[List[str]] = None, limit: int = 25) -> str:
304
304
  """
305
305
  Search for geographic targeting locations.
306
306
 
307
307
  Args:
308
- access_token: Meta API access token (optional - will use cached token if not provided)
309
308
  query: Search term for locations (e.g., "New York", "California", "Japan")
309
+ access_token: Meta API access token (optional - will use cached token if not provided)
310
310
  location_types: Types of locations to search. Options: ['country', 'region', 'city', 'zip',
311
311
  'geo_market', 'electoral_district']. If not specified, searches all types.
312
312
  limit: Maximum number of results to return (default: 25)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.10.9
3
+ Version: 0.11.1
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
@@ -0,0 +1,28 @@
1
+ meta_ads_mcp/__init__.py,sha256=PjB9-VtSzMqi_4ayhHLep5t9KBHvAdrVigxMbUGMNrQ,1477
2
+ meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
+ meta_ads_mcp/core/__init__.py,sha256=IEJtqpyUo0CZSUWeQPljQ-D2vKorTFwXnpBQWSi1hIM,1819
4
+ meta_ads_mcp/core/accounts.py,sha256=7Zoqq0zMIJi_Xsxe9-_b3EYx-UTeieJJvO7HxVRuUS0,4327
5
+ meta_ads_mcp/core/ads.py,sha256=yQl6YssmBhvEK_Sciy7xpcTBKQeVGD6TQGYyJI2bMI4,60129
6
+ meta_ads_mcp/core/ads_library.py,sha256=smGz9FhM6RIUjlQT4Jv1BaZmXahGdK21eRCB7QMhK-4,3228
7
+ meta_ads_mcp/core/adsets.py,sha256=HYaDv1AlTnHoAynT5hNruHu93pAZRjM7cDAffQMQgdU,15850
8
+ meta_ads_mcp/core/api.py,sha256=58F6fRrg3ny_vuLgHu1ZN1yueNAxVXz_nEcF6JlxlWk,16469
9
+ meta_ads_mcp/core/auth.py,sha256=l_IvejK2KYXg8yhBiP0ifE6mGwJ6ZujqYQbVw1KOUME,23649
10
+ meta_ads_mcp/core/authentication.py,sha256=ftoKec1HpfYCCVYIVKUD3ezvVAk6n_CJlBuePn8fzpM,10547
11
+ meta_ads_mcp/core/budget_schedules.py,sha256=FVyJuKbjUE4cmtlPJEbIwpN6JmU-1WQjc7io1y5JaHE,2911
12
+ meta_ads_mcp/core/callback_server.py,sha256=LIAJv9DW--83kdZ7VWWZal8xEprYjRZ8iug4rMczYbQ,9372
13
+ meta_ads_mcp/core/campaigns.py,sha256=m24epO1QmyBVBXfqHIFZAi6sRCTlOGLQckjy0azqkvo,14319
14
+ meta_ads_mcp/core/duplication.py,sha256=wtLLuO5ohbuXO79g-CRdS-_yNLUCZwRkJxukhufdH9g,19024
15
+ meta_ads_mcp/core/http_auth_integration.py,sha256=lGpKhfzJcyWugBcYEvypY-qnlt-3UDBLqh7xAUH0DGw,12473
16
+ meta_ads_mcp/core/insights.py,sha256=O8eldZG7wi46_heVgnOGiHPK3M_YNzvfT81wZQt9m0Q,4710
17
+ meta_ads_mcp/core/openai_deep_research.py,sha256=mqnDK72GXLyH6noJSOXKXnvYGInlk2bst8dOcYLnZ9A,18803
18
+ meta_ads_mcp/core/pipeboard_auth.py,sha256=vv0yc4RBcGOm7VOovud-QNV1JmvBF-njOKICzAOlyf0,24602
19
+ meta_ads_mcp/core/reports.py,sha256=2pXYCCjYc3MZ8GlrbSHND436W62WlbfbtMll1dfJdqE,5750
20
+ meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0w,1236
21
+ meta_ads_mcp/core/server.py,sha256=9SlgM_qvdlxo24ctnZzLgW1e1nfAspCSx3YyJQkKP64,17856
22
+ meta_ads_mcp/core/targeting.py,sha256=-QziS2MvTWzM02pwiUtId4sblWURd3UYPR_YYfVuiUk,13913
23
+ meta_ads_mcp/core/utils.py,sha256=ytj41yC5SqduLrAiZYBSd6OUwlJRaIClTwnnYKpNFds,9387
24
+ meta_ads_mcp-0.11.1.dist-info/METADATA,sha256=aw2oEknvxU62yHKlcJr_zmECgks-AKFu7DDEjVr8BtI,24265
25
+ meta_ads_mcp-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ meta_ads_mcp-0.11.1.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
+ meta_ads_mcp-0.11.1.dist-info/licenses/LICENSE,sha256=E2d762fbhwKRYn8o7J6Szr6vyBPrHVDlK3jbHPx-d84,3851
28
+ meta_ads_mcp-0.11.1.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- meta_ads_mcp/__init__.py,sha256=Zh3K7XVia0Eh3hCmSqoEJaSwTVxDo3tP5ztVCVl6BhA,1477
2
- meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
- meta_ads_mcp/core/__init__.py,sha256=IEJtqpyUo0CZSUWeQPljQ-D2vKorTFwXnpBQWSi1hIM,1819
4
- meta_ads_mcp/core/accounts.py,sha256=0lfyvRbhBigFKgNq5Mk3fk4Qh6MtIkKScdOnoc_rQkg,4314
5
- meta_ads_mcp/core/ads.py,sha256=XRaFs60I7kKkBlQyodBCWodQ8-Lef6MVuZF8Vx6XVEs,59456
6
- meta_ads_mcp/core/ads_library.py,sha256=BBGVbtjO5eFV42iiY3XPU-wIV8HupzUKpHgPBrydSvU,3232
7
- meta_ads_mcp/core/adsets.py,sha256=kfggH1F2KD19iPDErE0QdKw2aVX7F409EPLDTM7MKnQ,16024
8
- meta_ads_mcp/core/api.py,sha256=kWpIafvSsxnesfb5TqndA7ozKoIspby5e_6Jl23L7hY,16447
9
- meta_ads_mcp/core/auth.py,sha256=2CjFbxpJM3OR3OzCipB8l_-l2xQ1nioGfdI3ZDMnjHM,23629
10
- meta_ads_mcp/core/authentication.py,sha256=o5YMW0xmoTDiXDmpgVPo4mYSxCil6aGMQHbIeHxj_kg,10509
11
- meta_ads_mcp/core/budget_schedules.py,sha256=UxseExsvKAiPwfDCY9aycT4kys4xqeNytyq-yyDOxrs,2901
12
- meta_ads_mcp/core/callback_server.py,sha256=LIAJv9DW--83kdZ7VWWZal8xEprYjRZ8iug4rMczYbQ,9372
13
- meta_ads_mcp/core/campaigns.py,sha256=TUPTAvY89uU4REewDtL6EWL2Mk9LQVoaUjbg5rfj5qY,14461
14
- meta_ads_mcp/core/duplication.py,sha256=UUmTDFx9o5ZsPQG2Rb9c4ZyuKUVN3FfTjebfTIHHdo4,18984
15
- meta_ads_mcp/core/http_auth_integration.py,sha256=lGpKhfzJcyWugBcYEvypY-qnlt-3UDBLqh7xAUH0DGw,12473
16
- meta_ads_mcp/core/insights.py,sha256=unhcCaYjgsir62llCdIDg0F-PHISiHune08uYG5IXTM,4707
17
- meta_ads_mcp/core/openai_deep_research.py,sha256=9w3F2Mqj-hYOcvFlnG0tnALlW12VwavDMMpO7LqApd0,18807
18
- meta_ads_mcp/core/pipeboard_auth.py,sha256=ZwEQy8r0TwobFRQ5gmlSjhIfvlUmMtfWNlpQjXCUhl0,24582
19
- meta_ads_mcp/core/reports.py,sha256=Dv3hfsPOR7IZ9WrYrKd_6SNgZl-USIphg7knva3UYAw,5747
20
- meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0w,1236
21
- meta_ads_mcp/core/server.py,sha256=9SlgM_qvdlxo24ctnZzLgW1e1nfAspCSx3YyJQkKP64,17856
22
- meta_ads_mcp/core/targeting.py,sha256=nl_JVXNhCu85ho5ssklZiQYC3iL_oosdlpEuyCOVXcM,13824
23
- meta_ads_mcp/core/utils.py,sha256=ytj41yC5SqduLrAiZYBSd6OUwlJRaIClTwnnYKpNFds,9387
24
- meta_ads_mcp-0.10.9.dist-info/METADATA,sha256=Iz6cmxL8TTF3nyilJwoXRAUDApndnnLtgcnUokEUckU,24265
25
- meta_ads_mcp-0.10.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- meta_ads_mcp-0.10.9.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
- meta_ads_mcp-0.10.9.dist-info/licenses/LICENSE,sha256=E2d762fbhwKRYn8o7J6Szr6vyBPrHVDlK3jbHPx-d84,3851
28
- meta_ads_mcp-0.10.9.dist-info/RECORD,,