Skip to content

Commit e418e35

Browse files
fix: exempt healthz and CORS preflight from MCP auth middleware
SRE Agent connector shows perpetual 'Connecting' because: - GET /healthz returns 401 (no unauthenticated health endpoint) - OPTIONS /mcp returns 401 (CORS preflight blocked by auth) Added three middleware layers stacked in correct order: 1. Health wrapper: serves /healthz, /health, /ready without auth 2. CORS middleware: handles OPTIONS preflight with proper headers 3. Auth middleware: validates bearer token on all other requests Middleware order: health → CORS → auth → rate-limit ensures health probes and preflight pass while POST /mcp stays secured. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ae4dda7 commit e418e35

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

src/mcp_server/server.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,48 @@ async def sap_lifespan(server: FastMCP) -> AsyncIterator[SapContext]:
352352
_mcp_asgi = mcp.streamable_http_app()
353353

354354

355+
_HEALTH_PATHS = {"/healthz", "/health", "/ready"}
356+
357+
358+
def _create_health_wrapper(app):
359+
"""Add unauthenticated health-check endpoints in front of the MCP app."""
360+
361+
async def wrapper(scope, receive, send):
362+
if scope["type"] == "http" and scope.get("path", "") in _HEALTH_PATHS:
363+
response = JSONResponse({"status": "ok"})
364+
await response(scope, receive, send)
365+
return
366+
await app(scope, receive, send)
367+
368+
return wrapper
369+
370+
371+
def _create_cors_middleware(app):
372+
"""Handle CORS preflight (OPTIONS) so connectors can reach the MCP endpoint."""
373+
374+
_allowed_origin = os.environ.get("CORS_ORIGINS", "*")
375+
_allowed_headers = "Authorization, Content-Type, Accept, Mcp-Session-Id"
376+
_allowed_methods = "GET, POST, OPTIONS, DELETE"
377+
378+
async def middleware(scope, receive, send):
379+
if scope["type"] == "http" and scope.get("method", "") == "OPTIONS":
380+
response = JSONResponse(
381+
content=None,
382+
status_code=204,
383+
headers={
384+
"Access-Control-Allow-Origin": _allowed_origin,
385+
"Access-Control-Allow-Methods": _allowed_methods,
386+
"Access-Control-Allow-Headers": _allowed_headers,
387+
"Access-Control-Max-Age": "86400",
388+
},
389+
)
390+
await response(scope, receive, send)
391+
return
392+
await app(scope, receive, send)
393+
394+
return middleware
395+
396+
355397
def _create_auth_middleware(app, verifier):
356398
"""Wrap ASGI app with bearer token authentication."""
357399

@@ -376,6 +418,8 @@ async def middleware(scope, receive, send):
376418
return middleware
377419

378420

421+
_mcp_asgi = _create_health_wrapper(_mcp_asgi)
422+
_mcp_asgi = _create_cors_middleware(_mcp_asgi)
379423
if _token_verifier is not None:
380424
_mcp_asgi = _create_auth_middleware(_mcp_asgi, _token_verifier)
381425

0 commit comments

Comments
 (0)