This commit is contained in:
2026-03-27 03:58:57 +08:00
commit 86eba27a24
38 changed files with 4074 additions and 0 deletions

102
main.py Normal file
View File

@@ -0,0 +1,102 @@
"""
main.py
Application entrypoint.
Starts the FastAPI HTTP server (web UI + REST API) and mounts the FastMCP
streamable-HTTP endpoint under /mcp.
"""
from __future__ import annotations
import logging
from contextlib import asynccontextmanager
import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from app.api import routes_config, routes_instructions, routes_status
from app.config import settings
from app.database import init_db
from app.logging_setup import configure_logging
from app.mcp_server import mcp, mcp_asgi_app
from app.services import instruction_service
# ---------------------------------------------------------------------------
# Logging must be configured before any module emits log messages
# ---------------------------------------------------------------------------
configure_logging(settings)
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Build the FastAPI application
# ---------------------------------------------------------------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
logger.info("local-mcp starting up initialising database …")
init_db(settings.db_path)
await instruction_service.init_wakeup()
logger.info(
"local-mcp ready http://%s:%d | MCP http://%s:%d/mcp (stateless=%s)",
settings.host, settings.http_port,
settings.host, settings.http_port,
settings.mcp_stateless,
)
# Run the MCP session manager for the duration of the app lifetime
async with mcp.session_manager.run():
yield
# Shutdown
logger.info("local-mcp shutting down")
def create_app() -> FastAPI:
app = FastAPI(
title="local-mcp",
description="Localhost MCP server with instruction queue management UI",
version="1.0.0",
lifespan=lifespan,
)
# --- Global exception handler ---
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.exception("Unhandled exception for %s %s", request.method, request.url)
return JSONResponse(
status_code=500,
content={"detail": "Internal server error", "error": str(exc)},
)
# --- API routers ---
app.include_router(routes_status.router)
app.include_router(routes_instructions.router)
app.include_router(routes_config.router)
# --- MCP streamable-HTTP transport mounted at /mcp ---
# mcp_asgi_app was pre-built in mcp_server.py; session manager is
# started explicitly in the lifespan above.
app.mount("/mcp", mcp_asgi_app)
# --- Static files for the web UI ---
app.mount("/static", StaticFiles(directory="static", html=False), name="static")
@app.get("/", include_in_schema=False)
def serve_index():
return FileResponse("static/index.html")
return app
app = create_app()
if __name__ == "__main__":
uvicorn.run(
"main:app",
host=settings.host,
port=settings.http_port,
log_level=settings.log_level.lower(),
reload=False,
)