mashrur950's picture
added disable button in generate key
bd41a78
"""
FleetMind MCP Server - Hugging Face Space Entry Point (Track 1)
This file serves as the entry point for HuggingFace Space deployment.
Exposes 29 MCP tools via Server-Sent Events (SSE) endpoint for AI clients.
Architecture:
User β†’ MCP Client (Claude Desktop, Continue, etc.)
β†’ SSE Endpoint (this file)
β†’ FleetMind MCP Server (server.py)
β†’ Tools (chat/tools.py)
β†’ Database (PostgreSQL)
For Track 1: Building MCP Servers - Enterprise Category
https://huggingface.co/MCP-1st-Birthday
Compatible with:
- Claude Desktop (via SSE transport)
- Continue.dev (VS Code extension)
- Cline (VS Code extension)
- Any MCP client supporting SSE protocol
"""
import os
import sys
import logging
from pathlib import Path
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent))
# Configure logging for HuggingFace Space
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
# Import the MCP server instance
from server import mcp
# ============================================================================
# HUGGING FACE SPACE CONFIGURATION
# ============================================================================
# HuggingFace Space default port
# NOTE: When using proxy.py, FastMCP runs on 7861 (internal port)
# The proxy runs on 7860 (public) and forwards requests here
HF_SPACE_PORT = int(os.getenv("PORT", 7861))
HF_SPACE_HOST = os.getenv("HOST", "0.0.0.0")
# ============================================================================
# MAIN ENTRY POINT
# ============================================================================
if __name__ == "__main__":
logger.info("=" * 70)
logger.info("FleetMind MCP Server - HuggingFace Space (Track 1)")
logger.info("=" * 70)
logger.info("MCP Server: FleetMind Dispatch Coordinator v1.0.0")
logger.info("Protocol: Model Context Protocol (MCP)")
logger.info("Transport: Server-Sent Events (SSE)")
logger.info(f"SSE Endpoint: https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse")
logger.info("=" * 70)
logger.info("Features:")
logger.info(" βœ“ 29 AI Tools (Order + Driver + Assignment Management)")
logger.info(" βœ“ 2 Real-Time Resources (orders://all, drivers://all)")
logger.info(" βœ“ Gemini 2.0 Flash AI - Intelligent Assignment")
logger.info(" βœ“ Google Maps API Integration (Routes + Geocoding)")
logger.info(" βœ“ Weather-Aware Routing (OpenWeatherMap)")
logger.info(" βœ“ PostgreSQL Database (Neon)")
logger.info("=" * 70)
logger.info("Compatible Clients:")
logger.info(" β€’ Claude Desktop")
logger.info(" β€’ Continue.dev (VS Code)")
logger.info(" β€’ Cline (VS Code)")
logger.info(" β€’ Any MCP-compatible client")
logger.info("=" * 70)
logger.info("How to Connect (Claude Desktop):")
logger.info(' Add to claude_desktop_config.json:')
logger.info(' {')
logger.info(' "mcpServers": {')
logger.info(' "fleetmind": {')
logger.info(' "command": "npx",')
logger.info(' "args": [')
logger.info(' "mcp-remote",')
logger.info(' "https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse"')
logger.info(' ]')
logger.info(' }')
logger.info(' }')
logger.info(' }')
logger.info("=" * 70)
logger.info(f"Starting SSE server on {HF_SPACE_HOST}:{HF_SPACE_PORT}...")
logger.info("Waiting for MCP client connections...")
logger.info("=" * 70)
try:
# Add web routes for landing page and API key generation
from starlette.responses import HTMLResponse, JSONResponse
from starlette.requests import Request
from database.api_keys import generate_api_key as db_generate_api_key
# =====================================================================
# PROXY-BASED AUTHENTICATION
# Authentication is handled by proxy.py (port 7860)
# The proxy captures API keys from SSE connections and injects them
# into tool requests before forwarding to FastMCP (port 7861)
# =====================================================================
logger.info("[Auth] Using proxy-based authentication")
logger.info("[Auth] Proxy captures API keys and injects into tool requests")
@mcp.custom_route("/", methods=["GET"])
async def landing_page(request):
"""Landing page with MCP connection information"""
return HTMLResponse("""
<!DOCTYPE html>
<html>
<head>
<title>FleetMind MCP Server</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; max-width: 1000px; margin: 50px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
.container { background: #1e293b; padding: 40px; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.4); }
h1 { color: #f1f5f9; margin-top: 0; }
h2 { color: #e2e8f0; border-bottom: 2px solid #334155; padding-bottom: 10px; }
h3 { color: #cbd5e1; }
code { background: #334155; color: #60a5fa; padding: 3px 8px; border-radius: 4px; font-family: 'Courier New', monospace; }
pre { background: #0f172a; color: #f1f5f9; padding: 20px; border-radius: 8px; overflow-x: auto; border: 1px solid #334155; }
.endpoint { background: #1e3a5f; padding: 15px; margin: 15px 0; border-left: 4px solid #3b82f6; border-radius: 4px; }
.feature { background: #134e4a; padding: 15px; margin: 10px 0; border-left: 4px solid #10b981; border-radius: 4px; }
.badge { display: inline-block; background: #3b82f6; color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; margin: 5px; }
a { color: #60a5fa; text-decoration: none; }
a:hover { text-decoration: underline; color: #93c5fd; }
ol { line-height: 1.8; }
ul { line-height: 1.8; }
p { color: #cbd5e1; }
</style>
</head>
<body>
<div class="container">
<h1>🚚 FleetMind MCP Server</h1>
<p><strong>Enterprise Model Context Protocol Server for AI-Powered Delivery Dispatch</strong></p>
<p><span class="badge">MCP 1st Birthday Hackathon</span> <span class="badge">Track 1: Building MCP</span> <span class="badge">Enterprise Category</span></p>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<h2>πŸ”Œ MCP Server Connection</h2>
<div class="endpoint">
<strong>SSE Endpoint URL:</strong><br>
<code>https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse</code>
</div>
<h3>πŸ”‘ Step 1: Get Your API Key</h3>
<p style="text-align: center; margin: 20px 0;">
<a href="/generate-key" style="display: inline-block; background: #3b82f6; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: bold; font-size: 18px;">
Generate API Key β†’
</a>
</p>
<p>Click the button above to generate your unique API key. You'll need this to authenticate with the server.</p>
<h3>βš™οΈ Step 2: Configure Claude Desktop</h3>
<p>Add this to your <code>claude_desktop_config.json</code> file:</p>
<pre>{
"mcpServers": {
"fleetmind": {
"command": "npx",
"args": [
"mcp-remote",
"https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse<strong style="color: #60a5fa;">?api_key=fm_your_api_key_here</strong>"
]
}
}
}</pre>
<p style="background: #1e3a5f; padding: 10px; border-radius: 6px; margin: 10px 0; border-left: 4px solid #3b82f6;">
πŸ’‘ <strong>Important:</strong> Add your API key as a query parameter (<code>?api_key=...</code>) in the URL, not in the <code>env</code> section!
</p>
<h3>πŸ“‹ Step 3: Connect</h3>
<ol>
<li><strong>Generate your API key</strong> using the button above</li>
<li>Install <a href="https://claude.ai/download" target="_blank">Claude Desktop</a></li>
<li>Locate your <code>claude_desktop_config.json</code> file</li>
<li>Add the configuration, replacing <code>fm_your_api_key_here</code> with your actual API key <strong>in the URL</strong></li>
<li>Restart Claude Desktop</li>
<li>Look for "FleetMind" in the πŸ”Œ tools menu</li>
</ol>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<h2>πŸ› οΈ Available Tools (29 Total)</h2>
<div class="feature">
<strong>πŸ“ Geocoding & Routing (3 tools):</strong><br>
geocode_address, calculate_route, calculate_intelligent_route
</div>
<div class="feature">
<strong>πŸ“¦ Order Management (8 tools):</strong><br>
create_order, count_orders, fetch_orders, get_order_details, search_orders, get_incomplete_orders, update_order, delete_order
</div>
<div class="feature">
<strong>πŸ‘₯ Driver Management (8 tools):</strong><br>
create_driver, count_drivers, fetch_drivers, get_driver_details, search_drivers, get_available_drivers, update_driver, delete_driver
</div>
<div class="feature">
<strong>πŸ”— Assignment Management (8 tools):</strong><br>
create_assignment, <strong>auto_assign_order</strong>, <strong>intelligent_assign_order</strong>, get_assignment_details, update_assignment, unassign_order, complete_delivery, fail_delivery
</div>
<div class="feature">
<strong>πŸ—‘οΈ Bulk Operations (2 tools):</strong><br>
delete_all_orders, delete_all_drivers
</div>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<h2>⭐ Key Features</h2>
<ul>
<li><strong>πŸ”‘ API Key Authentication</strong> - Secure multi-tenant access with personal API keys (URL-based)</li>
<li><strong>πŸ‘₯ Multi-Tenant Isolation</strong> - Complete data separation via user_id (deterministic from email)</li>
<li><strong>🧠 Gemini 2.0 Flash AI</strong> - Intelligent order assignment with detailed reasoning</li>
<li><strong>🌦️ Weather-Aware Routing</strong> - Safety-first delivery planning with OpenWeatherMap</li>
<li><strong>🚦 Real-Time Traffic</strong> - Google Routes API integration with live traffic data</li>
<li><strong>πŸ“Š SLA Tracking</strong> - Automatic on-time performance monitoring</li>
<li><strong>πŸ—„οΈ PostgreSQL Database</strong> - Production-grade data storage with user_id filtering (Neon)</li>
<li><strong>πŸš€ Multi-Client Support</strong> - Works with Claude Desktop, Continue, Cline, any MCP client</li>
</ul>
<h2>πŸ”’ Authentication & Security</h2>
<div class="feature">
<strong>How Authentication Works:</strong><br>
1. Generate API key via <a href="/generate-key">/generate-key</a><br>
2. API key hashed (SHA-256) before storage<br>
3. User ID generated: <code>user_&#123;MD5(email)[:12]&#125;</code><br>
4. Add key to URL: <code>?api_key=fm_...</code><br>
5. Server validates on each SSE connection<br>
6. All queries filter by user_id for isolation
</div>
<div class="feature">
<strong>Security Features:</strong><br>
βœ… One-Time Display (keys shown once)<br>
βœ… Hashed Storage (SHA-256, never plaintext)<br>
βœ… Database-Level Isolation (all tables have user_id)<br>
βœ… Deterministic User IDs (same email β†’ same user_id)<br>
βœ… Production Safeguards (ENV-based SKIP_AUTH protection)
</div>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<h2>πŸ“š Resources</h2>
<ul>
<li><strong>GitHub:</strong> <a href="https://github.com/mashrur-rahman-fahim/fleetmind-mcp" target="_blank">mashrur-rahman-fahim/fleetmind-mcp</a></li>
<li><strong>HuggingFace Space:</strong> <a href="https://huggingface.co/spaces/MCP-1st-Birthday/fleetmind-dispatch-ai" target="_blank">MCP-1st-Birthday/fleetmind-dispatch-ai</a></li>
<li><strong>MCP Protocol Docs:</strong> <a href="https://modelcontextprotocol.io" target="_blank">modelcontextprotocol.io</a></li>
</ul>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<p style="text-align: center; color: #6b7280; font-size: 14px;">
FleetMind v1.0 - Built for MCP 1st Birthday Hackathon<br>
29 AI Tools | 2 Real-Time Resources | Enterprise-Ready
</p>
</div>
</body>
</html>
""")
@mcp.custom_route("/generate-key", methods=["GET", "POST"])
async def generate_key_page(request):
"""API Key generation page"""
if request.method == "GET":
return HTMLResponse("""
<!DOCTYPE html>
<html>
<head>
<title>Generate FleetMind API Key</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
.container { background: #1e293b; padding: 40px; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.4); }
h1 { color: #f1f5f9; }
input { width: 100%; padding: 12px; margin: 10px 0; border-radius: 6px; border: 1px solid #334155; background: #0f172a; color: #e2e8f0; font-size: 16px; }
button { background: #3b82f6; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 16px; cursor: pointer; width: 100%; }
button:hover { background: #2563eb; }
button:disabled { background: #64748b; cursor: not-allowed; opacity: 0.6; }
.info { background: #1e3a5f; padding: 15px; border-radius: 6px; margin: 15px 0; border-left: 4px solid #3b82f6; }
</style>
<script>
function handleGenerateKey(event) {
const button = event.target.querySelector('button');
button.textContent = 'Generating...';
button.disabled = true;
}
</script>
</head>
<body>
<div class="container">
<h1>πŸ”‘ Generate API Key</h1>
<p>Create your FleetMind MCP Server API key</p>
<div class="info">
<strong>πŸ“‹ What you'll need:</strong><br>
β€’ Your email address (used to generate your unique user_id)<br>
β€’ Your name (optional)<br>
<br>
<strong>πŸ” What you'll get:</strong><br>
β€’ API Key: <code>fm_xxxxx...</code> (show once, copy immediately!)<br>
β€’ User ID: <code>user_xxxxx</code> (deterministic from your email)<br>
β€’ All your data (orders/drivers/assignments) will be isolated by this user_id
</div>
<form method="POST" onsubmit="handleGenerateKey(event)">
<input type="email" name="email" placeholder="Your email address" required>
<input type="text" name="name" placeholder="Your name (optional)">
<button type="submit">Generate API Key</button>
</form>
<p style="text-align: center; margin-top: 20px;">
<a href="/" style="color: #60a5fa; text-decoration: none;">← Back to Home</a>
</p>
</div>
</body>
</html>
""")
# POST - Generate API key
else:
try:
form_data = await request.form()
email = form_data.get("email")
name = form_data.get("name") or None
if not email:
return HTMLResponse("<h1>Error: Email is required</h1>", status_code=400)
result = db_generate_api_key(email, name)
if not result['success']:
return HTMLResponse(f"<h1>Error: {result['error']}</h1>", status_code=400)
# Success - show the API key (one time only!)
return HTMLResponse(f"""
<!DOCTYPE html>
<html>
<head>
<title>Your FleetMind API Key</title>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }}
.container {{ background: #1e293b; padding: 40px; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.4); }}
h1 {{ color: #f1f5f9; }}
.success {{ background: #10b981; padding: 15px; border-radius: 6px; margin: 15px 0; }}
.warning {{ background: #ef4444; padding: 15px; border-radius: 6px; margin: 15px 0; }}
code {{ background: #334155; color: #60a5fa; padding: 3px 8px; border-radius: 4px; font-family: 'Courier New', monospace; display: block; margin: 10px 0; word-wrap: break-word; font-size: 14px; }}
pre {{ background: #0f172a; color: #f1f5f9; padding: 20px; border-radius: 8px; overflow-x: auto; border: 1px solid #334155; }}
button {{ background: #3b82f6; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 16px; cursor: pointer; margin: 10px 5px; }}
button:hover {{ background: #2563eb; }}
</style>
<script>
function copyKey() {{
navigator.clipboard.writeText('{result["api_key"]}');
alert('API key copied to clipboard!');
}}
</script>
</head>
<body>
<div class="container">
<h1>βœ… API Key Generated!</h1>
<div class="success">
<strong>Your API key has been created successfully</strong>
</div>
<p><strong>πŸ“§ Email:</strong> {result["email"]}</p>
<p><strong>πŸ‘€ Name:</strong> {result["name"]}</p>
<p><strong>πŸ†” User ID:</strong> <code>{result["user_id"]}</code></p>
<div class="warning">
<strong>⚠️ SAVE THIS KEY NOW - IT WON'T BE SHOWN AGAIN!</strong>
</div>
<h2>πŸ”‘ Your API Key:</h2>
<code>{result["api_key"]}</code>
<button onclick="copyKey()">πŸ“‹ Copy Key</button>
<h2>πŸ‘₯ Multi-Tenant Isolation</h2>
<p style="background: #1e3a5f; padding: 15px; border-radius: 6px; margin: 10px 0; border-left: 4px solid #3b82f6;">
<strong>Your user_id (<code>{result["user_id"]}</code>) ensures complete data isolation:</strong><br>
βœ… You will only see your own orders, drivers, and assignments<br>
βœ… All database operations automatically filter by your user_id<br>
βœ… Your user_id is deterministic - same email always gets same ID<br>
βœ… Even if you regenerate your API key, your user_id stays the same
</p>
<h2>πŸ“‹ Claude Desktop Setup:</h2>
<p>Add this to your <code>claude_desktop_config.json</code>:</p>
<pre>{{
"mcpServers": {{
"fleetmind": {{
"command": "npx",
"args": [
"mcp-remote",
"https://mcp-1st-birthday-fleetmind-dispatch-ai.hf.space/sse?api_key={result["api_key"]}"
]
}}
}}
}}</pre>
<p style="background: #1e3a5f; padding: 10px; border-radius: 6px; margin: 10px 0; border-left: 4px solid #3b82f6;">
πŸ’‘ <strong>Important:</strong> The API key is included in the URL as a query parameter (<code>?api_key=...</code>)
</p>
<h2>πŸš€ Next Steps:</h2>
<ol>
<li>Copy your API key (click the button above)</li>
<li>Add it to your Claude Desktop config</li>
<li>Restart Claude Desktop</li>
<li>Start using FleetMind tools!</li>
</ol>
<h2>πŸ” How Authentication Works:</h2>
<div style="background: #134e4a; padding: 15px; border-radius: 6px; margin: 10px 0; border-left: 4px solid #10b981;">
<strong>Authentication Flow:</strong><br><br>
1️⃣ <strong>Connection:</strong> Claude Desktop connects to SSE endpoint with your API key in URL<br>
2️⃣ <strong>Validation:</strong> Server hashes your key (SHA-256) and looks it up in database<br>
3️⃣ <strong>User Extraction:</strong> Server retrieves your user_id: <code>{result["user_id"]}</code><br>
4️⃣ <strong>Data Isolation:</strong> All tool calls automatically filter by your user_id<br>
5️⃣ <strong>Security:</strong> You can only access data associated with your user_id<br>
</div>
<p style="text-align: center; margin-top: 30px;">
<a href="/" style="color: #60a5fa; text-decoration: none;">← Back to Home</a>
</p>
</div>
</body>
</html>
""")
except Exception as e:
return HTMLResponse(f"<h1>Error: {str(e)}</h1>", status_code=500)
logger.info("[OK] Landing page added at / route")
logger.info("[OK] API key generation page added at /generate-key")
logger.info("[OK] MCP SSE endpoint available at /sse")
logger.info("[OK] Authentication handled by proxy.py (port 7860)")
# Run MCP server with SSE transport
# No middleware needed - proxy.py handles API key capture and injection
mcp.run(
transport="sse",
host=HF_SPACE_HOST,
port=HF_SPACE_PORT
)
except Exception as e:
logger.error(f"Failed to start server: {e}")
logger.error("Check that:")
logger.error(" 1. Database connection is configured (DB_HOST, DB_USER, etc.)")
logger.error(" 2. Google Maps API key is set (GOOGLE_MAPS_API_KEY)")
logger.error(" 3. Port 7860 is available")
raise