Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:36:21 +02:00
commit cb5be898ea
663 files changed
+82674

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
from .router import ClientCreateRequest, ClientOnboardingResponse, build_client_router
from .service import ClientService
__all__ = [
"ClientCreateRequest",
"ClientOnboardingResponse",
"ClientService",
"build_client_router",
]
+141
View File
@@ -0,0 +1,141 @@
"""Authenticated management routes for machine-client onboarding and revocation."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, ConfigDict, Field
from packages.hub_clients import BootstrapHandle, ClientConfig, ClientKind, ClientRecord
from .service import ClientService
def _deny_by_default(_: Request) -> None:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="client management authorization required"
)
def _deny_all_actions(_: Any, action: str) -> None:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"client management authorization required for {action}",
)
class ClientCreateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str = Field(min_length=1, max_length=128)
project: str = Field(min_length=1, max_length=63, pattern=r"^[a-z0-9][a-z0-9-]*$")
environment: str = Field(min_length=1, max_length=32, pattern=r"^[a-z0-9][a-z0-9-]*$")
kind: ClientKind
class ClientOnboardingResponse(BaseModel):
model_config = ConfigDict(extra="forbid")
client: ClientRecord
bootstrap: BootstrapHandle
config: ClientConfig
provisioning: dict[str, Any]
def build_client_router(
service: ClientService,
*,
authenticate: Callable[[Request], Any] = _deny_by_default,
authorize: Callable[[Any, str], None] = _deny_all_actions,
prefix: str = "/clients",
token_endpoint: str = "https://auth.example.invalid/application/o/token/", # noqa: S107
oauth_client_id: str = "itworx-mcp-hub",
) -> APIRouter:
"""Every route authenticates first, then checks a specific action against
`authorize` -- deliberately not one blanket router-level dependency, so a
read (list/config) and a destructive action (revoke) can require
different roles. See services/control_api/authz.py for the actual
permission matrix `authorize` is expected to enforce in production.
"""
router = APIRouter(prefix=prefix, tags=["clients"])
# Bound once, reused as route defaults below: ruff (flake8-bugbear B008)
# flags calling `Depends(...)` directly inside a function signature's
# default value.
authenticated = Depends(authenticate)
@router.post("", response_model=ClientOnboardingResponse, status_code=status.HTTP_201_CREATED)
def create(
payload: ClientCreateRequest, principal: Any = authenticated
) -> ClientOnboardingResponse:
authorize(principal, "client.create")
try:
client, bootstrap, config = service.create(**payload.model_dump())
except ValueError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
return ClientOnboardingResponse(
client=client,
bootstrap=bootstrap,
config=config,
provisioning={
"status": "pending-authentik-service-account",
"subject": None,
"issuer": str(getattr(principal, "issuer", "unavailable")),
"oauthUsername": (
f"svc-mcp-codex-{client.project}-{str(client.id).replace('-', '')[:8]}"
),
"tokenEndpoint": token_endpoint,
"oauthClientId": oauth_client_id,
"steps": [
"Provision the named Authentik service account and app password.",
"Activate this pending Hub client with the exact Authentik user UUID.",
"Install the generated Codex stdio profile using a user-only credential file.",
"Run MCP discovery, one read-only invocation and one cross-project denial.",
],
},
)
@router.get("", response_model=tuple[ClientRecord, ...])
def list_clients(principal: Any = authenticated) -> tuple[ClientRecord, ...]:
authorize(principal, "client.read")
return service.list()
@router.get("/{client_id}/config", response_model=ClientConfig)
def config(
client_id: UUID, kind: ClientKind | None = None, principal: Any = authenticated
) -> ClientConfig:
authorize(principal, "client.read")
try:
return service.config(client_id, kind)
except KeyError as exc:
raise HTTPException(status_code=404, detail="client not found") from exc
@router.post("/{client_id}/bootstrap", response_model=BootstrapHandle)
def bootstrap(client_id: UUID, principal: Any = authenticated) -> BootstrapHandle:
authorize(principal, "client.bootstrap")
try:
return service.bootstrap(client_id)
except KeyError as exc:
raise HTTPException(status_code=404, detail="client not found") from exc
except PermissionError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
@router.post("/{client_id}/disable", response_model=ClientRecord)
def disable(client_id: UUID, principal: Any = authenticated) -> ClientRecord:
authorize(principal, "client.disable")
try:
return service.disable(client_id)
except KeyError as exc:
raise HTTPException(status_code=404, detail="client not found") from exc
@router.post("/{client_id}/revoke", response_model=ClientRecord)
def revoke(client_id: UUID, principal: Any = authenticated) -> ClientRecord:
authorize(principal, "client.revoke")
try:
return service.revoke(client_id)
except KeyError as exc:
raise HTTPException(status_code=404, detail="client not found") from exc
return router
+51
View File
@@ -0,0 +1,51 @@
"""Control-plane service for independently scoped MCP clients."""
from __future__ import annotations
from uuid import UUID
from packages.hub_clients import (
BootstrapHandle,
ClientConfig,
ClientKind,
ClientRecord,
ClientRegistry,
generate_config,
)
class ClientService:
def __init__(self, registry: ClientRegistry | None = None) -> None:
self.registry = registry or ClientRegistry()
def create(
self, *, name: str, project: str, environment: str, kind: ClientKind
) -> tuple[ClientRecord, BootstrapHandle, ClientConfig]:
record = self.registry.create(
name=name, project=project, environment=environment, kind=kind
)
bootstrap = self.registry.issue_bootstrap(record.id)
return record, bootstrap, generate_config(record)
def list(self) -> tuple[ClientRecord, ...]:
return self.registry.list()
def config(self, client_id: UUID, kind: ClientKind | None = None) -> ClientConfig:
record = self.registry.get(client_id)
if record is None:
raise KeyError(client_id)
selected = (
record
if kind is None or kind is record.kind
else record.model_copy(update={"kind": kind})
)
return generate_config(selected)
def disable(self, client_id: UUID) -> ClientRecord:
return self.registry.disable(client_id)
def revoke(self, client_id: UUID) -> ClientRecord:
return self.registry.revoke(client_id)
def bootstrap(self, client_id: UUID) -> BootstrapHandle:
return self.registry.issue_bootstrap(client_id)