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