Spaces:
Running
Running
File size: 14,438 Bytes
85a0eea 5d8b48a 85a0eea 5d8b48a 85a0eea 1163bbb 5d8b48a 3edbca5 5d8b48a 1163bbb 5d8b48a 1163bbb 3edbca5 d25c7d1 1163bbb 5d8b48a 1163bbb d25c7d1 1163bbb 3edbca5 1163bbb 8c8802e 5d8b48a 1163bbb 8c8802e 5d8b48a 1163bbb 5d8b48a 1163bbb 3edbca5 5d8b48a 85a0eea 3edbca5 85a0eea aec9b35 1e2a207 1bb47d3 c9d4ca5 1e2a207 5a09c18 aec9b35 1bb47d3 aec9b35 85a0eea 4c7af52 85a0eea | 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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | # =============================================================================
# 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
# =============================================================================
@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
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
@app.route("/api", methods=["POST"])
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
@app.route("/crypto", methods=["POST"])
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
@app.route("/mcp", methods=["GET", "POST"])
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))
|