Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| """ | |
| HuggingFace Spaces entry point for the Weights & Biases MCP Server. | |
| Using the correct FastMCP mounting pattern with streamable_http_app(). | |
| """ | |
| import os | |
| import sys | |
| import logging | |
| import contextlib | |
| from pathlib import Path | |
| # Add the src directory to Python path | |
| sys.path.insert(0, str(Path(__file__).parent / "src")) | |
| # Configure W&B directories for HF Spaces (must be done before importing wandb) | |
| os.environ["WANDB_CACHE_DIR"] = "/tmp/.wandb_cache" | |
| os.environ["WANDB_CONFIG_DIR"] = "/tmp/.wandb_config" | |
| os.environ["WANDB_DATA_DIR"] = "/tmp/.wandb_data" | |
| os.environ["HOME"] = "/tmp" | |
| os.environ["WANDB_SILENT"] = "True" | |
| os.environ["WEAVE_SILENT"] = "True" | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from mcp.server.fastmcp import FastMCP | |
| # Import W&B setup functions | |
| from wandb_mcp_server.server import ( | |
| validate_and_get_api_key, | |
| setup_wandb_login, | |
| configure_wandb_logging, | |
| initialize_weave_tracing, | |
| register_tools, | |
| ServerMCPArgs | |
| ) | |
| # Import authentication | |
| from wandb_mcp_server.auth import mcp_auth_middleware | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger("wandb-mcp-server") | |
| # Read the index.html file content | |
| INDEX_HTML_PATH = Path(__file__).parent / "index.html" | |
| with open(INDEX_HTML_PATH, "r") as f: | |
| INDEX_HTML_CONTENT = f.read() | |
| # Initialize W&B | |
| logger.info("Initializing W&B configuration...") | |
| configure_wandb_logging() | |
| args = ServerMCPArgs( | |
| transport="http", | |
| host="0.0.0.0", | |
| port=7860, | |
| wandb_api_key=os.environ.get("WANDB_API_KEY") | |
| ) | |
| wandb_configured = False | |
| api_key = validate_and_get_api_key(args) | |
| if api_key: | |
| try: | |
| setup_wandb_login(api_key) | |
| initialize_weave_tracing() | |
| wandb_configured = True | |
| logger.info("Server W&B API key configured successfully") | |
| except Exception as e: | |
| logger.warning(f"Failed to configure server W&B API key: {e}") | |
| else: | |
| logger.info("No server W&B API key configured - clients will provide their own") | |
| # Create the MCP server | |
| logger.info("Creating W&B MCP server...") | |
| mcp = FastMCP("wandb-mcp-server") | |
| # Register all W&B tools | |
| register_tools(mcp) | |
| # Create lifespan context manager for session management | |
| async def lifespan(app: FastAPI): | |
| """Manage MCP session lifecycle.""" | |
| async with mcp.session_manager.run(): | |
| logger.info("MCP session manager started") | |
| yield | |
| logger.info("MCP session manager stopped") | |
| # Create the main FastAPI app with lifespan | |
| app = FastAPI( | |
| title="Weights & Biases MCP Server", | |
| description="Model Context Protocol server for W&B", | |
| lifespan=lifespan | |
| ) | |
| # Add CORS middleware | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Add authentication middleware for MCP endpoints | |
| async def auth_middleware(request, call_next): | |
| """Add OAuth 2.1 Bearer token authentication for MCP endpoints.""" | |
| return await mcp_auth_middleware(request, call_next) | |
| # Add custom routes | |
| async def index(): | |
| """Serve the landing page.""" | |
| return INDEX_HTML_CONTENT | |
| # Removed OAuth endpoints - only API key authentication is supported | |
| # See AUTH_README.md for details on why full OAuth isn't feasible | |
| async def health(): | |
| """Health check endpoint.""" | |
| # list_tools is async, so we need to handle it properly | |
| try: | |
| tools = await mcp.list_tools() | |
| tool_count = len(tools) | |
| except: | |
| tool_count = 0 | |
| auth_status = "disabled" if os.environ.get("MCP_AUTH_DISABLED", "false").lower() == "true" else "enabled" | |
| return { | |
| "status": "healthy", | |
| "service": "wandb-mcp-server", | |
| "wandb_configured": wandb_configured, | |
| "tools_registered": tool_count, | |
| "authentication": auth_status | |
| } | |
| # Mount the MCP streamable HTTP app | |
| # Note: streamable_http_app() creates internal routes at /mcp | |
| # So we mount at root to avoid /mcp/mcp double path | |
| mcp_app = mcp.streamable_http_app() | |
| logger.info("Mounting MCP streamable HTTP app") | |
| # Mount at root, so MCP endpoint will be at /mcp (not /mcp/mcp) | |
| app.mount("/", mcp_app) | |
| # Port for HF Spaces | |
| PORT = int(os.environ.get("PORT", "7860")) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| logger.info(f"Starting server on 0.0.0.0:{PORT}") | |
| logger.info("Landing page: /") | |
| logger.info("Health check: /health") | |
| logger.info("MCP endpoint: /mcp") | |
| uvicorn.run(app, host="0.0.0.0", port=PORT) |