From 679fda849103d2ea88cc8f562cdac8817b1761a1 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Mon, 2 Mar 2026 19:11:48 -0600 Subject: [PATCH] fix: initialize MCP session_manager in lifespan and fix mount path StreamableHTTPSessionManager requires its run() context manager to be active before handling requests. Without it, every MCP call returned RuntimeError: Task group is not initialized. Also changed mount from /mcp to / (at end of route list) so the MCP endpoint is accessible at /mcp rather than /mcp/mcp, matching the sub-app's internal route structure. Co-Authored-By: Claude Sonnet 4.6 --- src/main.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main.py b/src/main.py index dcb67ed..99346d8 100644 --- a/src/main.py +++ b/src/main.py @@ -77,6 +77,13 @@ async def stats() -> dict: return s.model_dump(mode="json") +# Initialize the MCP sub-app early so session_manager is available for lifespan. +# streamable_http_app() creates a sub-app with a route at /mcp internally. +# Mounted at "/" (last in the route list), FastAPI routes take priority and +# requests to /mcp fall through to the sub-app — keeping the public URL at /mcp. +_mcp_app = mcp.streamable_http_app() + + # --------------------------------------------------------------------------- # FastAPI app # --------------------------------------------------------------------------- @@ -84,7 +91,8 @@ async def stats() -> dict: async def lifespan(app: FastAPI): logger.info("OpenBrain starting up") await db.get_pool() # Warm the connection pool - yield + async with mcp.session_manager.run(): + yield await db.close_pool() logger.info("OpenBrain shut down") @@ -96,9 +104,6 @@ app = FastAPI( lifespan=lifespan, ) -# Mount MCP server at /mcp (HTTP streamable transport) -app.mount("/mcp", mcp.streamable_http_app()) - # --------------------------------------------------------------------------- # REST endpoints (for direct API access and health checks) @@ -126,3 +131,8 @@ async def api_recent(limit: int = 20, _: str = Depends(require_api_key)) -> list @app.get("/v1/stats", response_model=Stats) async def api_stats(_: str = Depends(require_api_key)) -> Stats: return await brain.stats() + + +# Mount MCP sub-app at "/" last — FastAPI routes above take priority, +# /mcp falls through to the sub-app's internal /mcp route. +app.mount("/", _mcp_app)