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 +15 -3
- meta_ads_mcp/core/__init__.py +10 -0
- meta_ads_mcp/core/openai_deep_research.py +316 -0
- meta_ads_mcp/core/server.py +6 -1
- meta_ads_mcp/core/targeting.py +185 -0
- {meta_ads_mcp-0.5.0.dist-info → meta_ads_mcp-0.7.0.dist-info}/METADATA +49 -1
- {meta_ads_mcp-0.5.0.dist-info → meta_ads_mcp-0.7.0.dist-info}/RECORD +10 -8
- {meta_ads_mcp-0.5.0.dist-info → meta_ads_mcp-0.7.0.dist-info}/WHEEL +0 -0
- {meta_ads_mcp-0.5.0.dist-info → meta_ads_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
- {meta_ads_mcp-0.5.0.dist-info → meta_ads_mcp-0.7.0.dist-info}/licenses/LICENSE +0 -0
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.
|
|
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
|
meta_ads_mcp/core/__init__.py
CHANGED
|
@@ -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)
|
meta_ads_mcp/core/server.py
CHANGED
|
@@ -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.
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
23
|
-
meta_ads_mcp-0.
|
|
24
|
-
meta_ads_mcp-0.
|
|
25
|
-
meta_ads_mcp-0.
|
|
26
|
-
meta_ads_mcp-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|