meta-ads-mcp 0.5.0__tar.gz → 0.6.0__tar.gz
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-0.5.0 → meta_ads_mcp-0.6.0}/PKG-INFO +1 -1
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/__init__.py +1 -1
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/__init__.py +3 -0
- meta_ads_mcp-0.6.0/meta_ads_mcp/core/openai_deep_research.py +316 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/server.py +6 -1
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/pyproject.toml +1 -1
- meta_ads_mcp-0.6.0/tests/test_account_search.py +336 -0
- meta_ads_mcp-0.6.0/tests/test_integration_openai_mcp.py +242 -0
- meta_ads_mcp-0.6.0/tests/test_openai_mcp_deep_research.py +381 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/.github/workflows/publish.yml +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/.github/workflows/test.yml +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/.gitignore +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/CUSTOM_META_APP.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/Dockerfile +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/LICENSE +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/LOCAL_INSTALLATION.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/META_API_NOTES.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/README.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/RELEASE.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/STREAMABLE_HTTP_SETUP.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/examples/README.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/examples/example_http_client.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/future_improvements.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/images/meta-ads-example.png +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_auth.sh +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/__main__.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/accounts.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/ads.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/ads_library.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/adsets.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/api.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/auth.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/authentication.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/budget_schedules.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/callback_server.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/campaigns.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/duplication.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/http_auth_integration.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/insights.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/reports.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/resources.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/meta_ads_mcp/core/utils.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/requirements.txt +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/setup.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/smithery.yaml +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/README.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/README_REGRESSION_TESTS.md +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/__init__.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/conftest.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/test_duplication.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/test_duplication_regression.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/test_get_ad_creatives_fix.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/test_get_ad_image_regression.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/test_http_transport.py +0 -0
- {meta_ads_mcp-0.5.0 → meta_ads_mcp-0.6.0}/tests/test_openai.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meta-ads-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -13,6 +13,7 @@ from .ads_library import search_ads_archive
|
|
|
13
13
|
from .budget_schedules import create_budget_schedule
|
|
14
14
|
from . import reports # Import module to register conditional tools
|
|
15
15
|
from . import duplication # Import module to register conditional duplication tools
|
|
16
|
+
from .openai_deep_research import search, fetch # OpenAI MCP Deep Research tools
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
'mcp_server',
|
|
@@ -36,4 +37,6 @@ __all__ = [
|
|
|
36
37
|
'main',
|
|
37
38
|
'search_ads_archive',
|
|
38
39
|
'create_budget_schedule',
|
|
40
|
+
'search', # OpenAI MCP Deep Research search tool
|
|
41
|
+
'fetch', # OpenAI MCP Deep Research fetch tool
|
|
39
42
|
]
|
|
@@ -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")
|