feat: optional bearer-token auth for streamable-http transport

Pure-ASGI middleware requiring Authorization: Bearer <MCP_API_KEY> on every HTTP
request when MCP_API_KEY is set; open/stdio behaviour preserved when unset.
Closes unauthenticated-MCP exposure (HOME-119 P1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-06-21 20:38:17 +00:00
parent 6a1b671d79
commit 35c982ef62

View file

@ -224,9 +224,48 @@ def delete_paste(paste_id: str) -> dict:
# Entry point # Entry point
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
MCP_API_KEY = os.environ.get("MCP_API_KEY", "").strip()
class _BearerAuthMiddleware:
"""Pure-ASGI middleware rejecting HTTP requests without a valid bearer token.
Enforced only when MCP_API_KEY is set; stdio transport is unaffected.
"""
def __init__(self, app, token: str) -> None:
self.app = app
self._expected = f"Bearer {token}".encode()
async def __call__(self, scope, receive, send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
headers = dict(scope.get("headers") or [])
if headers.get(b"authorization") != self._expected:
from starlette.responses import JSONResponse
await JSONResponse(
{"jsonrpc": "2.0",
"error": {"code": -32001, "message": "Unauthorized"},
"id": None},
status_code=401,
headers={"WWW-Authenticate": "Bearer"},
)(scope, receive, send)
return
await self.app(scope, receive, send)
def main() -> None: def main() -> None:
if MCP_TRANSPORT not in {"stdio", "sse", "streamable-http"}: if MCP_TRANSPORT not in {"stdio", "sse", "streamable-http"}:
raise ValueError("PASTEBIN_MCP_TRANSPORT must be stdio, sse, or streamable-http.") raise ValueError("PASTEBIN_MCP_TRANSPORT must be stdio, sse, or streamable-http.")
if MCP_TRANSPORT == "streamable-http" and MCP_API_KEY:
import uvicorn
app = mcp.streamable_http_app()
app.add_middleware(_BearerAuthMiddleware, token=MCP_API_KEY)
uvicorn.run(app, host=MCP_HOST, port=MCP_PORT)
else:
mcp.run(transport=MCP_TRANSPORT) mcp.run(transport=MCP_TRANSPORT)