Files
ITWorx_MCP_Hub-Public/services/control_api/persistence/dataplane_source.py
T

134 lines
5.2 KiB
Python

"""Read-only alias projection from Hub-owned persistence."""
from __future__ import annotations
import re
from collections import defaultdict
from typing import Any, cast
from urllib.parse import urlsplit
from pydantic import AnyHttpUrl, TypeAdapter
from sqlalchemy import select
from sqlalchemy.orm import Session
from packages.hub_contracts.dataplane import (
SUPPORTED_PROTOCOLS,
AliasRecord,
ClientBinding,
EnvironmentName,
)
from .models import (
ClientBindingRow,
ClientRow,
EnvironmentRow,
ProjectRow,
VirtualServerRow,
)
class SqlAlchemyAliasSource:
"""Projects only enabled Hub virtual servers and active explicit bindings."""
def __init__(self, session: Session, *, workspace_id: Any, gateway_base_url: str) -> None:
self.session = session
self.workspace_id = workspace_id
base = TypeAdapter(AnyHttpUrl).validate_python(gateway_base_url)
internal_contextforge = (
base.scheme == "http"
and base.host in {"contextforge", "contextforge-control"}
and base.port == 4444
and (base.path or "").rstrip("/") == "/mcp"
)
if (
base.username is not None
or base.password is not None
or (base.scheme != "https" and not internal_contextforge)
):
raise ValueError("gateway URL must be HTTPS or the fixed internal gateway")
self.gateway_base_url = str(base).rstrip("/")
self.internal_contextforge = internal_contextforge
def aliases(self) -> tuple[AliasRecord, ...]:
projects = {
row.id: row
for row in self.session.scalars(
select(ProjectRow).where(ProjectRow.workspace_id == self.workspace_id)
).all()
}
environments = {
row.id: row
for row in self.session.scalars(
select(EnvironmentRow).where(EnvironmentRow.workspace_id == self.workspace_id)
).all()
}
clients = {
row.id: row
for row in self.session.scalars(
select(ClientRow).where(ClientRow.workspace_id == self.workspace_id)
).all()
}
bindings_by_server: dict[Any, list[ClientBinding]] = defaultdict(list)
for row in self.session.scalars(
select(ClientBindingRow).where(
ClientBindingRow.workspace_id == self.workspace_id,
ClientBindingRow.enabled.is_(True),
)
).all():
client = clients.get(row.client_id)
if client is None or client.status != "active":
continue
bindings_by_server[row.virtual_server_id].append(
ClientBinding(
client_id=client.id,
issuer=client.issuer,
subject=client.subject,
enabled=True,
)
)
values: list[AliasRecord] = []
for server in self.session.scalars(
select(VirtualServerRow).where(
VirtualServerRow.workspace_id == self.workspace_id,
VirtualServerRow.enabled.is_(True),
VirtualServerRow.health_state.in_(("healthy", "degraded")),
)
).all():
if self.internal_contextforge and not server.gateway_external_id:
continue
project = projects.get(server.project_id)
environment = environments.get(server.environment_id)
if project is None or environment is None:
continue
if environment.name not in {"dev", "test", "staging", "prod"}:
continue
metadata = server.record_metadata
endpoint_alias = metadata.get("endpointAlias") if isinstance(metadata, dict) else None
if not isinstance(endpoint_alias, str):
endpoint_alias = f"/mcp/{project.slug}/{environment.name}"
try:
if self.internal_contextforge:
external_id = server.gateway_external_id or ""
if not re.fullmatch(r"[0-9a-f]{32}", external_id):
raise ValueError("invalid ContextForge server route identifier")
parsed = urlsplit(self.gateway_base_url)
upstream = f"{parsed.scheme}://{parsed.netloc}/servers/{external_id}/mcp"
else:
upstream = f"{self.gateway_base_url}{endpoint_alias}"
values.append(
AliasRecord(
project_slug=project.slug,
workspace_id=self.workspace_id,
project_id=project.id,
environment=cast(EnvironmentName, environment.name),
alias=endpoint_alias,
upstream_url=TypeAdapter(AnyHttpUrl).validate_python(upstream),
enabled=True,
protocol_revisions=tuple(SUPPORTED_PROTOCOLS),
bindings=tuple(bindings_by_server.get(server.id, ())),
)
)
except ValueError:
continue
return tuple(sorted(values, key=lambda item: (item.project_slug, item.environment)))