mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-10 22:32:16 +02:00
feat(mcp): add health endpoint with public-path auth exemption
This commit is contained in:
parent
2acc1426bf
commit
2b9daead58
2 changed files with 21 additions and 8 deletions
|
|
@ -6,10 +6,13 @@ not reach the tool handler. A pure middleware binds the key in the request's own
|
||||||
task, from which the SDK's per-request handling inherits it.
|
task, from which the SDK's per-request handling inherits it.
|
||||||
|
|
||||||
Requests without a key are rejected here so no tool ever runs unauthenticated.
|
Requests without a key are rejected here so no tool ever runs unauthenticated.
|
||||||
|
Paths in ``public_paths`` (e.g. the health probe) skip the check entirely.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from starlette.datastructures import Headers
|
from starlette.datastructures import Headers
|
||||||
from starlette.responses import JSONResponse
|
from starlette.responses import JSONResponse
|
||||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||||
|
|
@ -21,11 +24,12 @@ from .identity import bind_api_key, unbind_api_key
|
||||||
class ApiKeyIdentityMiddleware:
|
class ApiKeyIdentityMiddleware:
|
||||||
"""Binds the per-request API key into the identity contextvar, or 401s."""
|
"""Binds the per-request API key into the identity contextvar, or 401s."""
|
||||||
|
|
||||||
def __init__(self, app: ASGIApp) -> None:
|
def __init__(self, app: ASGIApp, public_paths: Iterable[str] = ()) -> None:
|
||||||
self._app = app
|
self._app = app
|
||||||
|
self._public_paths = frozenset(public_paths)
|
||||||
|
|
||||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||||
if scope["type"] != "http":
|
if scope["type"] != "http" or scope["path"] in self._public_paths:
|
||||||
await self._app(scope, receive, send)
|
await self._app(scope, receive, send)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,32 @@
|
||||||
"""Assemble the streamable-http ASGI app for the remote transport.
|
"""Wrap the SDK's MCP endpoint with identity + CORS for the remote transport.
|
||||||
|
|
||||||
Wraps the SDK's MCP endpoint with the API-key identity middleware and CORS.
|
CORS is outermost so keyless browser preflight is answered before the identity
|
||||||
CORS sits outermost so browser preflight (which carries no key) is answered
|
middleware. ``/health`` is a public path, exempt from the key check.
|
||||||
before the identity middleware, and clients can read the ``Mcp-Session-Id``
|
|
||||||
header the streamable-http protocol relies on.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from starlette.requests import Request
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
from starlette.middleware.cors import CORSMiddleware
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
from starlette.types import ASGIApp
|
from starlette.types import ASGIApp
|
||||||
|
|
||||||
from ..auth.middleware import ApiKeyIdentityMiddleware
|
from ..auth.middleware import ApiKeyIdentityMiddleware
|
||||||
|
|
||||||
|
HEALTH_PATH = "/health"
|
||||||
|
|
||||||
|
|
||||||
|
async def _health(_request: Request) -> JSONResponse:
|
||||||
|
return JSONResponse({"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
def build_http_app(mcp: FastMCP) -> ASGIApp:
|
def build_http_app(mcp: FastMCP) -> ASGIApp:
|
||||||
"""Return the MCP streamable-http app wrapped with identity + CORS."""
|
"""Return the MCP streamable-http app wrapped with identity + CORS."""
|
||||||
app: ASGIApp = ApiKeyIdentityMiddleware(mcp.streamable_http_app())
|
mcp.custom_route(HEALTH_PATH, methods=["GET"])(_health)
|
||||||
|
app: ASGIApp = ApiKeyIdentityMiddleware(
|
||||||
|
mcp.streamable_http_app(), public_paths={HEALTH_PATH}
|
||||||
|
)
|
||||||
return CORSMiddleware(
|
return CORSMiddleware(
|
||||||
app,
|
app,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue