meta-ads-mcp 0.2.6__py3-none-any.whl → 0.2.8__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 +1 -1
- meta_ads_mcp/api.py +122 -53
- meta_ads_mcp/core/__init__.py +2 -1
- meta_ads_mcp/core/adsets.py +7 -2
- meta_ads_mcp/core/api.py +29 -1
- meta_ads_mcp/core/auth.py +81 -13
- meta_ads_mcp/core/authentication.py +103 -49
- meta_ads_mcp/core/pipeboard_auth.py +484 -0
- meta_ads_mcp/core/server.py +49 -4
- meta_ads_mcp/core/utils.py +11 -5
- {meta_ads_mcp-0.2.6.dist-info → meta_ads_mcp-0.2.8.dist-info}/METADATA +115 -29
- meta_ads_mcp-0.2.8.dist-info/RECORD +21 -0
- meta_ads_mcp-0.2.6.dist-info/RECORD +0 -20
- {meta_ads_mcp-0.2.6.dist-info → meta_ads_mcp-0.2.8.dist-info}/WHEEL +0 -0
- {meta_ads_mcp-0.2.6.dist-info → meta_ads_mcp-0.2.8.dist-info}/entry_points.txt +0 -0
meta_ads_mcp/__init__.py
CHANGED
meta_ads_mcp/api.py
CHANGED
|
@@ -30,7 +30,7 @@ USER_AGENT = "meta-ads-mcp/1.0"
|
|
|
30
30
|
META_APP_ID = os.environ.get("META_APP_ID", "") # Default to empty string
|
|
31
31
|
|
|
32
32
|
# Auth constants
|
|
33
|
-
AUTH_SCOPE = "ads_management,ads_read,business_management"
|
|
33
|
+
AUTH_SCOPE = "ads_management,ads_read,business_management,public_profile"
|
|
34
34
|
AUTH_REDIRECT_URI = "http://localhost:8888/callback"
|
|
35
35
|
AUTH_RESPONSE_TYPE = "token"
|
|
36
36
|
|
|
@@ -521,45 +521,40 @@ def meta_api_tool(func):
|
|
|
521
521
|
if not access_token:
|
|
522
522
|
needs_authentication = True
|
|
523
523
|
|
|
524
|
-
#
|
|
525
|
-
|
|
524
|
+
# Check if we're using Pipeboard authentication
|
|
525
|
+
using_pipeboard = bool(os.environ.get("PIPEBOARD_API_TOKEN", ""))
|
|
526
526
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
result = await func(**kwargs)
|
|
559
|
-
|
|
560
|
-
# If authentication is needed after the call (e.g., token was invalidated)
|
|
561
|
-
if needs_authentication:
|
|
562
|
-
# Start the callback server
|
|
527
|
+
if using_pipeboard:
|
|
528
|
+
# For Pipeboard, we use a different authentication flow
|
|
529
|
+
try:
|
|
530
|
+
# Here we'd import dynamically to avoid circular imports
|
|
531
|
+
from .core.pipeboard_auth import pipeboard_auth_manager
|
|
532
|
+
|
|
533
|
+
# Initiate the Pipeboard auth flow
|
|
534
|
+
auth_data = pipeboard_auth_manager.initiate_auth_flow()
|
|
535
|
+
login_url = auth_data.get("loginUrl")
|
|
536
|
+
|
|
537
|
+
# Return a user-friendly authentication required response for Pipeboard
|
|
538
|
+
return json.dumps({
|
|
539
|
+
"error": "Authentication Required",
|
|
540
|
+
"details": {
|
|
541
|
+
"message": "You need to authenticate with the Meta API via Pipeboard",
|
|
542
|
+
"action_required": "Please authenticate using the link below",
|
|
543
|
+
"login_url": login_url,
|
|
544
|
+
"markdown_link": f"[Click here to authenticate with Meta Ads API via Pipeboard]({login_url})",
|
|
545
|
+
"authentication_method": "pipeboard"
|
|
546
|
+
}
|
|
547
|
+
}, indent=2)
|
|
548
|
+
except Exception as e:
|
|
549
|
+
return json.dumps({
|
|
550
|
+
"error": f"Pipeboard Authentication Error: {str(e)}",
|
|
551
|
+
"details": {
|
|
552
|
+
"message": "Failed to initiate Pipeboard authentication flow",
|
|
553
|
+
"action_required": "Please check your PIPEBOARD_API_TOKEN environment variable"
|
|
554
|
+
}
|
|
555
|
+
}, indent=2)
|
|
556
|
+
else:
|
|
557
|
+
# For direct Meta auth, start the callback server
|
|
563
558
|
port = start_callback_server()
|
|
564
559
|
|
|
565
560
|
# Get current app ID from config
|
|
@@ -570,6 +565,7 @@ def meta_api_tool(func):
|
|
|
570
565
|
"help": "This is required for authentication with Meta Graph API."
|
|
571
566
|
}, indent=2)
|
|
572
567
|
|
|
568
|
+
# Update auth manager with current app ID
|
|
573
569
|
auth_manager.app_id = current_app_id
|
|
574
570
|
print(f"Using Meta App ID from config: {current_app_id}")
|
|
575
571
|
|
|
@@ -579,21 +575,94 @@ def meta_api_tool(func):
|
|
|
579
575
|
# Generate the authentication URL
|
|
580
576
|
login_url = auth_manager.get_auth_url()
|
|
581
577
|
|
|
582
|
-
#
|
|
583
|
-
|
|
584
|
-
"error": "
|
|
585
|
-
"
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
578
|
+
# Return a user-friendly authentication required response
|
|
579
|
+
return json.dumps({
|
|
580
|
+
"error": "Authentication Required",
|
|
581
|
+
"details": {
|
|
582
|
+
"message": "You need to authenticate with the Meta API before using this tool",
|
|
583
|
+
"action_required": "Please authenticate using the link below",
|
|
584
|
+
"login_url": login_url,
|
|
585
|
+
"markdown_link": f"[Click here to authenticate with Meta Ads API]({login_url})",
|
|
586
|
+
"authentication_method": "meta_oauth"
|
|
587
|
+
}
|
|
588
|
+
}, indent=2)
|
|
589
|
+
|
|
590
|
+
# Call the original function
|
|
591
|
+
try:
|
|
592
|
+
result = await func(**kwargs)
|
|
593
|
+
|
|
594
|
+
# If authentication is needed after the call (e.g., token was invalidated)
|
|
595
|
+
if needs_authentication:
|
|
596
|
+
# Check if we're using Pipeboard authentication
|
|
597
|
+
using_pipeboard = bool(os.environ.get("PIPEBOARD_API_TOKEN", ""))
|
|
595
598
|
|
|
596
|
-
|
|
599
|
+
if using_pipeboard:
|
|
600
|
+
# For Pipeboard, we use a different authentication flow
|
|
601
|
+
try:
|
|
602
|
+
# Here we'd import dynamically to avoid circular imports
|
|
603
|
+
from .core.pipeboard_auth import pipeboard_auth_manager
|
|
604
|
+
|
|
605
|
+
# Initiate the Pipeboard auth flow
|
|
606
|
+
auth_data = pipeboard_auth_manager.initiate_auth_flow()
|
|
607
|
+
login_url = auth_data.get("loginUrl")
|
|
608
|
+
|
|
609
|
+
# Create a resource response that includes the markdown link format
|
|
610
|
+
response = {
|
|
611
|
+
"error": "Session expired or token invalid. Please re-authenticate with Meta Ads API",
|
|
612
|
+
"login_url": login_url,
|
|
613
|
+
"markdown_link": f"[Click here to re-authenticate with Meta Ads API via Pipeboard]({login_url})",
|
|
614
|
+
"message": "IMPORTANT: Please use the Markdown link format in your response to allow the user to click it.",
|
|
615
|
+
"instructions_for_llm": "You must present this link as clickable Markdown to the user using the markdown_link format provided.",
|
|
616
|
+
"authentication_method": "pipeboard",
|
|
617
|
+
"note": "After authenticating, the token will be automatically saved."
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return json.dumps(response, indent=2)
|
|
621
|
+
except Exception as e:
|
|
622
|
+
return json.dumps({
|
|
623
|
+
"error": f"Pipeboard Authentication Error: {str(e)}",
|
|
624
|
+
"details": {
|
|
625
|
+
"message": "Failed to initiate Pipeboard authentication flow",
|
|
626
|
+
"action_required": "Please check your PIPEBOARD_API_TOKEN environment variable"
|
|
627
|
+
}
|
|
628
|
+
}, indent=2)
|
|
629
|
+
else:
|
|
630
|
+
# For direct Meta auth, start the callback server
|
|
631
|
+
port = start_callback_server()
|
|
632
|
+
|
|
633
|
+
# Get current app ID from config
|
|
634
|
+
current_app_id = meta_config.get_app_id()
|
|
635
|
+
if not current_app_id:
|
|
636
|
+
return json.dumps({
|
|
637
|
+
"error": "No Meta App ID provided. Please provide a valid app ID via environment variable META_APP_ID or --app-id CLI argument.",
|
|
638
|
+
"help": "This is required for authentication with Meta Graph API."
|
|
639
|
+
}, indent=2)
|
|
640
|
+
|
|
641
|
+
auth_manager.app_id = current_app_id
|
|
642
|
+
print(f"Using Meta App ID from config: {current_app_id}")
|
|
643
|
+
|
|
644
|
+
# Update auth manager's redirect URI with the current port
|
|
645
|
+
auth_manager.redirect_uri = f"http://localhost:{port}/callback"
|
|
646
|
+
|
|
647
|
+
# Generate the authentication URL
|
|
648
|
+
login_url = auth_manager.get_auth_url()
|
|
649
|
+
|
|
650
|
+
# Create a resource response that includes the markdown link format
|
|
651
|
+
response = {
|
|
652
|
+
"error": "Session expired or token invalid. Please re-authenticate with Meta Ads API",
|
|
653
|
+
"login_url": login_url,
|
|
654
|
+
"server_status": f"Callback server running on port {port}",
|
|
655
|
+
"markdown_link": f"[Click here to re-authenticate with Meta Ads API]({login_url})",
|
|
656
|
+
"message": "IMPORTANT: Please use the Markdown link format in your response to allow the user to click it.",
|
|
657
|
+
"instructions_for_llm": "You must present this link as clickable Markdown to the user using the markdown_link format provided.",
|
|
658
|
+
"authentication_method": "meta_oauth",
|
|
659
|
+
"note": "After authenticating, the token will be automatically saved."
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
# Wait a moment to ensure the server is fully started
|
|
663
|
+
await asyncio.sleep(1)
|
|
664
|
+
|
|
665
|
+
return json.dumps(response, indent=2)
|
|
597
666
|
|
|
598
667
|
# If result is a string (JSON), check for app ID errors and improve them
|
|
599
668
|
if isinstance(result, str):
|
meta_ads_mcp/core/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ from .server import mcp_server
|
|
|
4
4
|
from .accounts import get_ad_accounts, get_account_info
|
|
5
5
|
from .campaigns import get_campaigns, get_campaign_details, create_campaign
|
|
6
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
|
|
7
|
+
from .ads import get_ads, get_ad_details, get_ad_creatives, get_ad_image, update_ad
|
|
8
8
|
from .insights import get_insights, debug_image_download
|
|
9
9
|
from .authentication import get_login_link
|
|
10
10
|
from .server import login_cli, main
|
|
@@ -24,6 +24,7 @@ __all__ = [
|
|
|
24
24
|
'get_ad_details',
|
|
25
25
|
'get_ad_creatives',
|
|
26
26
|
'get_ad_image',
|
|
27
|
+
'update_ad',
|
|
27
28
|
'get_insights',
|
|
28
29
|
'debug_image_download',
|
|
29
30
|
'get_login_link',
|
meta_ads_mcp/core/adsets.py
CHANGED
|
@@ -78,7 +78,8 @@ async def get_adset_details(access_token: str = None, adset_id: str = None) -> s
|
|
|
78
78
|
@mcp_server.tool()
|
|
79
79
|
@meta_api_tool
|
|
80
80
|
async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, Any]] = None, bid_strategy: str = None,
|
|
81
|
-
bid_amount: int = None, status: str = None, targeting: Dict[str, Any] = None,
|
|
81
|
+
bid_amount: int = None, status: str = None, targeting: Dict[str, Any] = None,
|
|
82
|
+
optimization_goal: str = None, access_token: str = None) -> str:
|
|
82
83
|
"""
|
|
83
84
|
Update an ad set with new settings including frequency caps.
|
|
84
85
|
|
|
@@ -91,6 +92,7 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
|
|
|
91
92
|
status: Update ad set status (ACTIVE, PAUSED, etc.)
|
|
92
93
|
targeting: Targeting specifications including targeting_automation
|
|
93
94
|
(e.g. {"targeting_automation":{"advantage_audience":1}})
|
|
95
|
+
optimization_goal: Conversion optimization goal (e.g., 'LINK_CLICKS', 'CONVERSIONS', 'APP_INSTALLS', etc.)
|
|
94
96
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
95
97
|
"""
|
|
96
98
|
if not adset_id:
|
|
@@ -110,6 +112,9 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
|
|
|
110
112
|
if status is not None:
|
|
111
113
|
changes['status'] = status
|
|
112
114
|
|
|
115
|
+
if optimization_goal is not None:
|
|
116
|
+
changes['optimization_goal'] = optimization_goal
|
|
117
|
+
|
|
113
118
|
if targeting is not None:
|
|
114
119
|
# Get current ad set details to preserve existing targeting settings
|
|
115
120
|
current_details_json = await get_adset_details(adset_id=adset_id, access_token=access_token)
|
|
@@ -163,7 +168,7 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
|
|
|
163
168
|
"current_details": current_details,
|
|
164
169
|
"proposed_changes": changes,
|
|
165
170
|
"instructions_for_llm": "You must present this link as clickable Markdown to the user using the markdown_link format provided.",
|
|
166
|
-
"note": "
|
|
171
|
+
"note": "Click the link to confirm and apply your ad set updates. Refresh the browser page if it doesn't load immediately."
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
return json.dumps(response, indent=2)
|
meta_ads_mcp/core/api.py
CHANGED
|
@@ -193,7 +193,7 @@ def meta_api_tool(func):
|
|
|
193
193
|
logger.debug(f"Current app_id: {app_id}")
|
|
194
194
|
logger.debug(f"META_APP_ID env var: {os.environ.get('META_APP_ID')}")
|
|
195
195
|
|
|
196
|
-
# If access_token is not in kwargs, try to get it from auth_manager
|
|
196
|
+
# If access_token is not in kwargs or not kwargs['access_token'], try to get it from auth_manager
|
|
197
197
|
if 'access_token' not in kwargs or not kwargs['access_token']:
|
|
198
198
|
try:
|
|
199
199
|
access_token = await get_current_access_token()
|
|
@@ -202,13 +202,36 @@ def meta_api_tool(func):
|
|
|
202
202
|
logger.debug("Using access token from auth_manager")
|
|
203
203
|
else:
|
|
204
204
|
logger.warning("No access token available from auth_manager")
|
|
205
|
+
# Add more details about why token might be missing
|
|
206
|
+
if (auth_manager.app_id == "YOUR_META_APP_ID" or not auth_manager.app_id) and not auth_manager.use_pipeboard:
|
|
207
|
+
logger.error("TOKEN VALIDATION FAILED: No valid app_id configured")
|
|
208
|
+
logger.error("Please set META_APP_ID environment variable or configure in your code")
|
|
209
|
+
else:
|
|
210
|
+
logger.error("Check logs above for detailed token validation failures")
|
|
205
211
|
except Exception as e:
|
|
206
212
|
logger.error(f"Error getting access token: {str(e)}")
|
|
213
|
+
# Add stack trace for better debugging
|
|
214
|
+
import traceback
|
|
215
|
+
logger.error(f"Stack trace: {traceback.format_exc()}")
|
|
207
216
|
|
|
208
217
|
# Final validation - if we still don't have a valid token, return authentication required
|
|
209
218
|
if 'access_token' not in kwargs or not kwargs['access_token']:
|
|
210
219
|
logger.warning("No access token available, authentication needed")
|
|
220
|
+
|
|
221
|
+
# Add more specific troubleshooting information
|
|
211
222
|
auth_url = auth_manager.get_auth_url()
|
|
223
|
+
app_id = auth_manager.app_id
|
|
224
|
+
|
|
225
|
+
logger.error("TOKEN VALIDATION SUMMARY:")
|
|
226
|
+
logger.error(f"- Current app_id: '{app_id}'")
|
|
227
|
+
logger.error(f"- Environment META_APP_ID: '{os.environ.get('META_APP_ID', 'Not set')}'")
|
|
228
|
+
logger.error(f"- Pipeboard API token configured: {'Yes' if os.environ.get('PIPEBOARD_API_TOKEN') else 'No'}")
|
|
229
|
+
|
|
230
|
+
# Check for common configuration issues
|
|
231
|
+
if app_id == "YOUR_META_APP_ID" or not app_id:
|
|
232
|
+
logger.error("ISSUE DETECTED: No valid Meta App ID configured")
|
|
233
|
+
logger.error("ACTION REQUIRED: Set META_APP_ID environment variable with a valid App ID")
|
|
234
|
+
|
|
212
235
|
return json.dumps({
|
|
213
236
|
"error": {
|
|
214
237
|
"message": "Authentication Required",
|
|
@@ -216,6 +239,11 @@ def meta_api_tool(func):
|
|
|
216
239
|
"description": "You need to authenticate with the Meta API before using this tool",
|
|
217
240
|
"action_required": "Please authenticate first",
|
|
218
241
|
"auth_url": auth_url,
|
|
242
|
+
"configuration_status": {
|
|
243
|
+
"app_id_configured": bool(app_id) and app_id != "YOUR_META_APP_ID",
|
|
244
|
+
"pipeboard_enabled": bool(os.environ.get('PIPEBOARD_API_TOKEN')),
|
|
245
|
+
},
|
|
246
|
+
"troubleshooting": "Check logs for TOKEN VALIDATION FAILED messages",
|
|
219
247
|
"markdown_link": f"[Click here to authenticate with Meta Ads API]({auth_url})"
|
|
220
248
|
}
|
|
221
249
|
}
|
meta_ads_mcp/core/auth.py
CHANGED
|
@@ -18,8 +18,11 @@ from .callback_server import (
|
|
|
18
18
|
update_confirmation
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
+
# Import the new Pipeboard authentication
|
|
22
|
+
from .pipeboard_auth import pipeboard_auth_manager
|
|
23
|
+
|
|
21
24
|
# Auth constants
|
|
22
|
-
AUTH_SCOPE = "ads_management,ads_read,business_management"
|
|
25
|
+
AUTH_SCOPE = "ads_management,ads_read,business_management,public_profile"
|
|
23
26
|
AUTH_REDIRECT_URI = "http://localhost:8888/callback"
|
|
24
27
|
AUTH_RESPONSE_TYPE = "token"
|
|
25
28
|
|
|
@@ -123,7 +126,10 @@ class AuthManager:
|
|
|
123
126
|
self.app_id = app_id
|
|
124
127
|
self.redirect_uri = redirect_uri
|
|
125
128
|
self.token_info = None
|
|
126
|
-
|
|
129
|
+
# Check for Pipeboard token first
|
|
130
|
+
self.use_pipeboard = bool(os.environ.get("PIPEBOARD_API_TOKEN", ""))
|
|
131
|
+
if not self.use_pipeboard:
|
|
132
|
+
self._load_cached_token()
|
|
127
133
|
|
|
128
134
|
def _get_token_cache_path(self) -> pathlib.Path:
|
|
129
135
|
"""Get the platform-specific path for token cache file"""
|
|
@@ -154,14 +160,14 @@ class AuthManager:
|
|
|
154
160
|
|
|
155
161
|
# Check if token is expired
|
|
156
162
|
if self.token_info.is_expired():
|
|
157
|
-
|
|
163
|
+
logger.info("Cached token is expired")
|
|
158
164
|
self.token_info = None
|
|
159
165
|
return False
|
|
160
166
|
|
|
161
|
-
|
|
167
|
+
logger.info(f"Loaded cached token (expires in {(self.token_info.created_at + self.token_info.expires_in) - int(time.time())} seconds)")
|
|
162
168
|
return True
|
|
163
169
|
except Exception as e:
|
|
164
|
-
|
|
170
|
+
logger.error(f"Error loading cached token: {e}")
|
|
165
171
|
return False
|
|
166
172
|
|
|
167
173
|
def _save_token_to_cache(self) -> None:
|
|
@@ -174,9 +180,9 @@ class AuthManager:
|
|
|
174
180
|
try:
|
|
175
181
|
with open(cache_path, "w") as f:
|
|
176
182
|
json.dump(self.token_info.serialize(), f)
|
|
177
|
-
|
|
183
|
+
logger.info(f"Token cached at: {cache_path}")
|
|
178
184
|
except Exception as e:
|
|
179
|
-
|
|
185
|
+
logger.error(f"Error saving token to cache: {e}")
|
|
180
186
|
|
|
181
187
|
def get_auth_url(self) -> str:
|
|
182
188
|
"""Generate the Facebook OAuth URL for desktop app flow"""
|
|
@@ -198,6 +204,12 @@ class AuthManager:
|
|
|
198
204
|
Returns:
|
|
199
205
|
Access token if successful, None otherwise
|
|
200
206
|
"""
|
|
207
|
+
# If Pipeboard auth is available, use that instead
|
|
208
|
+
if self.use_pipeboard:
|
|
209
|
+
logger.info("Using Pipeboard authentication")
|
|
210
|
+
return pipeboard_auth_manager.get_access_token(force_refresh=force_refresh)
|
|
211
|
+
|
|
212
|
+
# Otherwise, use the original OAuth flow
|
|
201
213
|
# Check if we already have a valid token
|
|
202
214
|
if not force_refresh and self.token_info and not self.token_info.is_expired():
|
|
203
215
|
return self.token_info.access_token
|
|
@@ -212,7 +224,7 @@ class AuthManager:
|
|
|
212
224
|
auth_url = self.get_auth_url()
|
|
213
225
|
|
|
214
226
|
# Open browser with auth URL
|
|
215
|
-
|
|
227
|
+
logger.info(f"Opening browser with URL: {auth_url}")
|
|
216
228
|
webbrowser.open(auth_url)
|
|
217
229
|
|
|
218
230
|
# We don't wait for the token here anymore
|
|
@@ -227,6 +239,10 @@ class AuthManager:
|
|
|
227
239
|
Returns:
|
|
228
240
|
Access token if available, None otherwise
|
|
229
241
|
"""
|
|
242
|
+
# If using Pipeboard, always delegate to the Pipeboard auth manager
|
|
243
|
+
if self.use_pipeboard:
|
|
244
|
+
return pipeboard_auth_manager.get_access_token()
|
|
245
|
+
|
|
230
246
|
if not self.token_info or self.token_info.is_expired():
|
|
231
247
|
return None
|
|
232
248
|
|
|
@@ -234,8 +250,13 @@ class AuthManager:
|
|
|
234
250
|
|
|
235
251
|
def invalidate_token(self) -> None:
|
|
236
252
|
"""Invalidate the current token, usually because it has expired or is invalid"""
|
|
253
|
+
# If using Pipeboard, delegate to the Pipeboard auth manager
|
|
254
|
+
if self.use_pipeboard:
|
|
255
|
+
pipeboard_auth_manager.invalidate_token()
|
|
256
|
+
return
|
|
257
|
+
|
|
237
258
|
if self.token_info:
|
|
238
|
-
|
|
259
|
+
logger.info(f"Invalidating token: {self.token_info.access_token[:10]}...")
|
|
239
260
|
self.token_info = None
|
|
240
261
|
|
|
241
262
|
# Signal that authentication is needed
|
|
@@ -247,12 +268,12 @@ class AuthManager:
|
|
|
247
268
|
cache_path = self._get_token_cache_path()
|
|
248
269
|
if cache_path.exists():
|
|
249
270
|
os.remove(cache_path)
|
|
250
|
-
|
|
271
|
+
logger.info(f"Removed cached token file: {cache_path}")
|
|
251
272
|
except Exception as e:
|
|
252
|
-
|
|
273
|
+
logger.error(f"Error removing cached token file: {e}")
|
|
253
274
|
|
|
254
275
|
def clear_token(self) -> None:
|
|
255
|
-
"""
|
|
276
|
+
"""Alias for invalidate_token for consistency with other APIs"""
|
|
256
277
|
self.invalidate_token()
|
|
257
278
|
|
|
258
279
|
|
|
@@ -385,18 +406,55 @@ async def get_current_access_token() -> Optional[str]:
|
|
|
385
406
|
app_id = meta_config.get_app_id()
|
|
386
407
|
logger.debug(f"Current app_id: {app_id}")
|
|
387
408
|
|
|
409
|
+
# Check if using Pipeboard authentication
|
|
410
|
+
using_pipeboard = auth_manager.use_pipeboard
|
|
411
|
+
|
|
412
|
+
# Check if app_id is valid - but only if not using Pipeboard authentication
|
|
413
|
+
if not app_id and not using_pipeboard:
|
|
414
|
+
logger.error("TOKEN VALIDATION FAILED: No valid app_id configured")
|
|
415
|
+
logger.error("Please set META_APP_ID environment variable or configure via meta_config.set_app_id()")
|
|
416
|
+
return None
|
|
417
|
+
|
|
388
418
|
# Attempt to get access token
|
|
389
419
|
try:
|
|
390
420
|
token = auth_manager.get_access_token()
|
|
391
421
|
|
|
392
422
|
if token:
|
|
393
|
-
|
|
423
|
+
# Add basic token validation - check if it looks like a valid token
|
|
424
|
+
if len(token) < 20: # Most Meta tokens are much longer
|
|
425
|
+
logger.error(f"TOKEN VALIDATION FAILED: Token appears malformed (length: {len(token)})")
|
|
426
|
+
auth_manager.invalidate_token()
|
|
427
|
+
return None
|
|
428
|
+
|
|
429
|
+
logger.debug(f"Access token found in auth_manager (starts with: {token[:10]}...)")
|
|
394
430
|
return token
|
|
395
431
|
else:
|
|
396
432
|
logger.warning("No valid access token available in auth_manager")
|
|
433
|
+
|
|
434
|
+
# Check why token might be missing
|
|
435
|
+
if hasattr(auth_manager, 'token_info') and auth_manager.token_info:
|
|
436
|
+
if auth_manager.token_info.is_expired():
|
|
437
|
+
logger.error("TOKEN VALIDATION FAILED: Token is expired")
|
|
438
|
+
# Add expiration details
|
|
439
|
+
if hasattr(auth_manager.token_info, 'expires_in') and auth_manager.token_info.expires_in:
|
|
440
|
+
expiry_time = auth_manager.token_info.created_at + auth_manager.token_info.expires_in
|
|
441
|
+
current_time = int(time.time())
|
|
442
|
+
expired_seconds_ago = current_time - expiry_time
|
|
443
|
+
logger.error(f"Token expired {expired_seconds_ago} seconds ago")
|
|
444
|
+
elif not auth_manager.token_info.access_token:
|
|
445
|
+
logger.error("TOKEN VALIDATION FAILED: Token object exists but access_token is empty")
|
|
446
|
+
else:
|
|
447
|
+
logger.error("TOKEN VALIDATION FAILED: Token exists but was rejected for unknown reason")
|
|
448
|
+
else:
|
|
449
|
+
logger.error("TOKEN VALIDATION FAILED: No token information available")
|
|
450
|
+
|
|
451
|
+
# Suggest next steps for troubleshooting
|
|
452
|
+
logger.error("To fix: Try re-authenticating or check if your token has been revoked")
|
|
397
453
|
return None
|
|
398
454
|
except Exception as e:
|
|
399
455
|
logger.error(f"Error getting access token: {str(e)}")
|
|
456
|
+
import traceback
|
|
457
|
+
logger.error(f"Token validation stacktrace: {traceback.format_exc()}")
|
|
400
458
|
return None
|
|
401
459
|
|
|
402
460
|
|
|
@@ -443,4 +501,14 @@ def login():
|
|
|
443
501
|
|
|
444
502
|
# Initialize auth manager with a placeholder - will be updated at runtime
|
|
445
503
|
META_APP_ID = os.environ.get("META_APP_ID", "YOUR_META_APP_ID")
|
|
504
|
+
|
|
505
|
+
# Only show warnings about missing META_APP_ID/META_APP_SECRET when not using Pipeboard
|
|
506
|
+
if not os.environ.get("PIPEBOARD_API_TOKEN"):
|
|
507
|
+
# Log warnings about missing environment variables
|
|
508
|
+
if META_APP_ID == "YOUR_META_APP_ID":
|
|
509
|
+
logger.warning("META_APP_ID environment variable is not set. Authentication will not work properly.")
|
|
510
|
+
|
|
511
|
+
if not os.environ.get("META_APP_SECRET"):
|
|
512
|
+
logger.warning("META_APP_SECRET environment variable is not set. Long-lived token exchange will not work.")
|
|
513
|
+
|
|
446
514
|
auth_manager = AuthManager(META_APP_ID)
|