Spaces:
Running
Running
| # ============================================================================= | |
| # root/app/app.py | |
| # Universal MCP Hub (Sandboxed) - based on PyFundaments Architecture | |
| # Copyright 2026 - Volkan KΓΌcΓΌkbudak | |
| # Apache License V. 2 + ESOL 1.1 | |
| # Repo: https://github.com/VolkanSah/Universal-MCP-Hub-sandboxed | |
| # ============================================================================= | |
| # ARCHITECTURE NOTE: | |
| # This file is the Orchestrator of the sandboxed app/* layer. | |
| # It is ONLY started by main.py (the "Guardian"). | |
| # All fundament services are injected via the `fundaments` dictionary. | |
| # Direct execution is blocked by design. | |
| # | |
| # SANDBOX RULES: | |
| # - fundaments dict is ONLY unpacked inside start_application() | |
| # - fundaments are NEVER stored globally or passed to other app/* modules | |
| # - app/* modules read their own config from app/.pyfun | |
| # - app/* internal state/IPC uses app/db_sync.py (SQLite) β NOT postgresql.py | |
| # - Secrets stay in .env β Guardian reads them β never touched by app/* | |
| # ============================================================================= | |
| from quart import Quart, request, jsonify # async Flask β ASGI compatible | |
| import logging | |
| from hypercorn.asyncio import serve # ASGI server β async native, replaces waitress | |
| from hypercorn.config import Config # hypercorn config | |
| import threading # for future tools that need own threads | |
| import requests # sync HTTP for future tool workers | |
| import time | |
| from datetime import datetime | |
| import asyncio | |
| from typing import Dict, Any, Optional | |
| # ============================================================================= | |
| # Import app/* modules β MINIMAL BUILD | |
| # Each module reads its own config from app/.pyfun independently. | |
| # NO fundaments passed into these modules! | |
| # ============================================================================= | |
| from . import mcp # MCP transport layer (SSE via Quart route) | |
| from . import config as app_config # app/.pyfun parser β used only in app/* | |
| from . import providers # API provider registry β reads app/.pyfun | |
| from . import models # Model config + token/rate limits β reads app/.pyfun | |
| from . import tools # MCP tool definitions + provider mapping β reads app/.pyfun | |
| from . import db_sync # Internal SQLite IPC β app/* state & communication | |
| # # db_sync β postgresql.py! Cloud DB is Guardian-only. | |
| # Future modules (will uncomment when ready): | |
| # from . import discord_api # Discord bot integration | |
| # from . import hf_hooks # HuggingFace Space hooks | |
| # from . import git_hooks # GitHub/GitLab webhook handler | |
| # from . import web_api # Generic REST API handler | |
| # ============================================================================= | |
| # Loggers β one per module for clean log filtering | |
| # ============================================================================= | |
| logger = logging.getLogger('application') | |
| logger_mcp = logging.getLogger('mcp') | |
| logger_config = logging.getLogger('config') | |
| logger_tools = logging.getLogger('tools') | |
| logger_providers = logging.getLogger('providers') | |
| logger_models = logging.getLogger('models') | |
| logger_db_sync = logging.getLogger('db_sync') | |
| # ============================================================================= | |
| # Quart app instance OLD | |
| # ============================================================================= | |
| # app = Quart(__name__) | |
| #START_TIME = datetime.utcnow() | |
| # ============================================================================= | |
| # Quart Routes | |
| # ============================================================================= | |
| #@app.route("/", methods=["GET"]) | |
| #async def health_check(): | |
| # """ | |
| # Health check endpoint. | |
| # Used by HuggingFace Spaces and monitoring systems to verify the app is running. | |
| # """ | |
| # uptime = datetime.utcnow() - START_TIME | |
| # return jsonify({ | |
| # "status": "running", | |
| # "service": "Universal MCP Hub", | |
| # "uptime_seconds": int(uptime.total_seconds()), | |
| # }) | |
| # ============================================================================= | |
| # Quart app instance NEW | |
| # ============================================================================= | |
| app = Quart(__name__) | |
| START_TIME = datetime.utcnow() | |
| _HEALTH_HTML = """\ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta http-equiv="refresh" content="10"> | |
| <title>LLM-API-Gateway</title> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{font-family:ui-monospace,monospace;background:#0d1117;color:#c9d1d9; | |
| min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem} | |
| .card{background:#161b22;border:1px solid #30363d;border-radius:12px; | |
| padding:2rem 2.5rem;max-width:480px;width:100%} | |
| .header{display:flex;align-items:center;gap:12px;margin-bottom:1.5rem; | |
| padding-bottom:1.5rem;border-bottom:1px solid #21262d} | |
| .dot{width:10px;height:10px;border-radius:50%;background:#3fb950;flex-shrink:0} | |
| h1{font-size:1.1rem;font-weight:600;color:#f0f6fc} | |
| .badge{margin-left:auto;font-size:.7rem;background:#1f6feb30;color:#58a6ff; | |
| border:1px solid #1f6feb;border-radius:20px;padding:2px 10px} | |
| .stats{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:1.5rem} | |
| .stat{background:#0d1117;border:1px solid #21262d;border-radius:8px;padding:.75rem 1rem} | |
| .stat-label{font-size:.7rem;color:#8b949e;margin-bottom:4px} | |
| .stat-value{font-size:1rem;color:#f0f6fc;font-weight:500} | |
| .green{color:#3fb950} | |
| .links{display:flex;flex-direction:column;gap:8px} | |
| .link{display:flex;align-items:center;gap:10px;padding:.6rem .9rem; | |
| background:#0d1117;border:1px solid #21262d;border-radius:8px; | |
| color:#8b949e;text-decoration:none;font-size:.82rem; | |
| transition:border-color .15s,color .15s} | |
| .link:hover{border-color:#58a6ff;color:#58a6ff} | |
| .arrow{font-size:.75rem;opacity:.4} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <div class="header"> | |
| <div class="dot"></div> | |
| <h1>LLM-API-Gateway</h1> | |
| <span class="badge">running</span> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat"> | |
| <div class="stat-label">status</div> | |
| <div class="stat-value green">operational</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">uptime</div> | |
| <div class="stat-value">__UPTIME__</div> | |
| </div> | |
| </div> | |
| <div class="links"> | |
| <a class="link" href="https://huggingface.co/spaces/codey-lab/Multi-LLM-API-Gateway" target="_blank"> | |
| <span>HuggingFace Space</span><span class="arrow">β</span> | |
| </a> | |
| <a class="link" href="https://github.com/VolkanSah/Multi-LLM-API-Gateway" target="_blank"> | |
| <span>GitHub Repository</span><span class="arrow">β</span> | |
| </a> | |
| </div> | |
| </div> | |
| </body> | |
| </html>""" | |
| # ============================================================================= | |
| # Quart Routes | |
| # ============================================================================= | |
| async def health_check(): | |
| """ | |
| Health check endpoint. | |
| Used by HuggingFace Spaces and monitoring systems to verify the app is running. | |
| """ | |
| uptime = datetime.utcnow() - START_TIME | |
| uptime_s = int(uptime.total_seconds()) | |
| if "text/html" not in request.headers.get("Accept", ""): | |
| return jsonify({ | |
| "status": "running", | |
| "service": "Universal MCP Hub", | |
| "uptime_seconds": uptime_s, | |
| }) | |
| # Uptime formatieren | |
| h = uptime_s // 3600 | |
| m = (uptime_s % 3600) // 60 | |
| s = uptime_s % 60 | |
| if h > 0: | |
| uptime_str = f"{h}h {m}m {s}s" | |
| elif m > 0: | |
| uptime_str = f"{m}m {s}s" | |
| else: | |
| uptime_str = f"{s}s" | |
| html = _HEALTH_HTML.replace("__UPTIME__", uptime_str) | |
| return html, 200, {"Content-Type": "text/html"} | |
| # end health_check | |
| async def api_endpoint(): | |
| try: | |
| data = await request.get_json() | |
| tool_name = data.get("tool") | |
| params = data.get("params", {}) | |
| # System tools β handle directly, no prompt needed! | |
| if tool_name == "list_active_tools": | |
| return jsonify({"result": { | |
| "active_tools": tools.list_all(), | |
| "active_llm_providers": providers.list_active_llm(), | |
| "active_search_providers": providers.list_active_search(), | |
| "available_models": models.list_all(), | |
| }}) | |
| if tool_name == "health_check": | |
| return jsonify({"result": {"status": "ok"}}) | |
| # db_query β handled by db_sync directly, not tools.run() | |
| if tool_name == "db_query": | |
| sql = params.get("sql", "") | |
| result = await db_sync.query(sql) | |
| return jsonify({"result": result}) | |
| # rename 'provider' β 'provider_name' for tools.run() | |
| if "provider" in params: | |
| params["provider_name"] = params.pop("provider") | |
| result = await tools.run(tool_name, **params) | |
| return jsonify({"result": result}) | |
| except Exception as e: | |
| logger.error(f"API error: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| async def crypto_endpoint(): | |
| """ | |
| Encrypted API endpoint. | |
| Encryption handled by app/* layer β no direct fundaments access here. | |
| """ | |
| # TODO: implement via app/* encryption wrapper | |
| data = await request.get_json() | |
| return jsonify({"status": "not_implemented"}), 501 | |
| async def mcp_endpoint(): | |
| """ | |
| MCP SSE Transport endpoint β routed through Quart/hypercorn. | |
| All MCP traffic passes through here β enables interception, logging, | |
| auth checks, rate limiting, payload transformation before reaching MCP. | |
| """ | |
| return await mcp.handle_request(request) | |
| # Future routes (uncomment when ready): | |
| # @app.route("/discord", methods=["POST"]) | |
| # async def discord_interactions(): | |
| # """Discord interactions endpoint β signature verification via discord_api module.""" | |
| # pass | |
| # @app.route("/webhook/hf", methods=["POST"]) | |
| # async def hf_webhook(): | |
| # """HuggingFace Space event hooks.""" | |
| # pass | |
| # @app.route("/webhook/git", methods=["POST"]) | |
| # async def git_webhook(): | |
| # """GitHub / GitLab webhook handler.""" | |
| # pass | |
| # ============================================================================= | |
| # Main entry point β called exclusively by Guardian (main.py) | |
| # ============================================================================= | |
| async def start_application(fundaments: Dict[str, Any]) -> None: | |
| """ | |
| Main entry point for the sandboxed app layer. | |
| Called exclusively by main.py after all fundament services are initialized. | |
| Args: | |
| fundaments: Dictionary of initialized services from Guardian (main.py). | |
| Services are unpacked here and NEVER stored globally or | |
| passed into other app/* modules. | |
| """ | |
| logger.info("Application starting...") | |
| # ========================================================================= | |
| # Unpack fundaments β ONLY here, NEVER elsewhere in app/* | |
| # These are the 6 fundament services from fundaments/* | |
| # ========================================================================= | |
| config_service = fundaments["config"] # fundaments/config_handler.py | |
| db_service = fundaments["db"] # fundaments/postgresql.py β None if not configured | |
| encryption_service = fundaments["encryption"] # fundaments/encryption.py β None if keys not set | |
| access_control_service = fundaments["access_control"] # fundaments/access_control.py β None if no DB | |
| user_handler_service = fundaments["user_handler"] # fundaments/user_handler.py β None if no DB | |
| security_service = fundaments["security"] # fundaments/security.py β None if deps missing | |
| # --- Log active fundament services --- | |
| if encryption_service: | |
| logger.info("Encryption service active.") | |
| if user_handler_service and security_service: | |
| logger.info("Auth services active (user_handler + security).") | |
| if access_control_service and security_service: | |
| logger.info("Access control active.") | |
| if db_service and not user_handler_service: | |
| logger.info("Database-only mode active (e.g. ML pipeline).") | |
| if not db_service: | |
| logger.info("Database-free mode active (e.g. Discord bot, API client).") | |
| # ========================================================================= | |
| # Initialize app/* internal services β MINIMAL BUILD | |
| # Uncomment each line when the module is ready! | |
| # ========================================================================= | |
| # await db_sync.initialize() # SQLite IPC store for app/* β unrelated to postgresql.py | |
| # await providers.initialize() # reads app/.pyfun [LLM_PROVIDERS] [SEARCH_PROVIDERS] # in mcp_init | |
| # await models.initialize() # reads app/.pyfun [MODELS] # in mcp_init | |
| # await tools.initialize() # reads app/.pyfun [TOOLS] | |
| # --- Initialize MCP (registers tools, prepares SSE handler) --- | |
| # db_sync only if cloud_DB used to! | |
| await db_sync.initialize() | |
| await mcp.initialize() | |
| # --- Read PORT from app/.pyfun [HUB] --- | |
| port = int(app_config.get_hub().get("HUB_PORT", "7860")) | |
| # --- Configure hypercorn --- | |
| config = Config() | |
| config.bind = [f"0.0.0.0:{port}"] | |
| logger.info(f"Starting hypercorn on port {port}...") | |
| logger.info("All services running.") | |
| # --- Run hypercorn β blocks until shutdown --- | |
| await serve(app, config) | |
| # ============================================================================= | |
| # Direct execution guard | |
| # ============================================================================= | |
| if __name__ == '__main__': | |
| print("WARNING: Running app.py directly. Fundament modules might not be correctly initialized.") | |
| print("Please run 'python main.py' instead for proper initialization.") | |
| test_fundaments = { | |
| "config": None, | |
| "db": None, | |
| "encryption": None, | |
| "access_control": None, | |
| "user_handler": None, | |
| "security": None, | |
| } | |
| asyncio.run(start_application(test_fundaments)) | |