meta-ads-mcp 0.5.0__py3-none-any.whl → 0.7.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.5.0"
10
+ __version__ = "0.7.0"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -26,7 +26,13 @@ __all__ = [
26
26
  'get_insights',
27
27
  'get_login_link',
28
28
  'login_cli',
29
- 'main'
29
+ 'main',
30
+ 'search_interests',
31
+ 'get_interest_suggestions',
32
+ 'validate_interests',
33
+ 'search_behaviors',
34
+ 'search_demographics',
35
+ 'search_geo_locations'
30
36
  ]
31
37
 
32
38
  # Import key functions to make them available at package level
@@ -47,7 +53,13 @@ from .core import (
47
53
  get_insights,
48
54
  get_login_link,
49
55
  login_cli,
50
- main
56
+ main,
57
+ search_interests,
58
+ get_interest_suggestions,
59
+ validate_interests,
60
+ search_behaviors,
61
+ search_demographics,
62
+ search_geo_locations
51
63
  )
52
64
 
53
65
  # Define a main function to be used as a package entry point
@@ -11,8 +11,10 @@ from .server import login_cli, main
11
11
  from .auth import login
12
12
  from .ads_library import search_ads_archive
13
13
  from .budget_schedules import create_budget_schedule
14
+ from .targeting import search_interests, get_interest_suggestions, validate_interests, search_behaviors, search_demographics, search_geo_locations
14
15
  from . import reports # Import module to register conditional tools
15
16
  from . import duplication # Import module to register conditional duplication tools
17
+ from .openai_deep_research import search, fetch # OpenAI MCP Deep Research tools
16
18
 
17
19
  __all__ = [
18
20
  'mcp_server',
@@ -36,4 +38,12 @@ __all__ = [
36
38
  'main',
37
39
  'search_ads_archive',
38
40
  'create_budget_schedule',
41
+ 'search_interests',
42
+ 'get_interest_suggestions',
43
+ 'validate_interests',
44
+ 'search_behaviors',
45
+ 'search_demographics',
46
+ 'search_geo_locations',
47
+ 'search', # OpenAI MCP Deep Research search tool
48
+ 'fetch', # OpenAI MCP Deep Research fetch tool
39
49
  ]
@@ -0,0 +1,316 @@
1
+ """OpenAI MCP Deep Research tools for Meta Ads API.
2
+
3
+ This module implements the required 'search' and 'fetch' tools for OpenAI's
4
+ ChatGPT Deep Research feature, providing access to Meta Ads data in the format
5
+ expected by ChatGPT.
6
+
7
+ The tools expose Meta Ads data (accounts, campaigns, ads, etc.) as searchable
8
+ and fetchable records for ChatGPT Deep Research analysis.
9
+ """
10
+
11
+ import json
12
+ import re
13
+ from typing import List, Dict, Any, Optional
14
+ from .api import meta_api_tool, make_api_request
15
+ from .server import mcp_server
16
+ from .utils import logger
17
+
18
+
19
+ class MetaAdsDataManager:
20
+ """Manages Meta Ads data for OpenAI MCP search and fetch operations"""
21
+
22
+ def __init__(self):
23
+ self._cache = {}
24
+ logger.debug("MetaAdsDataManager initialized")
25
+
26
+ async def _get_ad_accounts(self, access_token: str, limit: int = 25) -> List[Dict[str, Any]]:
27
+ """Get ad accounts data"""
28
+ try:
29
+ endpoint = "me/adaccounts"
30
+ params = {
31
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,business_city,business_country_code",
32
+ "limit": limit
33
+ }
34
+
35
+ data = await make_api_request(endpoint, access_token, params)
36
+
37
+ if "data" in data:
38
+ return data["data"]
39
+ return []
40
+ except Exception as e:
41
+ logger.error(f"Error fetching ad accounts: {e}")
42
+ return []
43
+
44
+ async def _get_campaigns(self, access_token: str, account_id: str, limit: int = 25) -> List[Dict[str, Any]]:
45
+ """Get campaigns data for an account"""
46
+ try:
47
+ endpoint = f"{account_id}/campaigns"
48
+ params = {
49
+ "fields": "id,name,status,objective,daily_budget,lifetime_budget,start_time,stop_time,created_time,updated_time",
50
+ "limit": limit
51
+ }
52
+
53
+ data = await make_api_request(endpoint, access_token, params)
54
+
55
+ if "data" in data:
56
+ return data["data"]
57
+ return []
58
+ except Exception as e:
59
+ logger.error(f"Error fetching campaigns for {account_id}: {e}")
60
+ return []
61
+
62
+ async def _get_ads(self, access_token: str, account_id: str, limit: int = 25) -> List[Dict[str, Any]]:
63
+ """Get ads data for an account"""
64
+ try:
65
+ endpoint = f"{account_id}/ads"
66
+ params = {
67
+ "fields": "id,name,status,creative,targeting,bid_amount,created_time,updated_time",
68
+ "limit": limit
69
+ }
70
+
71
+ data = await make_api_request(endpoint, access_token, params)
72
+
73
+ if "data" in data:
74
+ return data["data"]
75
+ return []
76
+ except Exception as e:
77
+ logger.error(f"Error fetching ads for {account_id}: {e}")
78
+ return []
79
+
80
+ async def search_records(self, query: str, access_token: str) -> List[str]:
81
+ """Search Meta Ads data and return matching record IDs
82
+
83
+ Args:
84
+ query: Search query string
85
+ access_token: Meta API access token
86
+
87
+ Returns:
88
+ List of record IDs that match the query
89
+ """
90
+ logger.info(f"Searching Meta Ads data with query: {query}")
91
+
92
+ # Normalize query for matching
93
+ query_lower = query.lower()
94
+ query_terms = re.findall(r'\w+', query_lower)
95
+
96
+ matching_ids = []
97
+
98
+ try:
99
+ # Search ad accounts
100
+ accounts = await self._get_ad_accounts(access_token, limit=25)
101
+ for account in accounts:
102
+ account_text = f"{account.get('name', '')} {account.get('id', '')} {account.get('account_status', '')} {account.get('business_city', '')} {account.get('business_country_code', '')}".lower()
103
+
104
+ if any(term in account_text for term in query_terms):
105
+ record_id = f"account:{account['id']}"
106
+ matching_ids.append(record_id)
107
+
108
+ # Cache the account data
109
+ self._cache[record_id] = {
110
+ "id": record_id,
111
+ "type": "account",
112
+ "title": f"Ad Account: {account.get('name', 'Unnamed Account')}",
113
+ "text": f"Meta Ads Account {account.get('name', 'Unnamed')} (ID: {account.get('id', 'N/A')}) - Status: {account.get('account_status', 'Unknown')}, Currency: {account.get('currency', 'Unknown')}, Spent: ${account.get('amount_spent', 0)}, Balance: ${account.get('balance', 0)}",
114
+ "metadata": {
115
+ "account_id": account.get('id'),
116
+ "account_name": account.get('name'),
117
+ "status": account.get('account_status'),
118
+ "currency": account.get('currency'),
119
+ "business_location": f"{account.get('business_city', '')}, {account.get('business_country_code', '')}".strip(', '),
120
+ "data_type": "meta_ads_account"
121
+ },
122
+ "raw_data": account
123
+ }
124
+
125
+ # Also search campaigns for this account if it matches
126
+ campaigns = await self._get_campaigns(access_token, account['id'], limit=10)
127
+ for campaign in campaigns:
128
+ campaign_text = f"{campaign.get('name', '')} {campaign.get('objective', '')} {campaign.get('status', '')}".lower()
129
+
130
+ if any(term in campaign_text for term in query_terms):
131
+ campaign_record_id = f"campaign:{campaign['id']}"
132
+ matching_ids.append(campaign_record_id)
133
+
134
+ # Cache the campaign data
135
+ self._cache[campaign_record_id] = {
136
+ "id": campaign_record_id,
137
+ "type": "campaign",
138
+ "title": f"Campaign: {campaign.get('name', 'Unnamed Campaign')}",
139
+ "text": f"Meta Ads Campaign {campaign.get('name', 'Unnamed')} (ID: {campaign.get('id', 'N/A')}) - Objective: {campaign.get('objective', 'Unknown')}, Status: {campaign.get('status', 'Unknown')}, Daily Budget: ${campaign.get('daily_budget', 'Not set')}, Account: {account.get('name', 'Unknown')}",
140
+ "metadata": {
141
+ "campaign_id": campaign.get('id'),
142
+ "campaign_name": campaign.get('name'),
143
+ "objective": campaign.get('objective'),
144
+ "status": campaign.get('status'),
145
+ "account_id": account.get('id'),
146
+ "account_name": account.get('name'),
147
+ "data_type": "meta_ads_campaign"
148
+ },
149
+ "raw_data": campaign
150
+ }
151
+
152
+ # If query specifically mentions "ads" or "ad", also search individual ads
153
+ if any(term in ['ad', 'ads', 'advertisement', 'creative'] for term in query_terms):
154
+ for account in accounts[:3]: # Limit to first 3 accounts for performance
155
+ ads = await self._get_ads(access_token, account['id'], limit=10)
156
+ for ad in ads:
157
+ ad_text = f"{ad.get('name', '')} {ad.get('status', '')}".lower()
158
+
159
+ if any(term in ad_text for term in query_terms):
160
+ ad_record_id = f"ad:{ad['id']}"
161
+ matching_ids.append(ad_record_id)
162
+
163
+ # Cache the ad data
164
+ self._cache[ad_record_id] = {
165
+ "id": ad_record_id,
166
+ "type": "ad",
167
+ "title": f"Ad: {ad.get('name', 'Unnamed Ad')}",
168
+ "text": f"Meta Ad {ad.get('name', 'Unnamed')} (ID: {ad.get('id', 'N/A')}) - Status: {ad.get('status', 'Unknown')}, Bid Amount: ${ad.get('bid_amount', 'Not set')}, Account: {account.get('name', 'Unknown')}",
169
+ "metadata": {
170
+ "ad_id": ad.get('id'),
171
+ "ad_name": ad.get('name'),
172
+ "status": ad.get('status'),
173
+ "account_id": account.get('id'),
174
+ "account_name": account.get('name'),
175
+ "data_type": "meta_ads_ad"
176
+ },
177
+ "raw_data": ad
178
+ }
179
+
180
+ except Exception as e:
181
+ logger.error(f"Error during search operation: {e}")
182
+ # Return empty list on error, but don't raise exception
183
+ return []
184
+
185
+ logger.info(f"Search completed. Found {len(matching_ids)} matching records")
186
+ return matching_ids[:50] # Limit to 50 results for performance
187
+
188
+ def fetch_record(self, record_id: str) -> Optional[Dict[str, Any]]:
189
+ """Fetch a cached record by ID
190
+
191
+ Args:
192
+ record_id: The record ID to fetch
193
+
194
+ Returns:
195
+ Record data or None if not found
196
+ """
197
+ logger.info(f"Fetching record: {record_id}")
198
+
199
+ record = self._cache.get(record_id)
200
+ if record:
201
+ logger.debug(f"Record found in cache: {record['type']}")
202
+ return record
203
+ else:
204
+ logger.warning(f"Record not found in cache: {record_id}")
205
+ return None
206
+
207
+
208
+ # Global data manager instance
209
+ _data_manager = MetaAdsDataManager()
210
+
211
+
212
+ @mcp_server.tool()
213
+ @meta_api_tool
214
+ async def search(
215
+ access_token: str = None,
216
+ query: str = None
217
+ ) -> str:
218
+ """
219
+ Search through Meta Ads data and return matching record IDs.
220
+
221
+ This tool is required for OpenAI ChatGPT Deep Research integration.
222
+ It searches across ad accounts, campaigns, and ads to find relevant records
223
+ based on the provided query.
224
+
225
+ Args:
226
+ access_token: Meta API access token (optional - will use cached token if not provided)
227
+ query: Search query string to find relevant Meta Ads records
228
+
229
+ Returns:
230
+ JSON response with list of matching record IDs
231
+
232
+ Example Usage:
233
+ search(query="active campaigns")
234
+ search(query="account spending")
235
+ search(query="facebook ads performance")
236
+ """
237
+ if not query:
238
+ return json.dumps({
239
+ "error": "query parameter is required",
240
+ "ids": []
241
+ }, indent=2)
242
+
243
+ try:
244
+ # Use the data manager to search records
245
+ matching_ids = await _data_manager.search_records(query, access_token)
246
+
247
+ response = {
248
+ "ids": matching_ids,
249
+ "query": query,
250
+ "total_results": len(matching_ids)
251
+ }
252
+
253
+ logger.info(f"Search successful. Query: '{query}', Results: {len(matching_ids)}")
254
+ return json.dumps(response, indent=2)
255
+
256
+ except Exception as e:
257
+ error_msg = str(e)
258
+ logger.error(f"Error in search tool: {error_msg}")
259
+
260
+ return json.dumps({
261
+ "error": "Failed to search Meta Ads data",
262
+ "details": error_msg,
263
+ "ids": [],
264
+ "query": query
265
+ }, indent=2)
266
+
267
+
268
+ @mcp_server.tool()
269
+ async def fetch(
270
+ id: str = None
271
+ ) -> str:
272
+ """
273
+ Fetch complete record data by ID.
274
+
275
+ This tool is required for OpenAI ChatGPT Deep Research integration.
276
+ It retrieves the full data for a specific record identified by its ID.
277
+
278
+ Args:
279
+ id: The record ID to fetch (format: "type:id", e.g., "account:act_123456")
280
+
281
+ Returns:
282
+ JSON response with complete record data including id, title, text, and metadata
283
+
284
+ Example Usage:
285
+ fetch(id="account:act_123456789")
286
+ fetch(id="campaign:23842588888640185")
287
+ fetch(id="ad:23842614006130185")
288
+ """
289
+ if not id:
290
+ return json.dumps({
291
+ "error": "id parameter is required"
292
+ }, indent=2)
293
+
294
+ try:
295
+ # Use the data manager to fetch the record
296
+ record = _data_manager.fetch_record(id)
297
+
298
+ if record:
299
+ logger.info(f"Record fetched successfully: {id}")
300
+ return json.dumps(record, indent=2)
301
+ else:
302
+ logger.warning(f"Record not found: {id}")
303
+ return json.dumps({
304
+ "error": f"Record not found: {id}",
305
+ "id": id
306
+ }, indent=2)
307
+
308
+ except Exception as e:
309
+ error_msg = str(e)
310
+ logger.error(f"Error in fetch tool: {error_msg}")
311
+
312
+ return json.dumps({
313
+ "error": "Failed to fetch record",
314
+ "details": error_msg,
315
+ "id": id
316
+ }, indent=2)
@@ -276,6 +276,8 @@ def main():
276
276
  pipeboard_api_token = os.environ.get("PIPEBOARD_API_TOKEN")
277
277
  if pipeboard_api_token:
278
278
  logger.info("Using Pipeboard authentication")
279
+ print("✅ Pipeboard authentication enabled")
280
+ print(f" API token: {pipeboard_api_token[:8]}...{pipeboard_api_token[-4:]}")
279
281
  # Check for existing token
280
282
  token = pipeboard_auth_manager.get_access_token()
281
283
  if not token:
@@ -312,6 +314,9 @@ def main():
312
314
  except Exception as e:
313
315
  logger.error(f"Error initiating browser-based authentication: {e}")
314
316
  print(f"Error: Could not start authentication: {e}")
317
+ else:
318
+ print(f"✅ Valid Pipeboard access token found")
319
+ print(f" Token preview: {token[:10]}...{token[-5:]}")
315
320
 
316
321
  # Transport-specific server initialization and startup
317
322
  if args.transport == "streamable-http":
@@ -336,7 +341,7 @@ def main():
336
341
  # Import all tool modules to ensure they are registered
337
342
  logger.info("Ensuring all tools are registered for HTTP transport")
338
343
  from . import accounts, campaigns, adsets, ads, insights, authentication
339
- from . import ads_library, budget_schedules, reports
344
+ from . import ads_library, budget_schedules, reports, openai_deep_research
340
345
 
341
346
  # ✅ NEW: Setup HTTP authentication middleware
342
347
  logger.info("Setting up HTTP authentication middleware")
@@ -0,0 +1,185 @@
1
+ """Targeting search functionality for Meta Ads API."""
2
+
3
+ import json
4
+ from typing import Optional, List, Dict, Any
5
+ from .api import meta_api_tool, make_api_request
6
+ from .server import mcp_server
7
+
8
+
9
+ @mcp_server.tool()
10
+ @meta_api_tool
11
+ async def search_interests(access_token: str = None, query: str = None, limit: int = 25) -> str:
12
+ """
13
+ Search for interest targeting options by keyword.
14
+
15
+ Args:
16
+ access_token: Meta API access token (optional - will use cached token if not provided)
17
+ query: Search term for interests (e.g., "baseball", "cooking", "travel")
18
+ limit: Maximum number of results to return (default: 25)
19
+
20
+ Returns:
21
+ JSON string containing interest data with id, name, audience_size, and path fields
22
+ """
23
+ if not query:
24
+ return json.dumps({"error": "No search query provided"}, indent=2)
25
+
26
+ endpoint = "search"
27
+ params = {
28
+ "type": "adinterest",
29
+ "q": query,
30
+ "limit": limit
31
+ }
32
+
33
+ data = await make_api_request(endpoint, access_token, params)
34
+
35
+ return json.dumps(data, indent=2)
36
+
37
+
38
+ @mcp_server.tool()
39
+ @meta_api_tool
40
+ async def get_interest_suggestions(access_token: str = None, interest_list: List[str] = None, limit: int = 25) -> str:
41
+ """
42
+ Get interest suggestions based on existing interests.
43
+
44
+ Args:
45
+ access_token: Meta API access token (optional - will use cached token if not provided)
46
+ interest_list: List of interest names to get suggestions for (e.g., ["Basketball", "Soccer"])
47
+ limit: Maximum number of suggestions to return (default: 25)
48
+
49
+ Returns:
50
+ JSON string containing suggested interests with id, name, audience_size, and description fields
51
+ """
52
+ if not interest_list:
53
+ return json.dumps({"error": "No interest list provided"}, indent=2)
54
+
55
+ endpoint = "search"
56
+ params = {
57
+ "type": "adinterestsuggestion",
58
+ "interest_list": json.dumps(interest_list),
59
+ "limit": limit
60
+ }
61
+
62
+ data = await make_api_request(endpoint, access_token, params)
63
+
64
+ return json.dumps(data, indent=2)
65
+
66
+
67
+ @mcp_server.tool()
68
+ @meta_api_tool
69
+ async def validate_interests(access_token: str = None, interest_list: List[str] = None,
70
+ interest_fbid_list: List[str] = None) -> str:
71
+ """
72
+ Validate interest names or IDs for targeting.
73
+
74
+ Args:
75
+ access_token: Meta API access token (optional - will use cached token if not provided)
76
+ interest_list: List of interest names to validate (e.g., ["Japan", "Basketball"])
77
+ interest_fbid_list: List of interest IDs to validate (e.g., ["6003700426513"])
78
+
79
+ Returns:
80
+ JSON string with validation results showing valid status and audience_size for each interest
81
+ """
82
+ if not interest_list and not interest_fbid_list:
83
+ return json.dumps({"error": "No interest list or FBID list provided"}, indent=2)
84
+
85
+ endpoint = "search"
86
+ params = {
87
+ "type": "adinterestvalid"
88
+ }
89
+
90
+ if interest_list:
91
+ params["interest_list"] = json.dumps(interest_list)
92
+
93
+ if interest_fbid_list:
94
+ params["interest_fbid_list"] = json.dumps(interest_fbid_list)
95
+
96
+ data = await make_api_request(endpoint, access_token, params)
97
+
98
+ return json.dumps(data, indent=2)
99
+
100
+
101
+ @mcp_server.tool()
102
+ @meta_api_tool
103
+ async def search_behaviors(access_token: str = None, limit: int = 50) -> str:
104
+ """
105
+ Get all available behavior targeting options.
106
+
107
+ Args:
108
+ access_token: Meta API access token (optional - will use cached token if not provided)
109
+ limit: Maximum number of results to return (default: 50)
110
+
111
+ Returns:
112
+ JSON string containing behavior targeting options with id, name, audience_size bounds, path, and description
113
+ """
114
+ endpoint = "search"
115
+ params = {
116
+ "type": "adTargetingCategory",
117
+ "class": "behaviors",
118
+ "limit": limit
119
+ }
120
+
121
+ data = await make_api_request(endpoint, access_token, params)
122
+
123
+ return json.dumps(data, indent=2)
124
+
125
+
126
+ @mcp_server.tool()
127
+ @meta_api_tool
128
+ async def search_demographics(access_token: str = None, demographic_class: str = "demographics", limit: int = 50) -> str:
129
+ """
130
+ Get demographic targeting options.
131
+
132
+ Args:
133
+ access_token: Meta API access token (optional - will use cached token if not provided)
134
+ demographic_class: Type of demographics to retrieve. Options: 'demographics', 'life_events',
135
+ 'industries', 'income', 'family_statuses', 'user_device', 'user_os' (default: 'demographics')
136
+ limit: Maximum number of results to return (default: 50)
137
+
138
+ Returns:
139
+ JSON string containing demographic targeting options with id, name, audience_size bounds, path, and description
140
+ """
141
+ endpoint = "search"
142
+ params = {
143
+ "type": "adTargetingCategory",
144
+ "class": demographic_class,
145
+ "limit": limit
146
+ }
147
+
148
+ data = await make_api_request(endpoint, access_token, params)
149
+
150
+ return json.dumps(data, indent=2)
151
+
152
+
153
+ @mcp_server.tool()
154
+ @meta_api_tool
155
+ async def search_geo_locations(access_token: str = None, query: str = None,
156
+ location_types: List[str] = None, limit: int = 25) -> str:
157
+ """
158
+ Search for geographic targeting locations.
159
+
160
+ Args:
161
+ access_token: Meta API access token (optional - will use cached token if not provided)
162
+ query: Search term for locations (e.g., "New York", "California", "Japan")
163
+ location_types: Types of locations to search. Options: ['country', 'region', 'city', 'zip',
164
+ 'geo_market', 'electoral_district']. If not specified, searches all types.
165
+ limit: Maximum number of results to return (default: 25)
166
+
167
+ Returns:
168
+ JSON string containing location data with key, name, type, and geographic hierarchy information
169
+ """
170
+ if not query:
171
+ return json.dumps({"error": "No search query provided"}, indent=2)
172
+
173
+ endpoint = "search"
174
+ params = {
175
+ "type": "adgeolocation",
176
+ "q": query,
177
+ "limit": limit
178
+ }
179
+
180
+ if location_types:
181
+ params["location_types"] = json.dumps(location_types)
182
+
183
+ data = await make_api_request(endpoint, access_token, params)
184
+
185
+ return json.dumps(data, indent=2)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Model Context Protocol (MCP) plugin for interacting with Meta Ads API
5
5
  Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
6
6
  Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
@@ -331,6 +331,54 @@ For local installation configuration, authentication options, and advanced techn
331
331
  - `access_token` (optional): Meta API access token.
332
332
  - Returns: JSON string with the ID of the created budget schedule or an error message.
333
333
 
334
+ 22. `mcp_meta_ads_search_interests`
335
+ - Search for interest targeting options by keyword
336
+ - Inputs:
337
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
338
+ - `query`: Search term for interests (e.g., "baseball", "cooking", "travel")
339
+ - `limit`: Maximum number of results to return (default: 25)
340
+ - Returns: Interest data with id, name, audience_size, and path fields
341
+
342
+ 23. `mcp_meta_ads_get_interest_suggestions`
343
+ - Get interest suggestions based on existing interests
344
+ - Inputs:
345
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
346
+ - `interest_list`: List of interest names to get suggestions for (e.g., ["Basketball", "Soccer"])
347
+ - `limit`: Maximum number of suggestions to return (default: 25)
348
+ - Returns: Suggested interests with id, name, audience_size, and description fields
349
+
350
+ 24. `mcp_meta_ads_validate_interests`
351
+ - Validate interest names or IDs for targeting
352
+ - Inputs:
353
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
354
+ - `interest_list`: List of interest names to validate (e.g., ["Japan", "Basketball"])
355
+ - `interest_fbid_list`: List of interest IDs to validate (e.g., ["6003700426513"])
356
+ - Returns: Validation results showing valid status and audience_size for each interest
357
+
358
+ 25. `mcp_meta_ads_search_behaviors`
359
+ - Get all available behavior targeting options
360
+ - Inputs:
361
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
362
+ - `limit`: Maximum number of results to return (default: 50)
363
+ - Returns: Behavior targeting options with id, name, audience_size bounds, path, and description
364
+
365
+ 26. `mcp_meta_ads_search_demographics`
366
+ - Get demographic targeting options
367
+ - Inputs:
368
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
369
+ - `demographic_class`: Type of demographics ('demographics', 'life_events', 'industries', 'income', 'family_statuses', 'user_device', 'user_os')
370
+ - `limit`: Maximum number of results to return (default: 50)
371
+ - Returns: Demographic targeting options with id, name, audience_size bounds, path, and description
372
+
373
+ 27. `mcp_meta_ads_search_geo_locations`
374
+ - Search for geographic targeting locations
375
+ - Inputs:
376
+ - `access_token` (optional): Meta API access token (will use cached token if not provided)
377
+ - `query`: Search term for locations (e.g., "New York", "California", "Japan")
378
+ - `location_types`: Types of locations to search (['country', 'region', 'city', 'zip', 'geo_market', 'electoral_district'])
379
+ - `limit`: Maximum number of results to return (default: 25)
380
+ - Returns: Location data with key, name, type, and geographic hierarchy information
381
+
334
382
  ## Privacy and Security
335
383
 
336
384
  Meta Ads MCP follows security best practices with secure token management and automatic authentication handling.
@@ -1,6 +1,6 @@
1
- meta_ads_mcp/__init__.py,sha256=sEcedZMAxKadL65GY8HWRyv7PPaCxZ1QrVMtUMaCeh0,1182
1
+ meta_ads_mcp/__init__.py,sha256=UDuLAO7mGVeZrzSJhTr3UAJKug3oHb6IK6OjooX6fk8,1492
2
2
  meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
- meta_ads_mcp/core/__init__.py,sha256=XVJjMOfdgnqxy3k8vCn2PCf7za8fMk4BdgJGiSFCVZY,1209
3
+ meta_ads_mcp/core/__init__.py,sha256=IHBUfLTQW44VLbH_gZDT83nqzGrVWZ0l7FG4P3bp_bU,1706
4
4
  meta_ads_mcp/core/accounts.py,sha256=Nmp7lPxO9wmq25jWV7_H0LIqnEbBhpCVBlLGW2HUaq0,2277
5
5
  meta_ads_mcp/core/ads.py,sha256=aaK70mgfhBJRXr4cdkKag5mjYzvHuHpRttJvTMzPk4Y,36156
6
6
  meta_ads_mcp/core/ads_library.py,sha256=onStn9UkRqYDC60gOPS-iKDtP1plz6DygUb7hUZ0Jw8,2807
@@ -14,13 +14,15 @@ meta_ads_mcp/core/campaigns.py,sha256=Fd477GsD1Gx08Ve0uXUCvr4fC-xQCeVHPBwRVaeRQK
14
14
  meta_ads_mcp/core/duplication.py,sha256=UUmTDFx9o5ZsPQG2Rb9c4ZyuKUVN3FfTjebfTIHHdo4,18984
15
15
  meta_ads_mcp/core/http_auth_integration.py,sha256=lGpKhfzJcyWugBcYEvypY-qnlt-3UDBLqh7xAUH0DGw,12473
16
16
  meta_ads_mcp/core/insights.py,sha256=U7KYdWQpGcdykE1WUtdJdYR3VTwKrXUzIzCREwWbf48,2599
17
+ meta_ads_mcp/core/openai_deep_research.py,sha256=Ocs8bmNNBLZQLmWfL6azlC3RNzevVzV5WgcEp4H2wdY,13240
17
18
  meta_ads_mcp/core/pipeboard_auth.py,sha256=VvbxEB8ZOhnMccLU7HI1HgaPWHCl5NGrzZCm-zzHze4,22798
18
19
  meta_ads_mcp/core/reports.py,sha256=Dv3hfsPOR7IZ9WrYrKd_6SNgZl-USIphg7knva3UYAw,5747
19
20
  meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0w,1236
20
- meta_ads_mcp/core/server.py,sha256=mmhtcyB7h1aO6jK4njLztPdAebPDmc3mhA7DksR1nlY,17583
21
+ meta_ads_mcp/core/server.py,sha256=WhbAag7xdhbGcp7rnU4sKhqXJ8Slapa_ba3T23Yp_2U,17889
22
+ meta_ads_mcp/core/targeting.py,sha256=3HW1qirEdwaQurlBZGenbIwawcb5J06ghJKRfgu9ZEs,6318
21
23
  meta_ads_mcp/core/utils.py,sha256=ofKUhyo-5SZoJVuBeTVFPPQCffk0UKpwmDMrd8qQxNc,8715
22
- meta_ads_mcp-0.5.0.dist-info/METADATA,sha256=o0vClv8CNhB8eXp9a7ZAnCBKVDo77S_EPBrvV52dMC0,17580
23
- meta_ads_mcp-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
- meta_ads_mcp-0.5.0.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
25
- meta_ads_mcp-0.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
- meta_ads_mcp-0.5.0.dist-info/RECORD,,
24
+ meta_ads_mcp-0.7.0.dist-info/METADATA,sha256=ADo7W0ulgt6gIZzJjOkyxM-PDqE0inab-THLjsStoGo,20409
25
+ meta_ads_mcp-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ meta_ads_mcp-0.7.0.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
+ meta_ads_mcp-0.7.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
28
+ meta_ads_mcp-0.7.0.dist-info/RECORD,,