feat(mcp): hovedhusets egen gateway paa /hovedhus/mcp

Selvstaendig under-app med egen Authentik-provider (hovedhus-mcp, gruppe
Hovedhus), egen OIDCProxy/noegle/lager og backends fra
data/hovedhus_gateway.json, aldrig fra mcp_registry.json. Ingen statiske
noegler; UserGate kraever navngiven bruger og fejler lukket.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
This commit is contained in:
agw
2026-09-29 17:27:26 +02:00
parent 52b1a2bb6c
commit 0635f4a76c
2 changed files with 135 additions and 1 deletions
+28 -1
View File
@@ -7,6 +7,7 @@ from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware
import contextlib
import time
import authentik as ak
@@ -20,11 +21,30 @@ import ytdl as yt
import mcpkeys
import mcp_server
import mcp_gateway
import mcp_hovedhus
# Portalens egen MCP (FastMCP), monteret in-process på /mcp. json_response=True
# => almindelig JSON (ingen SSE), så det passerer edge'en utunet. Bearer-gated.
mcp_app = mcp_gateway.gateway.http_app(path="/mcp", json_response=True)
app = FastAPI(title="Weircon Portal", lifespan=mcp_app.lifespan)
# Hovedhusets gateway (/hovedhus/mcp) er en selvstændig under-app med egen
# auth, så dens middleware kun gælder under /hovedhus. Begge apps har en
# lifespan (StreamableHTTP-sessionerne), og FastAPI tager kun én.
hh_app = (mcp_hovedhus.gateway.http_app(path="/mcp", json_response=True)
if mcp_hovedhus.gateway else None)
@contextlib.asynccontextmanager
async def _lifespan(a):
async with mcp_app.lifespan(a):
if hh_app is None:
yield
else:
async with hh_app.lifespan(hh_app):
yield
app = FastAPI(title="Weircon Portal", lifespan=_lifespan)
for _mcp_route in mcp_app.routes:
app.router.routes.append(_mcp_route)
# route-extend copies routes but NOT middleware — carry FastMCP's auth/context
@@ -34,6 +54,13 @@ for _mcp_mw in reversed(mcp_app.user_middleware):
app.add_middleware(SessionMiddleware, secret_key=config.SECRET_KEY, https_only=True,
same_site="lax", max_age=60 * 60 * 12)
app.mount("/static", StaticFiles(directory=config.BASE / "static"), name="static")
if hh_app is not None:
# OAuth-metadata skal ligge i roden (RFC 8414/9728) med stien som suffiks.
# Den bare /.well-known/openid-configuration tilhører hovedgatewayen.
for _r in mcp_hovedhus.gateway.auth.get_well_known_routes(mcp_path="/mcp"):
if _r.path != "/.well-known/openid-configuration":
app.router.routes.append(_r)
app.mount(mcp_hovedhus.PREFIX, hh_app)
templates = Jinja2Templates(directory=str(config.BASE / "templates"))
oauth = OAuth()