feat: add /health endpoint and switch to fastmcp 2.0
All checks were successful
Build and Push Komodo MCP Docker Image / build (push) Successful in 18s

- Switch from mcp package to fastmcp>=2.0 for Streamable HTTP support
  - Fixes 405 Method Not Allowed for MCP clients like Gemini CLI
  - MCP endpoint now at /mcp (POST) instead of /sse (GET)
  - Removes MCP_ALLOWED_HOSTS (handled at reverse proxy level)

- Add /health endpoint for Docker health checks
  - Returns {"status": "ok"} (200) or {"status": "degraded"} (503)
  - Enables health checks without transport security issues

- Add curl to Docker image for health checks
- Add healthcheck config to docker-compose files
- Add test-health and test-mcp Makefile targets
- Update documentation
This commit is contained in:
Ben
2025-12-20 22:30:56 +00:00
parent 0ecf6880a1
commit 98dc6b7f50
9 changed files with 80 additions and 28 deletions

View File

@@ -1,8 +1,10 @@
import os
import logging
import httpx
from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route, Mount
from dotenv import load_dotenv
# Load environment variables
@@ -16,7 +18,6 @@ logger = logging.getLogger(__name__)
KOMODO_URL = os.getenv("KOMODO_URL", "").rstrip("/")
KOMODO_API_KEY = os.getenv("KOMODO_API_KEY", "")
KOMODO_API_SECRET = os.getenv("KOMODO_API_SECRET", "")
MCP_ALLOWED_HOSTS = os.getenv("MCP_ALLOWED_HOSTS", "localhost:*,127.0.0.1:*")
class KomodoClient:
@@ -75,12 +76,9 @@ def _get_client() -> tuple[KomodoClient | None, dict | None]:
# --- MCP Server ---
transport_security = TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=[h.strip() for h in MCP_ALLOWED_HOSTS.split(",")],
allowed_origins=[],
)
mcp = FastMCP("Komodo MCP", transport_security=transport_security)
# Note: Host validation is handled at the reverse proxy/ingress level
# The /health endpoint bypasses this for Docker health checks
mcp = FastMCP("Komodo MCP")
# --- API Documentation Resource ---
@@ -225,6 +223,36 @@ def komodo_api_call(
return method(request_type, params or {})
# --- Health Check Endpoint ---
# This bypasses MCP_ALLOWED_HOSTS for Docker health checks
async def health(request):
"""Health check endpoint - bypasses transport security."""
# Optionally verify Komodo connectivity
client, error = _get_client()
if error:
return JSONResponse({"status": "degraded", "error": error["error"]}, status_code=503)
return JSONResponse({"status": "ok"})
# --- ASGI Application ---
def create_app():
"""Create the ASGI application with health check and MCP routes."""
mcp_app = mcp.http_app()
# Wrapper app: /health bypasses security, everything else goes to MCP
routes = [
Route("/health", health, methods=["GET"]),
Mount("/", app=mcp_app), # MCP handles /mcp endpoint
]
return Starlette(routes=routes)
# Create the app instance for uvicorn
app = create_app()
if __name__ == "__main__":
import uvicorn
uvicorn.run(mcp.sse_app, host="0.0.0.0", port=8000)
# Run the wrapper app (includes /health and /mcp endpoints)
uvicorn.run(app, host="0.0.0.0", port=8000)