Publish curated ITWorx_MCP_Hub source from 6c7434368ce2a594005983c5a6601a31a639e7f1
This commit is contained in:
1 parent
77946d62c4
commit
6d40ab975c
86 files changed
+6784
-1478
No files matched your search
@@ -23,6 +23,7 @@ against every route.
|
||||
| tool.invoke | | | X | X |
|
||||
| operations.check | | | X | X |
|
||||
| client.bootstrap | | | X | X |
|
||||
| client.verify | | | X | X |
|
||||
| client.disable | | | X | X |
|
||||
| client.revoke | | | X | X |
|
||||
| client.create | | | | X |
|
||||
@@ -62,6 +63,7 @@ ENDPOINT_PERMISSIONS: Mapping[str, frozenset[ManagementRole]] = {
|
||||
"operations.check": frozenset({_R.OPERATOR, _R.ADMIN}),
|
||||
"client.read": frozenset({_R.VIEWER, _R.AUDITOR, _R.OPERATOR, _R.ADMIN}),
|
||||
"client.bootstrap": frozenset({_R.OPERATOR, _R.ADMIN}),
|
||||
"client.verify": frozenset({_R.OPERATOR, _R.ADMIN}),
|
||||
"client.disable": frozenset({_R.OPERATOR, _R.ADMIN}),
|
||||
"client.revoke": frozenset({_R.OPERATOR, _R.ADMIN}),
|
||||
"client.create": frozenset({_R.ADMIN}),
|
||||
|
||||
@@ -150,6 +150,7 @@ class CatalogLoader:
|
||||
documents: list[CatalogDocument] = []
|
||||
source_files: list[str] = []
|
||||
seen: set[tuple[str, str]] = set()
|
||||
schema_store: dict[str, Any] | None = None
|
||||
if not self.root.is_dir():
|
||||
return ValidationReport(
|
||||
valid=False,
|
||||
@@ -191,7 +192,9 @@ class CatalogLoader:
|
||||
continue
|
||||
issues.extend(self._secret_issues(raw, path, []))
|
||||
schema = self._schema(schema_name)
|
||||
resolver = RefResolver.from_schema(schema, store=self._schema_store())
|
||||
if schema_store is None:
|
||||
schema_store = self._schema_store()
|
||||
resolver = RefResolver.from_schema(schema, store=schema_store)
|
||||
for error in sorted(
|
||||
Draft202012Validator(schema, resolver=resolver).iter_errors(raw),
|
||||
key=lambda item: list(item.path),
|
||||
|
||||
@@ -9,7 +9,13 @@ 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 packages.hub_clients import (
|
||||
BootstrapHandle,
|
||||
ClientConfig,
|
||||
ClientKind,
|
||||
ClientRecord,
|
||||
ClientStatus,
|
||||
)
|
||||
|
||||
from .service import ClientService
|
||||
|
||||
@@ -50,6 +56,9 @@ def build_client_router(
|
||||
*,
|
||||
authenticate: Callable[[Request], Any] = _deny_by_default,
|
||||
authorize: Callable[[Any, str], None] = _deny_all_actions,
|
||||
onboarding_status: Callable[[UUID], dict[str, Any]] | None = None,
|
||||
verify_onboarding: Callable[[UUID, Any], dict[str, Any]] | None = None,
|
||||
transition_client: Callable[[UUID, ClientStatus, Any], ClientRecord] | None = None,
|
||||
prefix: str = "/clients",
|
||||
token_endpoint: str = "https://auth.example.invalid/application/o/token/", # noqa: S107
|
||||
oauth_client_id: str = "itworx-mcp-hub",
|
||||
@@ -112,6 +121,28 @@ def build_client_router(
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
|
||||
@router.get("/{client_id}/onboarding-status")
|
||||
def onboarding(client_id: UUID, principal: Any = authenticated) -> dict[str, Any]:
|
||||
authorize(principal, "client.read")
|
||||
if onboarding_status is None:
|
||||
raise HTTPException(status_code=404, detail="client onboarding status is unavailable")
|
||||
try:
|
||||
return onboarding_status(client_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="onboarding client not found") from exc
|
||||
|
||||
@router.post("/{client_id}/verify")
|
||||
def verify(client_id: UUID, principal: Any = authenticated) -> dict[str, Any]:
|
||||
authorize(principal, "client.verify")
|
||||
if verify_onboarding is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="client onboarding verification is unavailable"
|
||||
)
|
||||
try:
|
||||
return verify_onboarding(client_id, principal)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="onboarding 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")
|
||||
@@ -126,15 +157,25 @@ def build_client_router(
|
||||
def disable(client_id: UUID, principal: Any = authenticated) -> ClientRecord:
|
||||
authorize(principal, "client.disable")
|
||||
try:
|
||||
return service.disable(client_id)
|
||||
return (
|
||||
transition_client(client_id, ClientStatus.DISABLED, principal)
|
||||
if transition_client is not None
|
||||
else service.disable(client_id)
|
||||
)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) 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)
|
||||
return (
|
||||
transition_client(client_id, ClientStatus.REVOKED, principal)
|
||||
if transition_client is not None
|
||||
else service.revoke(client_id)
|
||||
)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ from uuid import NAMESPACE_URL, UUID, uuid5
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from packages.hub_clients import ClientKind
|
||||
|
||||
from .models import (
|
||||
ClientBindingRow,
|
||||
ClientRow,
|
||||
@@ -114,6 +116,12 @@ def provision_machine_client(
|
||||
or by_id.status != "pending"
|
||||
):
|
||||
raise ValueError("existing Hub client is not an activatable pending identity")
|
||||
try:
|
||||
kind = ClientKind(existing_metadata.get("kind", ClientKind.STDIO_BRIDGE.value))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError("pending machine client has an invalid client kind") from exc
|
||||
else:
|
||||
kind = ClientKind.STDIO_BRIDGE
|
||||
client = principal or by_id
|
||||
metadata = {
|
||||
"managedBy": "authentik-machine-provisioning",
|
||||
@@ -121,7 +129,7 @@ def provision_machine_client(
|
||||
"name": name,
|
||||
"project": project_slug,
|
||||
"environment": environment_name,
|
||||
"kind": "stdio-bridge",
|
||||
"kind": kind.value,
|
||||
"oauthUsername": oauth_username,
|
||||
"credentialReference": f"authentik://service-account/{oauth_username}",
|
||||
"operatorDisabled": False,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
from uuid import UUID, uuid4
|
||||
@@ -14,7 +16,9 @@ from packages.hub_clients import (
|
||||
ClientStatus,
|
||||
generate_config,
|
||||
)
|
||||
from services.control_api.auth.models import AuthenticatedPrincipal
|
||||
from services.control_api.persistence.models import (
|
||||
AuditEventRow,
|
||||
ClientBindingRow,
|
||||
ClientRow,
|
||||
EnvironmentRow,
|
||||
@@ -158,13 +162,21 @@ class PersistentClientService:
|
||||
selected = record if kind is None else record.model_copy(update={"kind": kind})
|
||||
return generate_config(selected, public_host=self.public_host)
|
||||
|
||||
def _transition(self, client_id: UUID, status: ClientStatus) -> ClientRecord:
|
||||
def transition(
|
||||
self, client_id: UUID, status: ClientStatus, actor: AuthenticatedPrincipal | None = None
|
||||
) -> ClientRecord:
|
||||
now = datetime.now(UTC)
|
||||
with self.sessions() as session, session.begin():
|
||||
row = session.get(ClientRow, client_id)
|
||||
row = session.scalar(
|
||||
select(ClientRow)
|
||||
.join(WorkspaceRow, WorkspaceRow.id == ClientRow.workspace_id)
|
||||
.where(ClientRow.id == client_id, WorkspaceRow.slug == "itworx")
|
||||
)
|
||||
if row is None:
|
||||
raise KeyError(client_id)
|
||||
metadata = dict(row.record_metadata or {})
|
||||
if row.status == "revoked" and status is ClientStatus.DISABLED:
|
||||
raise ValueError("revoked clients cannot be changed to disabled")
|
||||
metadata["operatorDisabled"] = True
|
||||
if status is ClientStatus.DISABLED:
|
||||
metadata["disabledAt"] = now.isoformat()
|
||||
@@ -173,18 +185,55 @@ class PersistentClientService:
|
||||
row.status = "revoked"
|
||||
row.revoked_at = now
|
||||
row.record_metadata = metadata
|
||||
for binding in session.scalars(
|
||||
bindings = session.scalars(
|
||||
select(ClientBindingRow).where(ClientBindingRow.client_id == client_id)
|
||||
).all():
|
||||
).all()
|
||||
for binding in bindings:
|
||||
binding.enabled = False
|
||||
scope_binding = bindings[0] if len(bindings) == 1 else None
|
||||
scope_server = (
|
||||
session.get(VirtualServerRow, scope_binding.virtual_server_id)
|
||||
if scope_binding is not None
|
||||
else None
|
||||
)
|
||||
session.flush()
|
||||
action = "client.disable" if status is ClientStatus.DISABLED else "client.revoke"
|
||||
arguments = {"clientId": str(client_id), "action": action}
|
||||
session.add(
|
||||
AuditEventRow(
|
||||
id=uuid4(),
|
||||
occurred_at=now,
|
||||
trace_id=str(uuid4()),
|
||||
workspace_id=row.workspace_id,
|
||||
project_id=scope_binding.project_id if scope_binding else None,
|
||||
environment_id=scope_server.environment_id if scope_server else None,
|
||||
virtual_server_id=scope_server.id if scope_server else None,
|
||||
client_id=row.id,
|
||||
event_type=action,
|
||||
tool_name=action,
|
||||
principal_issuer=(actor.issuer if actor is not None else self.issuer)[:512],
|
||||
principal_subject=(
|
||||
actor.subject if actor is not None else "runtime-client-management"
|
||||
)[:256],
|
||||
policy_decision="allow",
|
||||
matched_rule_ids=[action],
|
||||
policy_trace=[{"action": action, "decision": "allow"}],
|
||||
arguments_hash=hashlib.sha256(
|
||||
json.dumps(arguments, sort_keys=True).encode("utf-8")
|
||||
).hexdigest(),
|
||||
sanitized_arguments=arguments,
|
||||
duration_ms=0,
|
||||
outcome="success",
|
||||
retention_until=now + timedelta(days=30),
|
||||
)
|
||||
)
|
||||
return self._record(row)
|
||||
|
||||
def disable(self, client_id: UUID) -> ClientRecord:
|
||||
return self._transition(client_id, ClientStatus.DISABLED)
|
||||
return self.transition(client_id, ClientStatus.DISABLED)
|
||||
|
||||
def revoke(self, client_id: UUID) -> ClientRecord:
|
||||
return self._transition(client_id, ClientStatus.REVOKED)
|
||||
return self.transition(client_id, ClientStatus.REVOKED)
|
||||
|
||||
def bootstrap(self, client_id: UUID) -> BootstrapHandle:
|
||||
record = self._get(client_id)
|
||||
|
||||
@@ -677,9 +677,7 @@ def build_server() -> MCPServer[Any]:
|
||||
)
|
||||
async def portfolio_governance_apply(
|
||||
context: Context[Any, Any],
|
||||
plans: Annotated[
|
||||
list[PortfolioGovernanceApplyPlan], Field(min_length=1, max_length=5)
|
||||
],
|
||||
plans: Annotated[list[PortfolioGovernanceApplyPlan], Field(min_length=1, max_length=5)],
|
||||
) -> dict[str, Any]:
|
||||
"""Apply at most five exact governance plans; every item is independently verified."""
|
||||
return connector.portfolio_governance_apply(
|
||||
@@ -813,7 +811,9 @@ def build_server() -> MCPServer[Any]:
|
||||
structured_output=True,
|
||||
)
|
||||
async def pull_request_validation_ensure(
|
||||
repositoryRef: str, number: int, expectedHeadSha: str,
|
||||
repositoryRef: str,
|
||||
number: int,
|
||||
expectedHeadSha: str,
|
||||
context: Context[Any, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Ensure a real canonical full PR run for the exact current head."""
|
||||
|
||||
+1214
-19
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user