Files
local-mcp/main.py
2026-03-27 18:16:30 +08:00

113 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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.api.auth import TokenAuthMiddleware
from app.config import APP_VERSION, 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 v%s starting up initialising database …", APP_VERSION)
init_db(settings.db_path)
await instruction_service.init_wakeup()
logger.info(
"local-mcp v%s ready http://%s:%d | MCP http://%s:%d/mcp (stateless=%s)",
APP_VERSION,
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=APP_VERSION,
lifespan=lifespan,
)
# --- Token auth middleware (no-op when API_TOKEN is not set) ---
app.add_middleware(TokenAuthMiddleware, token=settings.api_token)
# --- 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)},
)
# --- Public: lets the UI know whether it needs a token (no auth required) ---
@app.get("/auth-check", include_in_schema=False)
def auth_check():
return {"auth_required": bool(settings.api_token)}
# --- 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,
)