meta-ads-mcp-python 1.0.79__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 +79 -0
- meta_ads_mcp/__main__.py +10 -0
- meta_ads_mcp/core/__init__.py +55 -0
- meta_ads_mcp/core/accounts.py +141 -0
- meta_ads_mcp/core/ads.py +2751 -0
- meta_ads_mcp/core/ads_library.py +74 -0
- meta_ads_mcp/core/adsets.py +666 -0
- meta_ads_mcp/core/api.py +431 -0
- meta_ads_mcp/core/auth.py +567 -0
- meta_ads_mcp/core/authentication.py +207 -0
- meta_ads_mcp/core/budget_schedules.py +70 -0
- meta_ads_mcp/core/callback_server.py +256 -0
- meta_ads_mcp/core/campaigns.py +379 -0
- meta_ads_mcp/core/duplication.py +523 -0
- meta_ads_mcp/core/http_auth_integration.py +307 -0
- meta_ads_mcp/core/insights.py +161 -0
- meta_ads_mcp/core/mcc.py +232 -0
- meta_ads_mcp/core/openai_deep_research.py +418 -0
- meta_ads_mcp/core/pipeboard_auth.py +510 -0
- meta_ads_mcp/core/reports.py +135 -0
- meta_ads_mcp/core/resources.py +46 -0
- meta_ads_mcp/core/server.py +391 -0
- meta_ads_mcp/core/targeting.py +542 -0
- meta_ads_mcp/core/utils.py +225 -0
- meta_ads_mcp/settings.py +33 -0
- meta_ads_mcp_python-1.0.79.dist-info/METADATA +187 -0
- meta_ads_mcp_python-1.0.79.dist-info/RECORD +29 -0
- meta_ads_mcp_python-1.0.79.dist-info/WHEEL +4 -0
- meta_ads_mcp_python-1.0.79.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Resource handling for Meta Ads API."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
import base64
|
|
5
|
+
from .utils import ad_creative_images
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def list_resources() -> Dict[str, Any]:
|
|
9
|
+
"""
|
|
10
|
+
List all available resources (like ad creative images)
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Dictionary with resources list
|
|
14
|
+
"""
|
|
15
|
+
resources = []
|
|
16
|
+
|
|
17
|
+
# Add all ad creative images as resources
|
|
18
|
+
for resource_id, image_info in ad_creative_images.items():
|
|
19
|
+
resources.append({
|
|
20
|
+
"uri": f"meta-ads://images/{resource_id}",
|
|
21
|
+
"mimeType": image_info["mime_type"],
|
|
22
|
+
"name": image_info["name"]
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {"resources": resources}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def get_resource(resource_id: str) -> Dict[str, Any]:
|
|
29
|
+
"""
|
|
30
|
+
Get a specific resource by URI
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
resource_id: Unique identifier for the resource
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary with resource data
|
|
37
|
+
"""
|
|
38
|
+
if resource_id in ad_creative_images:
|
|
39
|
+
image_info = ad_creative_images[resource_id]
|
|
40
|
+
return {
|
|
41
|
+
"data": base64.b64encode(image_info["data"]).decode("utf-8"),
|
|
42
|
+
"mimeType": image_info["mime_type"]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Resource not found
|
|
46
|
+
return {"error": f"Resource not found: {resource_id}"}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""MCP server configuration for Meta Ads API."""
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import webbrowser
|
|
8
|
+
import json
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
from .auth import login as login_auth
|
|
13
|
+
from .pipeboard_auth import pipeboard_auth_manager
|
|
14
|
+
from .resources import list_resources, get_resource
|
|
15
|
+
from .utils import logger
|
|
16
|
+
from ..settings import load_settings
|
|
17
|
+
|
|
18
|
+
settings = load_settings()
|
|
19
|
+
|
|
20
|
+
# Initialize FastMCP server
|
|
21
|
+
mcp_server = FastMCP("meta-ads")
|
|
22
|
+
|
|
23
|
+
# Register resource URIs
|
|
24
|
+
mcp_server.resource(uri="meta-ads://resources")(list_resources)
|
|
25
|
+
mcp_server.resource(uri="meta-ads://images/{resource_id}")(get_resource)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StreamableHTTPHandler:
|
|
29
|
+
"""Handles stateless Streamable HTTP requests for Meta Ads MCP"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
"""Initialize handler with no session storage - all auth per request"""
|
|
33
|
+
logger.debug("StreamableHTTPHandler initialized for stateless operation")
|
|
34
|
+
|
|
35
|
+
def handle_request(self, request_headers: Dict[str, str], request_body: Dict[str, Any]) -> Dict[str, Any]:
|
|
36
|
+
"""Handle individual request with authentication
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
request_headers: HTTP request headers
|
|
40
|
+
request_body: JSON-RPC request body
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
JSON response with auth status and any tool results
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
# Extract authentication configuration from headers
|
|
47
|
+
auth_config = self.get_auth_config_from_headers(request_headers)
|
|
48
|
+
logger.debug(f"Auth method detected: {auth_config['auth_method']}")
|
|
49
|
+
|
|
50
|
+
# Handle based on auth method
|
|
51
|
+
if auth_config['auth_method'] == 'bearer':
|
|
52
|
+
return self.handle_bearer_request(auth_config, request_body)
|
|
53
|
+
elif auth_config['auth_method'] == 'custom_meta_app':
|
|
54
|
+
return self.handle_custom_app_request(auth_config, request_body)
|
|
55
|
+
else:
|
|
56
|
+
return self.handle_unauthenticated_request(request_body)
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Error handling request: {e}")
|
|
60
|
+
return {
|
|
61
|
+
'jsonrpc': '2.0',
|
|
62
|
+
'error': {
|
|
63
|
+
'code': -32603,
|
|
64
|
+
'message': 'Internal error',
|
|
65
|
+
'data': str(e)
|
|
66
|
+
},
|
|
67
|
+
'id': request_body.get('id')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def get_auth_config_from_headers(self, request_headers: Dict[str, str]) -> Dict[str, Any]:
|
|
71
|
+
"""Extract authentication configuration from HTTP headers
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
request_headers: HTTP request headers
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dictionary with auth method and relevant credentials
|
|
78
|
+
"""
|
|
79
|
+
# Security validation - only allow safe headers
|
|
80
|
+
ALLOWED_VIA_HEADERS = {
|
|
81
|
+
'pipeboard_api_token': True, # Primary method - simple and secure
|
|
82
|
+
'meta_app_id': True, # Fallback only - triggers OAuth complexity
|
|
83
|
+
'meta_app_secret': False, # Server environment only
|
|
84
|
+
'meta_access_token': False, # Use proper auth flows instead
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# PRIMARY: Check for Bearer token in Authorization header (handles 90%+ of cases)
|
|
88
|
+
auth_header = request_headers.get('Authorization') or request_headers.get('authorization')
|
|
89
|
+
if auth_header and auth_header.lower().startswith('bearer '):
|
|
90
|
+
token = auth_header[7:].strip()
|
|
91
|
+
logger.info("Bearer authentication detected (primary path)")
|
|
92
|
+
return {
|
|
93
|
+
'auth_method': 'bearer',
|
|
94
|
+
'bearer_token': token,
|
|
95
|
+
'requires_oauth': False # Simple token-based auth
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# FALLBACK: Custom Meta app (minority of users)
|
|
99
|
+
meta_app_id = request_headers.get('X-META-APP-ID') or request_headers.get('x-meta-app-id')
|
|
100
|
+
if meta_app_id:
|
|
101
|
+
logger.debug("Custom Meta app authentication detected (fallback path)")
|
|
102
|
+
return {
|
|
103
|
+
'auth_method': 'custom_meta_app',
|
|
104
|
+
'meta_app_id': meta_app_id,
|
|
105
|
+
'requires_oauth': True # Complex OAuth flow required
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# No authentication provided
|
|
109
|
+
logger.warning("No authentication method detected in headers")
|
|
110
|
+
return {
|
|
111
|
+
'auth_method': 'none',
|
|
112
|
+
'requires_oauth': False
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def handle_bearer_request(self, auth_config: Dict[str, Any], request_body: Dict[str, Any]) -> Dict[str, Any]:
|
|
116
|
+
"""Handle request with Bearer token (primary path)
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
auth_config: Authentication configuration from headers
|
|
120
|
+
request_body: JSON-RPC request body
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
JSON response ready for tool execution
|
|
124
|
+
"""
|
|
125
|
+
logger.debug("Processing Bearer authenticated request")
|
|
126
|
+
token = auth_config['bearer_token']
|
|
127
|
+
|
|
128
|
+
# Token is ready to use immediately for API calls
|
|
129
|
+
# TODO: In next phases, this will execute the actual tool call
|
|
130
|
+
return {
|
|
131
|
+
'jsonrpc': '2.0',
|
|
132
|
+
'result': {
|
|
133
|
+
'status': 'ready',
|
|
134
|
+
'auth_method': 'bearer',
|
|
135
|
+
'message': 'Authentication successful with Bearer token',
|
|
136
|
+
'token_source': 'bearer_header'
|
|
137
|
+
},
|
|
138
|
+
'id': request_body.get('id')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def handle_custom_app_request(self, auth_config: Dict[str, Any], request_body: Dict[str, Any]) -> Dict[str, Any]:
|
|
142
|
+
"""Handle request with custom Meta app (fallback path)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
auth_config: Authentication configuration from headers
|
|
146
|
+
request_body: JSON-RPC request body
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
JSON response indicating OAuth flow is required
|
|
150
|
+
"""
|
|
151
|
+
logger.debug("Processing custom Meta app request (OAuth required)")
|
|
152
|
+
|
|
153
|
+
# This may require OAuth flow initiation
|
|
154
|
+
# Each request is independent - no session state
|
|
155
|
+
return {
|
|
156
|
+
'jsonrpc': '2.0',
|
|
157
|
+
'result': {
|
|
158
|
+
'status': 'oauth_required',
|
|
159
|
+
'auth_method': 'custom_meta_app',
|
|
160
|
+
'meta_app_id': auth_config['meta_app_id'],
|
|
161
|
+
'message': 'OAuth flow required for custom Meta app authentication',
|
|
162
|
+
'next_steps': 'Use get_login_link tool to initiate OAuth flow'
|
|
163
|
+
},
|
|
164
|
+
'id': request_body.get('id')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def handle_unauthenticated_request(self, request_body: Dict[str, Any]) -> Dict[str, Any]:
|
|
168
|
+
"""Handle request with no authentication
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
request_body: JSON-RPC request body
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
JSON error response requesting authentication
|
|
175
|
+
"""
|
|
176
|
+
logger.warning("Unauthenticated request received")
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
'jsonrpc': '2.0',
|
|
180
|
+
'error': {
|
|
181
|
+
'code': -32600,
|
|
182
|
+
'message': 'Authentication required',
|
|
183
|
+
'data': {
|
|
184
|
+
'supported_methods': [
|
|
185
|
+
'Authorization: Bearer <token> (recommended)',
|
|
186
|
+
'X-META-APP-ID: Custom Meta app OAuth (advanced users)'
|
|
187
|
+
],
|
|
188
|
+
'documentation': 'https://github.com/pipeboard-co/meta-ads-mcp'
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
'id': request_body.get('id')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def login_cli():
|
|
196
|
+
"""
|
|
197
|
+
Command-line function to authenticate with Meta
|
|
198
|
+
"""
|
|
199
|
+
logger.info("Starting Meta Ads CLI authentication flow")
|
|
200
|
+
print("Starting Meta Ads CLI authentication flow...")
|
|
201
|
+
|
|
202
|
+
# Call the common login function
|
|
203
|
+
login_auth()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def main():
|
|
207
|
+
"""Main entry point for the package"""
|
|
208
|
+
# Log startup information
|
|
209
|
+
logger.info("Meta Ads MCP server starting")
|
|
210
|
+
logger.debug(f"Python version: {sys.version}")
|
|
211
|
+
logger.debug(f"Args: {sys.argv}")
|
|
212
|
+
|
|
213
|
+
# Initialize argument parser
|
|
214
|
+
parser = argparse.ArgumentParser(
|
|
215
|
+
description="Meta Ads MCP Server - Model Context Protocol server for Meta Ads API",
|
|
216
|
+
epilog="For more information, see https://github.com/pipeboard-co/meta-ads-mcp"
|
|
217
|
+
)
|
|
218
|
+
parser.add_argument("--login", action="store_true", help="Authenticate with Meta and store the token")
|
|
219
|
+
parser.add_argument("--app-id", type=str, help="Meta App ID (Client ID) for authentication")
|
|
220
|
+
parser.add_argument("--version", action="store_true", help="Show the version of the package")
|
|
221
|
+
|
|
222
|
+
# Transport configuration arguments
|
|
223
|
+
parser.add_argument("--transport", type=str, choices=["stdio", "streamable-http"],
|
|
224
|
+
default="stdio",
|
|
225
|
+
help="Transport method: 'stdio' for MCP clients (default), 'streamable-http' for HTTP API access")
|
|
226
|
+
parser.add_argument("--port", type=int, default=settings.server_port,
|
|
227
|
+
help="Port for Streamable HTTP transport (only used with --transport streamable-http)")
|
|
228
|
+
parser.add_argument("--host", type=str, default=settings.server_host,
|
|
229
|
+
help="Host for Streamable HTTP transport (only used with --transport streamable-http)")
|
|
230
|
+
parser.add_argument("--sse-response", action="store_true",
|
|
231
|
+
help="Use SSE response format instead of JSON (default: JSON, only used with --transport streamable-http)")
|
|
232
|
+
|
|
233
|
+
args = parser.parse_args()
|
|
234
|
+
logger.debug(f"Parsed args: login={args.login}, app_id={args.app_id}, version={args.version}")
|
|
235
|
+
logger.debug(f"Transport args: transport={args.transport}, port={args.port}, host={args.host}, sse_response={args.sse_response}")
|
|
236
|
+
|
|
237
|
+
# Validate CLI argument combinations
|
|
238
|
+
if args.transport == "stdio" and (args.port != 8080 or args.host != "localhost" or args.sse_response):
|
|
239
|
+
logger.warning("HTTP transport arguments (--port, --host, --sse-response) are ignored when using stdio transport")
|
|
240
|
+
print("Warning: HTTP transport arguments are ignored when using stdio transport")
|
|
241
|
+
|
|
242
|
+
# Update app ID if provided as environment variable or command line arg
|
|
243
|
+
from .auth import auth_manager, meta_config
|
|
244
|
+
|
|
245
|
+
# Check environment variable first (early init)
|
|
246
|
+
env_app_id = os.environ.get("META_APP_ID")
|
|
247
|
+
if env_app_id:
|
|
248
|
+
logger.debug(f"Found META_APP_ID in environment: {env_app_id}")
|
|
249
|
+
else:
|
|
250
|
+
logger.warning("META_APP_ID not found in environment variables")
|
|
251
|
+
|
|
252
|
+
# Command line takes precedence
|
|
253
|
+
if args.app_id:
|
|
254
|
+
logger.info(f"Setting app_id from command line: {args.app_id}")
|
|
255
|
+
auth_manager.app_id = args.app_id
|
|
256
|
+
meta_config.set_app_id(args.app_id)
|
|
257
|
+
elif env_app_id:
|
|
258
|
+
logger.info(f"Setting app_id from environment: {env_app_id}")
|
|
259
|
+
auth_manager.app_id = env_app_id
|
|
260
|
+
meta_config.set_app_id(env_app_id)
|
|
261
|
+
|
|
262
|
+
# Log the final app ID that will be used
|
|
263
|
+
logger.info(f"Final app_id from meta_config: {meta_config.get_app_id()}")
|
|
264
|
+
logger.info(f"Final app_id from auth_manager: {auth_manager.app_id}")
|
|
265
|
+
logger.info(f"ENV META_APP_ID: {os.environ.get('META_APP_ID')}")
|
|
266
|
+
|
|
267
|
+
# Show version if requested
|
|
268
|
+
if args.version:
|
|
269
|
+
from meta_ads_mcp import __version__
|
|
270
|
+
logger.info(f"Displaying version: {__version__}")
|
|
271
|
+
print(f"Meta Ads MCP v{__version__}")
|
|
272
|
+
return 0
|
|
273
|
+
|
|
274
|
+
# Handle login command
|
|
275
|
+
if args.login:
|
|
276
|
+
login_cli()
|
|
277
|
+
return 0
|
|
278
|
+
|
|
279
|
+
# Check for Pipeboard authentication and token
|
|
280
|
+
pipeboard_api_token = os.environ.get("PIPEBOARD_API_TOKEN")
|
|
281
|
+
if pipeboard_api_token:
|
|
282
|
+
logger.info("Using Pipeboard authentication")
|
|
283
|
+
print(" Pipeboard authentication enabled")
|
|
284
|
+
print(f" API token: {pipeboard_api_token[:8]}...{pipeboard_api_token[-4:]}")
|
|
285
|
+
# Check for existing token
|
|
286
|
+
token = pipeboard_auth_manager.get_access_token()
|
|
287
|
+
if not token:
|
|
288
|
+
logger.info("No valid Pipeboard token found. Initiating browser-based authentication flow.")
|
|
289
|
+
print("No valid Meta token found. Opening browser for authentication...")
|
|
290
|
+
try:
|
|
291
|
+
# Initialize the auth flow and get the login URL
|
|
292
|
+
auth_data = pipeboard_auth_manager.initiate_auth_flow()
|
|
293
|
+
login_url = auth_data.get('loginUrl')
|
|
294
|
+
if login_url:
|
|
295
|
+
logger.info(f"Opening browser with login URL: {login_url}")
|
|
296
|
+
webbrowser.open(login_url)
|
|
297
|
+
print("Please authorize the application in your browser.")
|
|
298
|
+
print("After authorization, the token will be automatically retrieved.")
|
|
299
|
+
print("Waiting for authentication to complete...")
|
|
300
|
+
|
|
301
|
+
# Poll for token completion
|
|
302
|
+
max_attempts = 30 # Try for 30 * 2 = 60 seconds
|
|
303
|
+
for attempt in range(max_attempts):
|
|
304
|
+
print(f"Waiting for authentication... ({attempt+1}/{max_attempts})")
|
|
305
|
+
# Try to get the token again
|
|
306
|
+
token = pipeboard_auth_manager.get_access_token(force_refresh=True)
|
|
307
|
+
if token:
|
|
308
|
+
print("Authentication successful!")
|
|
309
|
+
break
|
|
310
|
+
time.sleep(2) # Wait 2 seconds between attempts
|
|
311
|
+
|
|
312
|
+
if not token:
|
|
313
|
+
print("Authentication timed out. Starting server anyway.")
|
|
314
|
+
print("You may need to restart the server after completing authentication.")
|
|
315
|
+
else:
|
|
316
|
+
logger.error("No login URL received from Pipeboard API")
|
|
317
|
+
print("Error: Could not get authentication URL. Check your API token.")
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error(f"Error initiating browser-based authentication: {e}")
|
|
320
|
+
print(f"Error: Could not start authentication: {e}")
|
|
321
|
+
else:
|
|
322
|
+
print(f" Valid Pipeboard access token found")
|
|
323
|
+
print(f" Token preview: {token[:10]}...{token[-5:]}")
|
|
324
|
+
|
|
325
|
+
# Transport-specific server initialization and startup
|
|
326
|
+
if args.transport == "streamable-http":
|
|
327
|
+
logger.info(f"Starting MCP server with Streamable HTTP transport on {args.host}:{args.port}")
|
|
328
|
+
logger.info("Mode: Stateless (no session persistence)")
|
|
329
|
+
logger.info(f"Response format: {'SSE' if args.sse_response else 'JSON'}")
|
|
330
|
+
logger.info("Primary auth method: Bearer Token (recommended)")
|
|
331
|
+
logger.info("Fallback auth method: Custom Meta App OAuth (complex setup)")
|
|
332
|
+
|
|
333
|
+
print(f"Starting Meta Ads MCP server with Streamable HTTP transport")
|
|
334
|
+
print(f"Server will listen on {args.host}:{args.port}")
|
|
335
|
+
print(f"Response format: {'SSE' if args.sse_response else 'JSON'}")
|
|
336
|
+
print("Primary authentication: Bearer Token (via Authorization: Bearer <token> header)")
|
|
337
|
+
print("Fallback authentication: Custom Meta App OAuth (via X-META-APP-ID header)")
|
|
338
|
+
|
|
339
|
+
# Configure the existing server with streamable HTTP settings
|
|
340
|
+
mcp_server.settings.host = args.host
|
|
341
|
+
mcp_server.settings.port = args.port
|
|
342
|
+
mcp_server.settings.stateless_http = True
|
|
343
|
+
mcp_server.settings.json_response = not args.sse_response
|
|
344
|
+
|
|
345
|
+
# Import all tool modules to ensure they are registered
|
|
346
|
+
logger.info("Ensuring all tools are registered for HTTP transport")
|
|
347
|
+
from . import accounts, campaigns, adsets, ads, insights, authentication
|
|
348
|
+
from . import ads_library, budget_schedules, reports, openai_deep_research
|
|
349
|
+
from . import mcc
|
|
350
|
+
|
|
351
|
+
# NEW: Setup HTTP authentication middleware
|
|
352
|
+
logger.info("Setting up HTTP authentication middleware")
|
|
353
|
+
try:
|
|
354
|
+
from .http_auth_integration import setup_fastmcp_http_auth
|
|
355
|
+
|
|
356
|
+
# Setup the FastMCP HTTP auth integration
|
|
357
|
+
setup_fastmcp_http_auth(mcp_server)
|
|
358
|
+
logger.info("FastMCP HTTP authentication integration setup successful")
|
|
359
|
+
print(" FastMCP HTTP authentication integration enabled")
|
|
360
|
+
print(" - Bearer tokens via Authorization: Bearer <token> header")
|
|
361
|
+
print(" - Direct Meta tokens via X-META-ACCESS-TOKEN header")
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.error(f"Failed to setup FastMCP HTTP authentication integration: {e}")
|
|
365
|
+
print(f" FastMCP HTTP authentication integration setup failed: {e}")
|
|
366
|
+
print(" Server will still start but may not support header-based auth")
|
|
367
|
+
|
|
368
|
+
# Log final server configuration
|
|
369
|
+
logger.info(f"FastMCP server configured with:")
|
|
370
|
+
logger.info(f" - Host: {mcp_server.settings.host}")
|
|
371
|
+
logger.info(f" - Port: {mcp_server.settings.port}")
|
|
372
|
+
logger.info(f" - Stateless HTTP: {mcp_server.settings.stateless_http}")
|
|
373
|
+
logger.info(f" - JSON Response: {mcp_server.settings.json_response}")
|
|
374
|
+
logger.info(f" - Streamable HTTP Path: {mcp_server.settings.streamable_http_path}")
|
|
375
|
+
|
|
376
|
+
# Start the FastMCP server with Streamable HTTP transport
|
|
377
|
+
try:
|
|
378
|
+
logger.info("Starting FastMCP server with Streamable HTTP transport")
|
|
379
|
+
print(f" Server configured successfully")
|
|
380
|
+
print(f" URL: http://{args.host}:{args.port}{mcp_server.settings.streamable_http_path}/")
|
|
381
|
+
print(f" Mode: {'Stateless' if mcp_server.settings.stateless_http else 'Stateful'}")
|
|
382
|
+
print(f" Format: {'JSON' if mcp_server.settings.json_response else 'SSE'}")
|
|
383
|
+
mcp_server.run(transport="streamable-http")
|
|
384
|
+
except Exception as e:
|
|
385
|
+
logger.error(f"Error starting Streamable HTTP server: {e}")
|
|
386
|
+
print(f"Error: Failed to start Streamable HTTP server: {e}")
|
|
387
|
+
return 1
|
|
388
|
+
else:
|
|
389
|
+
# Default stdio transport
|
|
390
|
+
logger.info("Starting MCP server with stdio transport")
|
|
391
|
+
mcp_server.run(transport='stdio')
|