mirror of
https://github.com/Av0idD4rk/firegex.git
synced 2026-10-23 14:31:00 +00:00
Expose 28 tools with shared REST operations, role-scoped bearer authentication, revisions, durable retries, snapshots, jobs and timed rollback. Add Docker verification harnesses and setup/workflow documentation in docs/mcp.md and inst.md. Validated with 330 unit tests, two consecutive 25-case MCP/import runs, and restart recovery; the full upstream regression run remains incomplete.
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
from contextvars import ContextVar
|
|
from starlette.responses import JSONResponse
|
|
|
|
principal = ContextVar("firegex_mcp_principal", default=None)
|
|
ROLES = {"observer": 0, "operator": 1, "admin": 2}
|
|
|
|
|
|
class MCPError(Exception):
|
|
def __init__(self, code, message, **details):
|
|
super().__init__(message)
|
|
self.data = {"code": code, "message": message, **details}
|
|
|
|
|
|
def require(role="observer"):
|
|
user = principal.get()
|
|
if not user or ROLES.get(user["role"], -1) < ROLES[role]:
|
|
raise MCPError("FORBIDDEN", f"This operation requires the {role} role")
|
|
return user
|
|
|
|
|
|
class BearerMiddleware:
|
|
"""Authentication independent of the UI's password/unsafe-disable-auth setting."""
|
|
def __init__(self, app, store):
|
|
self.app, self.store = app, store
|
|
|
|
async def __call__(self, scope, receive, send):
|
|
if scope["type"] != "http":
|
|
await self.app(scope, receive, send)
|
|
return
|
|
headers = dict(scope.get("headers", []))
|
|
raw = headers.get(b"authorization", b"").decode("latin1")
|
|
user = self.store.token_verify(raw[7:]) if raw.lower().startswith("bearer ") else None
|
|
if user is None:
|
|
await JSONResponse({"error": "Invalid, expired or revoked MCP token"}, status_code=401,
|
|
headers={"WWW-Authenticate": 'Bearer realm="firegex-mcp"'})(scope, receive, send)
|
|
return
|
|
marker = principal.set(user)
|
|
try:
|
|
await self.app(scope, receive, send)
|
|
finally:
|
|
principal.reset(marker)
|