fix(mcp): Chain MCP lifespan with FastAPI app lifespan (#81)

The MCP server's lifespan was not being properly chained with the
FastAPI app's lifespan, causing the MCP server to not start/stop
correctly when mounted as a sub-application.

Changes:
- Create MCP app before FastAPI app to access its lifespan
- Chain MCP lifespan context with FastAPI's lifespan context
- Ensures MCP server lifecycle is properly managed

This fix is required for the MCP server to function correctly when
used with Claude Code and other MCP clients.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Bjorn Schliebitz 2026-01-02 02:33:58 +11:00 committed by GitHub
parent 54e2df0baf
commit 6b78f7d949
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -5,6 +5,7 @@ Provides both HTTP REST API and MCP (Model Context Protocol) server.
"""
import logging
from contextlib import asynccontextmanager
from typing import Optional
from fastapi import FastAPI
@ -45,6 +46,17 @@ def create_app(
# Both HTTP and MCP
app = create_app(memory, mcp_api_enabled=True)
"""
mcp_app = None
# Create MCP app first if enabled (we need its lifespan for chaining)
if mcp_api_enabled:
try:
from .mcp import create_mcp_app
mcp_app = create_mcp_app(memory=memory)
except ImportError as e:
logger.error(f"MCP server requested but dependencies not available: {e}")
logger.error("Install with: pip install hindsight-api[mcp]")
raise
# Import and create HTTP API if enabled
if http_api_enabled:
@ -57,20 +69,31 @@ def create_app(
app = FastAPI(title="Hindsight API", version="0.0.7")
logger.info("HTTP REST API disabled")
# Mount MCP server if enabled
if mcp_api_enabled:
try:
from .mcp import create_mcp_app
# Mount MCP server and chain its lifespan if enabled
if mcp_app is not None:
# Get the MCP app's underlying Starlette app for lifespan access
mcp_starlette_app = mcp_app.mcp_app
# Create MCP app with dynamic bank_id support
# Supports: /mcp/{bank_id}/sse (bank-specific SSE endpoint)
mcp_app = create_mcp_app(memory=memory)
app.mount(mcp_mount_path, mcp_app)
logger.info(f"MCP server enabled at {mcp_mount_path}/{{bank_id}}/sse")
except ImportError as e:
logger.error(f"MCP server requested but dependencies not available: {e}")
logger.error("Install with: pip install hindsight-api[mcp]")
raise
# Store the original lifespan
original_lifespan = app.router.lifespan_context
@asynccontextmanager
async def chained_lifespan(app_instance: FastAPI):
"""Chain the MCP lifespan with the main app lifespan."""
# Start MCP lifespan first
async with mcp_starlette_app.router.lifespan_context(mcp_starlette_app):
logger.info("MCP lifespan started")
# Then start the original app lifespan
async with original_lifespan(app_instance):
yield
logger.info("MCP lifespan stopped")
# Replace the app's lifespan with the chained version
app.router.lifespan_context = chained_lifespan
# Mount the MCP middleware
app.mount(mcp_mount_path, mcp_app)
logger.info(f"MCP server enabled at {mcp_mount_path}/{{bank_id}}/mcp")
return app