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.
- meta_ads_mcp/__init__.py +3 -1
- meta_ads_mcp/api.py +122 -53
- meta_ads_mcp/core/__init__.py +2 -1
- meta_ads_mcp/core/ads.py +61 -1
- meta_ads_mcp/core/adsets.py +8 -3
- meta_ads_mcp/core/api.py +29 -1
- meta_ads_mcp/core/auth.py +91 -1270
- meta_ads_mcp/core/authentication.py +103 -49
- meta_ads_mcp/core/callback_server.py +958 -0
- 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.5.dist-info → meta_ads_mcp-0.2.8.dist-info}/METADATA +139 -32
- meta_ads_mcp-0.2.8.dist-info/RECORD +21 -0
- meta_ads_mcp-0.2.5.dist-info/RECORD +0 -19
- {meta_ads_mcp-0.2.5.dist-info → meta_ads_mcp-0.2.8.dist-info}/WHEEL +0 -0
- {meta_ads_mcp-0.2.5.dist-info → meta_ads_mcp-0.2.8.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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)
|