meta-ads-mcp 0.2.5__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.
@@ -2,10 +2,12 @@
2
2
 
3
3
  import json
4
4
  import asyncio
5
+ import os
5
6
  from .api import meta_api_tool
6
7
  from .auth import start_callback_server, auth_manager, get_current_access_token
7
8
  from .server import mcp_server
8
9
  from .utils import logger, META_APP_SECRET
10
+ from .pipeboard_auth import pipeboard_auth_manager
9
11
 
10
12
 
11
13
  @mcp_server.tool()
@@ -13,60 +15,112 @@ async def get_login_link(access_token: str = None) -> str:
13
15
  """
14
16
  Get a clickable login link for Meta Ads authentication.
15
17
 
18
+ NOTE: This method should only be used if you're using your own Facebook app.
19
+ If using Pipeboard authentication (recommended), set the PIPEBOARD_API_TOKEN
20
+ environment variable instead (token obtainable via https://pipeboard.co).
21
+
16
22
  Args:
17
23
  access_token: Meta API access token (optional - will use cached token if not provided)
18
24
 
19
25
  Returns:
20
26
  A clickable resource link for Meta authentication
21
27
  """
22
- # Check if we have a cached token
23
- cached_token = auth_manager.get_access_token()
24
- token_status = "No token" if not cached_token else "Valid token"
28
+ # Check if we're using pipeboard authentication
29
+ using_pipeboard = bool(os.environ.get("PIPEBOARD_API_TOKEN", ""))
25
30
 
26
- # If we already have a valid token and none was provided, just return success
27
- if cached_token and not access_token:
28
- logger.info("get_login_link called with existing valid token")
29
- return json.dumps({
30
- "message": "Already authenticated",
31
+ if using_pipeboard:
32
+ # Handle Pipeboard authentication
33
+ # Check if we have a cached token
34
+ cached_token = pipeboard_auth_manager.get_access_token()
35
+ token_status = "No token" if not cached_token else "Valid token"
36
+
37
+ # If we already have a valid token and none was provided, just return success
38
+ if cached_token and not access_token:
39
+ logger.info("get_login_link called with existing valid Pipeboard token")
40
+ return json.dumps({
41
+ "message": "Already authenticated with Pipeboard",
42
+ "token_status": token_status,
43
+ "token_preview": cached_token[:10] + "..." if cached_token else None,
44
+ "authentication_method": "pipeboard"
45
+ }, indent=2)
46
+
47
+ # Initiate the auth flow via Pipeboard
48
+ try:
49
+ auth_data = pipeboard_auth_manager.initiate_auth_flow()
50
+ login_url = auth_data.get("loginUrl")
51
+
52
+ # Return a special format that helps the LLM format the response properly
53
+ response = {
54
+ "login_url": login_url,
55
+ "token_status": token_status,
56
+ "markdown_link": f"[Click here to authenticate with Meta Ads via Pipeboard]({login_url})",
57
+ "message": "IMPORTANT: Please use the Markdown link format in your response to allow the user to click it.",
58
+ "instructions_for_llm": "You must present this link as clickable Markdown to the user using the markdown_link format provided.",
59
+ "authentication_method": "pipeboard",
60
+ "token_duration": "Approximately 60 days",
61
+ "note": "After authenticating, the token will be automatically saved."
62
+ }
63
+
64
+ return json.dumps(response, indent=2)
65
+ except Exception as e:
66
+ logger.error(f"Error initiating Pipeboard auth flow: {e}")
67
+ return json.dumps({
68
+ "error": f"Failed to initiate Pipeboard authentication: {str(e)}",
69
+ "message": "Please check your PIPEBOARD_API_TOKEN environment variable.",
70
+ "authentication_method": "pipeboard"
71
+ }, indent=2)
72
+ else:
73
+ # Original Meta authentication flow
74
+ # Check if we have a cached token
75
+ cached_token = auth_manager.get_access_token()
76
+ token_status = "No token" if not cached_token else "Valid token"
77
+
78
+ # If we already have a valid token and none was provided, just return success
79
+ if cached_token and not access_token:
80
+ logger.info("get_login_link called with existing valid token")
81
+ return json.dumps({
82
+ "message": "Already authenticated",
83
+ "token_status": token_status,
84
+ "token_preview": cached_token[:10] + "...",
85
+ "created_at": auth_manager.token_info.created_at if hasattr(auth_manager, "token_info") else None,
86
+ "expires_in": auth_manager.token_info.expires_in if hasattr(auth_manager, "token_info") else None,
87
+ "authentication_method": "meta_oauth"
88
+ }, indent=2)
89
+
90
+ # IMPORTANT: Start the callback server first by calling our helper function
91
+ # This ensures the server is ready before we provide the URL to the user
92
+ logger.info("Starting callback server for authentication")
93
+ port = start_callback_server()
94
+ logger.info(f"Callback server started on port {port}")
95
+
96
+ # Generate direct login URL
97
+ auth_manager.redirect_uri = f"http://localhost:{port}/callback" # Ensure port is set correctly
98
+ logger.info(f"Setting redirect URI to {auth_manager.redirect_uri}")
99
+ login_url = auth_manager.get_auth_url()
100
+ logger.info(f"Generated login URL: {login_url}")
101
+
102
+ # Check if we can exchange for long-lived tokens
103
+ token_exchange_supported = bool(META_APP_SECRET)
104
+ token_duration = "60 days" if token_exchange_supported else "1-2 hours"
105
+
106
+ # Return a special format that helps the LLM format the response properly
107
+ response = {
108
+ "login_url": login_url,
31
109
  "token_status": token_status,
32
- "token_preview": cached_token[:10] + "...",
33
- "created_at": auth_manager.token_info.created_at if hasattr(auth_manager, "token_info") else None,
34
- "expires_in": auth_manager.token_info.expires_in if hasattr(auth_manager, "token_info") else None
35
- }, indent=2)
36
-
37
- # IMPORTANT: Start the callback server first by calling our helper function
38
- # This ensures the server is ready before we provide the URL to the user
39
- logger.info("Starting callback server for authentication")
40
- port = start_callback_server()
41
- logger.info(f"Callback server started on port {port}")
42
-
43
- # Generate direct login URL
44
- auth_manager.redirect_uri = f"http://localhost:{port}/callback" # Ensure port is set correctly
45
- logger.info(f"Setting redirect URI to {auth_manager.redirect_uri}")
46
- login_url = auth_manager.get_auth_url()
47
- logger.info(f"Generated login URL: {login_url}")
48
-
49
- # Check if we can exchange for long-lived tokens
50
- token_exchange_supported = bool(META_APP_SECRET)
51
- token_duration = "60 days" if token_exchange_supported else "1-2 hours"
52
-
53
- # Return a special format that helps the LLM format the response properly
54
- response = {
55
- "login_url": login_url,
56
- "token_status": token_status,
57
- "server_status": f"Callback server running on port {port}",
58
- "markdown_link": f"[Click here to authenticate with Meta Ads]({login_url})",
59
- "message": "IMPORTANT: Please use the Markdown link format in your response to allow the user to click it.",
60
- "instructions_for_llm": "You must present this link as clickable Markdown to the user using the markdown_link format provided.",
61
- "token_exchange": "enabled" if token_exchange_supported else "disabled",
62
- "token_duration": token_duration,
63
- "token_exchange_message": f"Your authentication token will be valid for approximately {token_duration}." +
64
- (" Long-lived token exchange is enabled." if token_exchange_supported else
65
- " To enable long-lived tokens (60 days), set the META_APP_SECRET environment variable."),
66
- "note": "After authenticating, the token will be automatically saved."
67
- }
68
-
69
- # Wait a moment to ensure the server is fully started
70
- await asyncio.sleep(1)
71
-
72
- return json.dumps(response, indent=2)
110
+ "server_status": f"Callback server running on port {port}",
111
+ "markdown_link": f"[Click here to authenticate with Meta Ads]({login_url})",
112
+ "message": "IMPORTANT: Please use the Markdown link format in your response to allow the user to click it.",
113
+ "instructions_for_llm": "You must present this link as clickable Markdown to the user using the markdown_link format provided.",
114
+ "token_exchange": "enabled" if token_exchange_supported else "disabled",
115
+ "token_duration": token_duration,
116
+ "authentication_method": "meta_oauth",
117
+ "token_exchange_message": f"Your authentication token will be valid for approximately {token_duration}." +
118
+ (" Long-lived token exchange is enabled." if token_exchange_supported else
119
+ " To enable long-lived tokens (60 days), set the META_APP_SECRET environment variable."),
120
+ "note": "After authenticating, the token will be automatically saved."
121
+ }
122
+
123
+ # Wait a moment to ensure the server is fully started
124
+ await asyncio.sleep(1)
125
+
126
+ return json.dumps(response, indent=2)