Spaces:
Paused
Paused
File size: 4,763 Bytes
f647629 04c12c4 f647629 04c12c4 f647629 2d37ec5 e2aaee8 a2dc155 ceeb737 04c12c4 f647629 a2dc155 e2aaee8 a2dc155 2d37ec5 f647629 a643202 f647629 561151f ceeb737 04c12c4 a2dc155 04c12c4 2d37ec5 04c12c4 2d37ec5 a2dc155 04c12c4 2d37ec5 e2aaee8 a2dc155 2d37ec5 04c12c4 a2dc155 2d37ec5 a643202 a2dc155 2d37ec5 04c12c4 a643202 04c12c4 2d37ec5 04c12c4 2d37ec5 04c12c4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
#!/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
@contextlib.asynccontextmanager
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
@app.middleware("http")
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
@app.get("/", response_class=HTMLResponse)
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
@app.get("/health")
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) |