""" 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, )