from mcp.server.fastmcp import FastMCP from starlette.middleware.cors import CORSMiddleware from starlette.requests import Request from app.core.config import settings from app.core.lifespan import app_lifespan from app.core.logger import logger from app.tools import register_markdown_tools from app.utils.response import create_response def _build_mcp_server() -> FastMCP: """Build the FastMCP instance and register every tool.""" mcp = FastMCP(settings.APP_NAME, lifespan=app_lifespan) register_markdown_tools(mcp) return mcp async def _health(_: Request): """Simple liveness probe.""" return create_response( status_value=True, message="Service is healthy", data={"app": settings.APP_NAME}, ) def create_app(): """Build the ASGI application. Mounts CORS and the health route directly on the MCP Starlette app to avoid nesting two Starlette instances (which produces a double http.response.start crash with uvicorn). """ logger.info("Building application: {}", settings.APP_NAME) mcp = _build_mcp_server() # sse_app() returns a Starlette instance — use it as the single ASGI app. app = mcp.sse_app() # Inject health route before the MCP catch-all mount. app.add_route("/health", _health, methods=["GET"]) # Add CORS once at the outermost middleware layer. app = CORSMiddleware( app, allow_origins=settings.CORS_ALLOW_ORIGINS, allow_methods=settings.CORS_ALLOW_METHODS, allow_headers=settings.CORS_ALLOW_HEADERS, ) return app