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:
parent
6a1b671d79
commit
35c982ef62
1 changed files with 40 additions and 1 deletions
|
|
@ -224,9 +224,48 @@ def delete_paste(paste_id: str) -> dict:
|
|||
# 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:
|
||||
if MCP_TRANSPORT not in {"stdio", "sse", "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)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue