meta-ads-mcp 0.2.0__py3-none-any.whl → 0.2.2__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
@@ -1,9 +1,18 @@
1
- """Meta Ads MCP - Model Calling Protocol plugin for Meta Ads API."""
1
+ """
2
+ Meta Ads MCP - Python Package
2
3
 
3
- __version__ = "0.2.0"
4
+ This package provides a Meta Ads Marketing Cloud Platform (MCP) integration
5
+ with the Claude LLM.
6
+ """
7
+
8
+ from meta_ads_mcp.core.server import main
9
+
10
+ __version__ = "0.2.2"
11
+
12
+ __all__ = ["main"]
4
13
 
5
14
  # Import key functions to make them available at package level
6
- from .api import (
15
+ from .core import (
7
16
  get_ad_accounts,
8
17
  get_account_info,
9
18
  get_campaigns,
@@ -11,17 +20,22 @@ from .api import (
11
20
  create_campaign,
12
21
  get_adsets,
13
22
  get_adset_details,
23
+ update_adset,
14
24
  get_ads,
15
25
  get_ad_details,
16
26
  get_ad_creatives,
17
27
  get_ad_image,
18
28
  get_insights,
29
+ debug_image_download,
30
+ save_ad_image_via_api,
19
31
  get_login_link,
20
32
  login_cli,
21
- main, # Import main function for entry point
22
33
  )
23
34
 
24
35
  # Define a main function to be used as a package entry point
25
36
  def entrypoint():
26
37
  """Main entry point for the package when invoked with uvx."""
27
- return main()
38
+ return main()
39
+
40
+ # Re-export main for direct access
41
+ main = main
@@ -0,0 +1,34 @@
1
+ """Core functionality for Meta Ads API MCP package."""
2
+
3
+ from .server import mcp_server
4
+ from .accounts import get_ad_accounts, get_account_info
5
+ from .campaigns import get_campaigns, get_campaign_details, create_campaign
6
+ from .adsets import get_adsets, get_adset_details, update_adset
7
+ from .ads import get_ads, get_ad_details, get_ad_creatives, get_ad_image
8
+ from .insights import get_insights, debug_image_download, save_ad_image_via_api
9
+ from .authentication import get_login_link
10
+ from .server import login_cli, main
11
+ from .auth import login
12
+
13
+ __all__ = [
14
+ 'mcp_server',
15
+ 'get_ad_accounts',
16
+ 'get_account_info',
17
+ 'get_campaigns',
18
+ 'get_campaign_details',
19
+ 'create_campaign',
20
+ 'get_adsets',
21
+ 'get_adset_details',
22
+ 'update_adset',
23
+ 'get_ads',
24
+ 'get_ad_details',
25
+ 'get_ad_creatives',
26
+ 'get_ad_image',
27
+ 'get_insights',
28
+ 'debug_image_download',
29
+ 'save_ad_image_via_api',
30
+ 'get_login_link',
31
+ 'login_cli',
32
+ 'login',
33
+ 'main',
34
+ ]
@@ -0,0 +1,59 @@
1
+ """Account-related functionality for Meta Ads API."""
2
+
3
+ import json
4
+ from typing import Optional
5
+ from .api import meta_api_tool, make_api_request
6
+
7
+
8
+ @meta_api_tool
9
+ async def get_ad_accounts(access_token: str = None, user_id: str = "me", limit: int = 10) -> str:
10
+ """
11
+ Get ad accounts accessible by a user.
12
+
13
+ Args:
14
+ access_token: Meta API access token (optional - will use cached token if not provided)
15
+ user_id: Meta user ID or "me" for the current user
16
+ limit: Maximum number of accounts to return (default: 10)
17
+ """
18
+ endpoint = f"{user_id}/adaccounts"
19
+ params = {
20
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,business_city,business_country_code",
21
+ "limit": limit
22
+ }
23
+
24
+ data = await make_api_request(endpoint, access_token, params)
25
+
26
+ return json.dumps(data, indent=2)
27
+
28
+
29
+ @meta_api_tool
30
+ async def get_account_info(access_token: str = None, account_id: str = None) -> str:
31
+ """
32
+ Get detailed information about a specific ad account.
33
+
34
+ Args:
35
+ access_token: Meta API access token (optional - will use cached token if not provided)
36
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX)
37
+ """
38
+ # If no account ID is specified, try to get the first one for the user
39
+ if not account_id:
40
+ accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
41
+ accounts_data = json.loads(accounts_json)
42
+
43
+ if "data" in accounts_data and accounts_data["data"]:
44
+ account_id = accounts_data["data"][0]["id"]
45
+ else:
46
+ return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
47
+
48
+ # Ensure account_id has the 'act_' prefix for API compatibility
49
+ if not account_id.startswith("act_"):
50
+ account_id = f"act_{account_id}"
51
+
52
+ endpoint = f"{account_id}"
53
+ params = {
54
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,funding_source_details,business_city,business_country_code,timezone_name,owner"
55
+ }
56
+
57
+ data = await make_api_request(endpoint, access_token, params)
58
+
59
+ return json.dumps(data, indent=2)
@@ -0,0 +1,361 @@
1
+ """Ad and Creative-related functionality for Meta Ads API."""
2
+
3
+ import json
4
+ from typing import Optional, Dict, Any
5
+ import io
6
+ from PIL import Image as PILImage
7
+ from mcp.server.fastmcp import Image
8
+
9
+ from .api import meta_api_tool, make_api_request
10
+ from .accounts import get_ad_accounts
11
+ from .utils import download_image, try_multiple_download_methods, ad_creative_images
12
+
13
+
14
+ @meta_api_tool
15
+ async def get_ads(access_token: str = None, account_id: str = None, limit: int = 10,
16
+ campaign_id: str = "", adset_id: str = "") -> str:
17
+ """
18
+ Get ads for a Meta Ads account with optional filtering.
19
+
20
+ Args:
21
+ access_token: Meta API access token (optional - will use cached token if not provided)
22
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX)
23
+ limit: Maximum number of ads to return (default: 10)
24
+ campaign_id: Optional campaign ID to filter by
25
+ adset_id: Optional ad set ID to filter by
26
+ """
27
+ # If no account ID is specified, try to get the first one for the user
28
+ if not account_id:
29
+ accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
30
+ accounts_data = json.loads(accounts_json)
31
+
32
+ if "data" in accounts_data and accounts_data["data"]:
33
+ account_id = accounts_data["data"][0]["id"]
34
+ else:
35
+ return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
36
+
37
+ endpoint = f"{account_id}/ads"
38
+ params = {
39
+ "fields": "id,name,adset_id,campaign_id,status,creative,created_time,updated_time,bid_amount,conversion_domain,tracking_specs",
40
+ "limit": limit
41
+ }
42
+
43
+ if campaign_id:
44
+ params["campaign_id"] = campaign_id
45
+
46
+ if adset_id:
47
+ params["adset_id"] = adset_id
48
+
49
+ data = await make_api_request(endpoint, access_token, params)
50
+
51
+ return json.dumps(data, indent=2)
52
+
53
+
54
+ @meta_api_tool
55
+ async def get_ad_details(access_token: str = None, ad_id: str = None) -> str:
56
+ """
57
+ Get detailed information about a specific ad.
58
+
59
+ Args:
60
+ access_token: Meta API access token (optional - will use cached token if not provided)
61
+ ad_id: Meta Ads ad ID
62
+ """
63
+ if not ad_id:
64
+ return json.dumps({"error": "No ad ID provided"}, indent=2)
65
+
66
+ endpoint = f"{ad_id}"
67
+ params = {
68
+ "fields": "id,name,adset_id,campaign_id,status,creative,created_time,updated_time,bid_amount,conversion_domain,tracking_specs,preview_shareable_link"
69
+ }
70
+
71
+ data = await make_api_request(endpoint, access_token, params)
72
+
73
+ return json.dumps(data, indent=2)
74
+
75
+
76
+ @meta_api_tool
77
+ async def get_ad_creatives(access_token: str = None, ad_id: str = None) -> str:
78
+ """
79
+ Get creative details for a specific ad. Best if combined with get_ad_image to get the full image.
80
+
81
+ Args:
82
+ access_token: Meta API access token (optional - will use cached token if not provided)
83
+ ad_id: Meta Ads ad ID
84
+ """
85
+ if not ad_id:
86
+ return json.dumps({"error": "No ad ID provided"}, indent=2)
87
+
88
+ # First, get the creative ID from the ad
89
+ endpoint = f"{ad_id}"
90
+ params = {
91
+ "fields": "creative"
92
+ }
93
+
94
+ ad_data = await make_api_request(endpoint, access_token, params)
95
+
96
+ if "error" in ad_data:
97
+ return json.dumps(ad_data, indent=2)
98
+
99
+ if "creative" not in ad_data:
100
+ return json.dumps({"error": "No creative found for this ad"}, indent=2)
101
+
102
+ creative_id = ad_data.get("creative", {}).get("id")
103
+ if not creative_id:
104
+ return json.dumps({"error": "Creative ID not found", "ad_data": ad_data}, indent=2)
105
+
106
+ # Now get the creative details with essential fields
107
+ creative_endpoint = f"{creative_id}"
108
+ creative_params = {
109
+ "fields": "id,name,title,body,image_url,object_story_spec,url_tags,link_url,thumbnail_url,image_hash,asset_feed_spec,object_type"
110
+ }
111
+
112
+ creative_data = await make_api_request(creative_endpoint, access_token, creative_params)
113
+
114
+ # Try to get full-size images in different ways:
115
+
116
+ # 1. First approach: Get ad images directly using the adimages endpoint
117
+ if "image_hash" in creative_data:
118
+ image_hash = creative_data.get("image_hash")
119
+ image_endpoint = f"act_{ad_data.get('account_id', '')}/adimages"
120
+ image_params = {
121
+ "hashes": [image_hash]
122
+ }
123
+ image_data = await make_api_request(image_endpoint, access_token, image_params)
124
+ if "data" in image_data and len(image_data["data"]) > 0:
125
+ creative_data["full_image_url"] = image_data["data"][0].get("url")
126
+
127
+ # 2. For creatives with object_story_spec
128
+ if "object_story_spec" in creative_data:
129
+ spec = creative_data.get("object_story_spec", {})
130
+
131
+ # For link ads
132
+ if "link_data" in spec:
133
+ link_data = spec.get("link_data", {})
134
+ # If there's an explicit image_url, use it
135
+ if "image_url" in link_data:
136
+ creative_data["full_image_url"] = link_data.get("image_url")
137
+ # If there's an image_hash, try to get the full image
138
+ elif "image_hash" in link_data:
139
+ image_hash = link_data.get("image_hash")
140
+ account_id = ad_data.get('account_id', '')
141
+ if not account_id:
142
+ # Try to get account ID from ad ID
143
+ ad_details_endpoint = f"{ad_id}"
144
+ ad_details_params = {
145
+ "fields": "account_id"
146
+ }
147
+ ad_details = await make_api_request(ad_details_endpoint, access_token, ad_details_params)
148
+ account_id = ad_details.get('account_id', '')
149
+
150
+ if account_id:
151
+ image_endpoint = f"act_{account_id}/adimages"
152
+ image_params = {
153
+ "hashes": [image_hash]
154
+ }
155
+ image_data = await make_api_request(image_endpoint, access_token, image_params)
156
+ if "data" in image_data and len(image_data["data"]) > 0:
157
+ creative_data["full_image_url"] = image_data["data"][0].get("url")
158
+
159
+ # For photo ads
160
+ if "photo_data" in spec:
161
+ photo_data = spec.get("photo_data", {})
162
+ if "image_hash" in photo_data:
163
+ image_hash = photo_data.get("image_hash")
164
+ account_id = ad_data.get('account_id', '')
165
+ if not account_id:
166
+ # Try to get account ID from ad ID
167
+ ad_details_endpoint = f"{ad_id}"
168
+ ad_details_params = {
169
+ "fields": "account_id"
170
+ }
171
+ ad_details = await make_api_request(ad_details_endpoint, access_token, ad_details_params)
172
+ account_id = ad_details.get('account_id', '')
173
+
174
+ if account_id:
175
+ image_endpoint = f"act_{account_id}/adimages"
176
+ image_params = {
177
+ "hashes": [image_hash]
178
+ }
179
+ image_data = await make_api_request(image_endpoint, access_token, image_params)
180
+ if "data" in image_data and len(image_data["data"]) > 0:
181
+ creative_data["full_image_url"] = image_data["data"][0].get("url")
182
+
183
+ # 3. If there's an asset_feed_spec, try to get images from there
184
+ if "asset_feed_spec" in creative_data and "images" in creative_data["asset_feed_spec"]:
185
+ images = creative_data["asset_feed_spec"]["images"]
186
+ if images and len(images) > 0 and "hash" in images[0]:
187
+ image_hash = images[0]["hash"]
188
+ account_id = ad_data.get('account_id', '')
189
+ if not account_id:
190
+ # Try to get account ID
191
+ ad_details_endpoint = f"{ad_id}"
192
+ ad_details_params = {
193
+ "fields": "account_id"
194
+ }
195
+ ad_details = await make_api_request(ad_details_endpoint, access_token, ad_details_params)
196
+ account_id = ad_details.get('account_id', '')
197
+
198
+ if account_id:
199
+ image_endpoint = f"act_{account_id}/adimages"
200
+ image_params = {
201
+ "hashes": [image_hash]
202
+ }
203
+ image_data = await make_api_request(image_endpoint, access_token, image_params)
204
+ if "data" in image_data and len(image_data["data"]) > 0:
205
+ creative_data["full_image_url"] = image_data["data"][0].get("url")
206
+
207
+ # If we have a thumbnail_url but no full_image_url, let's attempt to convert the thumbnail URL to full size
208
+ if "thumbnail_url" in creative_data and "full_image_url" not in creative_data:
209
+ thumbnail_url = creative_data["thumbnail_url"]
210
+ # Try to convert the URL to get higher resolution by removing size parameters
211
+ if "p64x64" in thumbnail_url:
212
+ full_url = thumbnail_url.replace("p64x64", "p1080x1080")
213
+ creative_data["full_image_url"] = full_url
214
+ elif "dst-emg0" in thumbnail_url:
215
+ # Remove the dst-emg0 parameter that seems to reduce size
216
+ full_url = thumbnail_url.replace("dst-emg0_", "")
217
+ creative_data["full_image_url"] = full_url
218
+
219
+ # Fallback to using thumbnail or image_url if we still don't have a full image
220
+ if "full_image_url" not in creative_data:
221
+ if "thumbnail_url" in creative_data:
222
+ creative_data["full_image_url"] = creative_data["thumbnail_url"]
223
+ elif "image_url" in creative_data:
224
+ creative_data["full_image_url"] = creative_data["image_url"]
225
+
226
+ return json.dumps(creative_data, indent=2)
227
+
228
+
229
+ @meta_api_tool
230
+ async def get_ad_image(access_token: str = None, ad_id: str = None) -> Image:
231
+ """
232
+ Get, download, and visualize a Meta ad image in one step. Useful to see the image in the LLM.
233
+
234
+ Args:
235
+ access_token: Meta API access token (optional - will use cached token if not provided)
236
+ ad_id: Meta Ads ad ID
237
+
238
+ Returns:
239
+ The ad image ready for direct visual analysis
240
+ """
241
+ if not ad_id:
242
+ return "Error: No ad ID provided"
243
+
244
+ print(f"Attempting to get and analyze creative image for ad {ad_id}")
245
+
246
+ # First, get creative and account IDs
247
+ ad_endpoint = f"{ad_id}"
248
+ ad_params = {
249
+ "fields": "creative{id},account_id"
250
+ }
251
+
252
+ ad_data = await make_api_request(ad_endpoint, access_token, ad_params)
253
+
254
+ if "error" in ad_data:
255
+ return f"Error: Could not get ad data - {json.dumps(ad_data)}"
256
+
257
+ # Extract account_id
258
+ account_id = ad_data.get("account_id", "")
259
+ if not account_id:
260
+ return "Error: No account ID found"
261
+
262
+ # Extract creative ID
263
+ if "creative" not in ad_data:
264
+ return "Error: No creative found for this ad"
265
+
266
+ creative_data = ad_data.get("creative", {})
267
+ creative_id = creative_data.get("id")
268
+ if not creative_id:
269
+ return "Error: No creative ID found"
270
+
271
+ # Get creative details to find image hash
272
+ creative_endpoint = f"{creative_id}"
273
+ creative_params = {
274
+ "fields": "id,name,image_hash,asset_feed_spec"
275
+ }
276
+
277
+ creative_details = await make_api_request(creative_endpoint, access_token, creative_params)
278
+
279
+ # Identify image hashes to use from creative
280
+ image_hashes = []
281
+
282
+ # Check for direct image_hash on creative
283
+ if "image_hash" in creative_details:
284
+ image_hashes.append(creative_details["image_hash"])
285
+
286
+ # Check asset_feed_spec for image hashes - common in Advantage+ ads
287
+ if "asset_feed_spec" in creative_details and "images" in creative_details["asset_feed_spec"]:
288
+ for image in creative_details["asset_feed_spec"]["images"]:
289
+ if "hash" in image:
290
+ image_hashes.append(image["hash"])
291
+
292
+ if not image_hashes:
293
+ # If no hashes found, try to extract from the first creative we found in the API
294
+ # Get creative for ad to try to extract hash
295
+ creative_json = await get_ad_creatives(ad_id, "", access_token)
296
+ creative_data = json.loads(creative_json)
297
+
298
+ # Try to extract hash from asset_feed_spec
299
+ if "asset_feed_spec" in creative_data and "images" in creative_data["asset_feed_spec"]:
300
+ images = creative_data["asset_feed_spec"]["images"]
301
+ if images and len(images) > 0 and "hash" in images[0]:
302
+ image_hashes.append(images[0]["hash"])
303
+
304
+ if not image_hashes:
305
+ return "Error: No image hashes found in creative"
306
+
307
+ print(f"Found image hashes: {image_hashes}")
308
+
309
+ # Now fetch image data using adimages endpoint with specific format
310
+ image_endpoint = f"act_{account_id}/adimages"
311
+
312
+ # Format the hashes parameter exactly as in our successful curl test
313
+ hashes_str = f'["{image_hashes[0]}"]' # Format first hash only, as JSON string array
314
+
315
+ image_params = {
316
+ "fields": "hash,url,width,height,name,status",
317
+ "hashes": hashes_str
318
+ }
319
+
320
+ print(f"Requesting image data with params: {image_params}")
321
+ image_data = await make_api_request(image_endpoint, access_token, image_params)
322
+
323
+ if "error" in image_data:
324
+ return f"Error: Failed to get image data - {json.dumps(image_data)}"
325
+
326
+ if "data" not in image_data or not image_data["data"]:
327
+ return "Error: No image data returned from API"
328
+
329
+ # Get the first image URL
330
+ first_image = image_data["data"][0]
331
+ image_url = first_image.get("url")
332
+
333
+ if not image_url:
334
+ return "Error: No valid image URL found"
335
+
336
+ print(f"Downloading image from URL: {image_url}")
337
+
338
+ # Download the image
339
+ image_bytes = await download_image(image_url)
340
+
341
+ if not image_bytes:
342
+ return "Error: Failed to download image"
343
+
344
+ try:
345
+ # Convert bytes to PIL Image
346
+ img = PILImage.open(io.BytesIO(image_bytes))
347
+
348
+ # Convert to RGB if needed
349
+ if img.mode != "RGB":
350
+ img = img.convert("RGB")
351
+
352
+ # Create a byte stream of the image data
353
+ byte_arr = io.BytesIO()
354
+ img.save(byte_arr, format="JPEG")
355
+ img_bytes = byte_arr.getvalue()
356
+
357
+ # Return as an Image object that LLM can directly analyze
358
+ return Image(data=img_bytes, format="jpeg")
359
+
360
+ except Exception as e:
361
+ return f"Error processing image: {str(e)}"
@@ -0,0 +1,115 @@
1
+ """Ad Set-related functionality for Meta Ads API."""
2
+
3
+ import json
4
+ from typing import Optional, Dict, Any, List
5
+ from .api import meta_api_tool, make_api_request
6
+ from .accounts import get_ad_accounts
7
+
8
+
9
+ @meta_api_tool
10
+ async def get_adsets(access_token: str = None, account_id: str = None, limit: int = 10, campaign_id: str = "") -> str:
11
+ """
12
+ Get ad sets for a Meta Ads account with optional filtering by campaign.
13
+
14
+ Args:
15
+ access_token: Meta API access token (optional - will use cached token if not provided)
16
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX)
17
+ limit: Maximum number of ad sets to return (default: 10)
18
+ campaign_id: Optional campaign ID to filter by
19
+ """
20
+ # If no account ID is specified, try to get the first one for the user
21
+ if not account_id:
22
+ accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
23
+ accounts_data = json.loads(accounts_json)
24
+
25
+ if "data" in accounts_data and accounts_data["data"]:
26
+ account_id = accounts_data["data"][0]["id"]
27
+ else:
28
+ return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
29
+
30
+ endpoint = f"{account_id}/adsets"
31
+ params = {
32
+ "fields": "id,name,campaign_id,status,daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time",
33
+ "limit": limit
34
+ }
35
+
36
+ if campaign_id:
37
+ params["campaign_id"] = campaign_id
38
+
39
+ data = await make_api_request(endpoint, access_token, params)
40
+
41
+ return json.dumps(data, indent=2)
42
+
43
+
44
+ @meta_api_tool
45
+ async def get_adset_details(access_token: str = None, adset_id: str = None) -> str:
46
+ """
47
+ Get detailed information about a specific ad set.
48
+
49
+ Args:
50
+ adset_id: Meta Ads ad set ID (required)
51
+ access_token: Meta API access token (optional - will use cached token if not provided)
52
+
53
+ Example:
54
+ To call this function through MCP, pass the adset_id as the first argument:
55
+ {
56
+ "args": "YOUR_ADSET_ID"
57
+ }
58
+ """
59
+ if not adset_id:
60
+ return json.dumps({"error": "No ad set ID provided"}, indent=2)
61
+
62
+ endpoint = f"{adset_id}"
63
+ params = {
64
+ "fields": "id,name,campaign_id,status,daily_budget,lifetime_budget,targeting,bid_amount,bid_strategy,optimization_goal,billing_event,start_time,end_time,created_time,updated_time,attribution_spec,destination_type,promoted_object,pacing_type,budget_remaining"
65
+ }
66
+
67
+ data = await make_api_request(endpoint, access_token, params)
68
+
69
+ return json.dumps(data, indent=2)
70
+
71
+
72
+ @meta_api_tool
73
+ async def update_adset(access_token: str = None, adset_id: str = None,
74
+ bid_strategy: Optional[str] = None,
75
+ bid_amount: Optional[int] = None,
76
+ frequency_control_specs: Optional[List[Dict[str, Any]]] = None,
77
+ status: Optional[str] = None) -> str:
78
+ """
79
+ Update an existing ad set with new settings including frequency caps.
80
+
81
+ Args:
82
+ access_token: Meta API access token (optional - will use cached token if not provided)
83
+ adset_id: Meta Ads ad set ID
84
+ bid_strategy: Bid strategy (e.g., 'LOWEST_COST_WITH_BID_CAP')
85
+ bid_amount: Bid amount in your account's currency (in cents for USD)
86
+ frequency_control_specs: List of frequency control specifications. Each spec should have:
87
+ - event: Type of event (e.g., 'IMPRESSIONS')
88
+ - interval_days: Number of days for the frequency cap
89
+ - max_frequency: Maximum number of times to show the ad
90
+ status: Update ad set status (ACTIVE, PAUSED, etc.)
91
+ """
92
+ if not adset_id:
93
+ return json.dumps({"error": "No ad set ID provided"}, indent=2)
94
+
95
+ endpoint = f"{adset_id}"
96
+ params = {}
97
+
98
+ if bid_strategy:
99
+ params["bid_strategy"] = bid_strategy
100
+
101
+ if bid_amount is not None:
102
+ params["bid_amount"] = bid_amount
103
+
104
+ if frequency_control_specs:
105
+ params["frequency_control_specs"] = frequency_control_specs
106
+
107
+ if status:
108
+ params["status"] = status
109
+
110
+ if not params:
111
+ return json.dumps({"error": "No update parameters provided"}, indent=2)
112
+
113
+ data = await make_api_request(endpoint, access_token, params, method="POST")
114
+
115
+ return json.dumps(data, indent=2)