Add /health endpoint for Docker healthchecks

- Create wrapper Starlette app with health route + FastMCP mount
- Pass FastMCP lifespan to parent app for proper session management
- Health returns JSON with status, version, timestamp, transport
- Use uvicorn directly for better ASGI integration
This commit is contained in:
Ryan Malloy 2026-01-11 15:55:35 -07:00
parent 322ed78427
commit 2e8517c62d

View File

@ -12,6 +12,7 @@ Architecture uses official FastMCP MCPMixin pattern for clean separation of conc
import os
import tempfile
from datetime import datetime, timezone
from fastmcp import FastMCP
from fastmcp.prompts import Prompt
@ -568,6 +569,11 @@ def main():
if transport == "streamable-http":
# HTTP transport for hosted/Docker mode
import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route, Mount
host = os.environ.get("MCP_HOST", "0.0.0.0")
port = int(os.environ.get("MCP_PORT", "8000"))
@ -577,17 +583,37 @@ def main():
except Exception:
pkg_version = "0.1.0"
# Health check endpoint
async def health_check(request):
return JSONResponse({
"status": "healthy",
"service": "mcwaddams",
"version": pkg_version,
"timestamp": datetime.now(timezone.utc).isoformat(),
"transport": "streamable-http",
})
# Get FastMCP's HTTP app
mcp_app = app.http_app()
# Create wrapper app with health endpoint + MCP routes
# IMPORTANT: Must pass mcp_app.lifespan to initialize task groups
wrapper_app = Starlette(
routes=[
Route("/health", health_check, methods=["GET"]),
Mount("/", app=mcp_app), # Mount MCP at root (serves /mcp)
],
lifespan=mcp_app.lifespan, # Required for FastMCP session management
)
print(f"🖨️ mcwaddams v{pkg_version}")
print(f"📋 MCP Office Tools - Document Extraction Server")
print(f"🌐 Starting streamable-http transport on {host}:{port}")
print(f" Endpoint: http://{host}:{port}/mcp")
print(f" Health: http://{host}:{port}/health")
print()
app.run(
transport="streamable-http",
host=host,
port=port,
)
uvicorn.run(wrapper_app, host=host, port=port, log_level="info")
else:
# Default stdio transport for local CLI usage
# CRITICAL: show_banner=False is required for stdio transport!