File size: 1,586 Bytes
4ccde7a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94961c3
4ccde7a
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
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