Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,234 @@
|
||||
"""Hub-owned gateway adapter contracts without public gateway IDs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, cast
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import (
|
||||
AnyHttpUrl,
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
TypeAdapter,
|
||||
field_validator,
|
||||
model_validator,
|
||||
)
|
||||
|
||||
from packages.hub_runtime import SecretFileError, read_secret_file
|
||||
|
||||
from .errors import ContextForgeError
|
||||
|
||||
|
||||
class ContextForgeSettings(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
base_url: AnyHttpUrl
|
||||
auth_token_env: str | None = Field(default=None, min_length=1, max_length=256)
|
||||
auth_token_file: Path | None = None
|
||||
auth_scheme: Literal["Bearer", "Basic"] = "Bearer"
|
||||
basic_username: str = Field(default="itworx-contextforge-admin", min_length=1, max_length=256)
|
||||
timeout_seconds: float = Field(default=5.0, gt=0, le=30)
|
||||
retries: int = Field(default=2, ge=0, le=5)
|
||||
max_response_bytes: int = Field(default=4 * 1024 * 1024, ge=1024, le=4 * 1024 * 1024)
|
||||
|
||||
@classmethod
|
||||
def from_env(cls, environ: Mapping[str, str] | None = None) -> ContextForgeSettings:
|
||||
values = environ if environ is not None else os.environ
|
||||
base_url = values.get("HUB_CONTEXTFORGE_URL")
|
||||
if not base_url:
|
||||
raise ContextForgeError("gateway_configuration_missing", "Gateway URL is unavailable.")
|
||||
try:
|
||||
return cls(
|
||||
base_url=TypeAdapter(AnyHttpUrl).validate_python(base_url),
|
||||
auth_token_env=(
|
||||
values.get("HUB_CONTEXTFORGE_TOKEN_ENV")
|
||||
if values.get("HUB_CONTEXTFORGE_TOKEN_FILE")
|
||||
else values.get("HUB_CONTEXTFORGE_TOKEN_ENV", "HUB_CONTEXTFORGE_TOKEN")
|
||||
),
|
||||
auth_token_file=(
|
||||
Path(values["HUB_CONTEXTFORGE_TOKEN_FILE"])
|
||||
if values.get("HUB_CONTEXTFORGE_TOKEN_FILE")
|
||||
else None
|
||||
),
|
||||
auth_scheme=cast(
|
||||
Literal["Bearer", "Basic"],
|
||||
values.get("HUB_CONTEXTFORGE_AUTH_SCHEME", "Bearer"),
|
||||
),
|
||||
basic_username=values.get(
|
||||
"HUB_CONTEXTFORGE_BASIC_USERNAME", "itworx-contextforge-admin"
|
||||
),
|
||||
timeout_seconds=float(values.get("HUB_CONTEXTFORGE_TIMEOUT_SECONDS", "5")),
|
||||
retries=int(values.get("HUB_CONTEXTFORGE_RETRIES", "2")),
|
||||
)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ContextForgeError(
|
||||
"gateway_configuration_invalid", "Gateway configuration is invalid."
|
||||
) from exc
|
||||
|
||||
@model_validator(mode="after")
|
||||
def exactly_one_credential_source(self) -> ContextForgeSettings:
|
||||
if bool(self.auth_token_env) == bool(self.auth_token_file):
|
||||
raise ValueError("configure exactly one ContextForge credential source")
|
||||
return self
|
||||
|
||||
def credential(self, environ: Mapping[str, str] | None = None) -> str:
|
||||
if self.auth_token_file:
|
||||
try:
|
||||
return read_secret_file(self.auth_token_file)
|
||||
except SecretFileError as exc:
|
||||
raise ContextForgeError(
|
||||
"gateway_credentials_missing", "Gateway authorization is unavailable."
|
||||
) from exc
|
||||
value = (environ or os.environ).get(self.auth_token_env or "")
|
||||
if not value:
|
||||
raise ContextForgeError(
|
||||
"gateway_credentials_missing", "Gateway authorization is unavailable."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class GatewayDesiredState(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
slug: str = Field(
|
||||
min_length=1, max_length=63, pattern=r"^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$"
|
||||
)
|
||||
url: AnyHttpUrl
|
||||
description: str = Field(default="", max_length=1000)
|
||||
transport: Literal["STREAMABLEHTTP", "SSE", "STDIO", "WEBSOCKET"] = "STREAMABLEHTTP"
|
||||
enabled: bool = True
|
||||
passthrough_headers: tuple[str, ...] = ()
|
||||
|
||||
@field_validator("passthrough_headers")
|
||||
@classmethod
|
||||
def validate_passthrough_headers(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
||||
allowed = {
|
||||
"x-itworx-project",
|
||||
"x-itworx-environment",
|
||||
"x-itworx-client-id",
|
||||
"x-itworx-trace-id",
|
||||
"x-itworx-identity-assertion",
|
||||
}
|
||||
normalized = tuple(dict.fromkeys(item.strip().lower() for item in value))
|
||||
if any(not re.fullmatch(r"[a-z0-9-]{1,64}", item) for item in normalized):
|
||||
raise ValueError("invalid passthrough header")
|
||||
if not set(normalized).issubset(allowed):
|
||||
raise ValueError("gateway passthrough header is not approved")
|
||||
return normalized
|
||||
|
||||
|
||||
class GatewaySnapshot(BaseModel):
|
||||
"""Publicly safe gateway state; `external_id` is intentionally absent."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
slug: str
|
||||
url: str
|
||||
description: str
|
||||
transport: str
|
||||
enabled: bool
|
||||
status: str
|
||||
passthrough_headers: tuple[str, ...] = ()
|
||||
|
||||
|
||||
class GatewayHandle(BaseModel):
|
||||
"""Adapter-internal handle; do not serialize into public Hub contracts."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
external_id: str = Field(min_length=1, max_length=256)
|
||||
snapshot: GatewaySnapshot
|
||||
|
||||
|
||||
class GatewayTool(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
external_id: str = Field(min_length=1, max_length=256)
|
||||
name: str = Field(min_length=1, max_length=256)
|
||||
published_name: str = Field(min_length=1, max_length=256)
|
||||
input_schema: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class GatewayPrompt(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
external_id: str = Field(min_length=1, max_length=256)
|
||||
name: str = Field(min_length=1, max_length=256)
|
||||
|
||||
|
||||
class VirtualServerDesiredState(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
external_id: UUID
|
||||
name: str = Field(pattern=r"^[a-z0-9][a-z0-9-]{0,62}$")
|
||||
description: str = Field(max_length=1000)
|
||||
associated_tool_ids: tuple[str, ...] = Field(min_length=1)
|
||||
associated_prompt_ids: tuple[str, ...] = ()
|
||||
# ContextForge's service token is intentionally public-only. The virtual
|
||||
# server is reachable solely on the private Docker network; the public Hub
|
||||
# dataplane remains the OAuth, binding, policy and rate-limit boundary.
|
||||
visibility: Literal["public"] = "public"
|
||||
|
||||
@field_validator("associated_tool_ids")
|
||||
@classmethod
|
||||
def unique_tool_ids(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
||||
if len(set(value)) != len(value) or any(not item or len(item) > 256 for item in value):
|
||||
raise ValueError("virtual server tool IDs must be unique bounded values")
|
||||
return tuple(sorted(value))
|
||||
|
||||
@field_validator("associated_prompt_ids")
|
||||
@classmethod
|
||||
def unique_prompt_ids(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
||||
if len(set(value)) != len(value) or any(not item or len(item) > 256 for item in value):
|
||||
raise ValueError("virtual server prompt IDs must be unique bounded values")
|
||||
return tuple(sorted(value))
|
||||
|
||||
|
||||
class VirtualServerHandle(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
external_id: str = Field(min_length=1, max_length=256)
|
||||
name: str = Field(min_length=1, max_length=63)
|
||||
associated_tool_ids: tuple[str, ...]
|
||||
associated_prompt_ids: tuple[str, ...] = ()
|
||||
enabled: bool
|
||||
visibility: Literal["public", "private", "team"]
|
||||
|
||||
|
||||
class GatewayTestResult(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
reachable: bool
|
||||
status: str
|
||||
detail: str = Field(default="", max_length=256)
|
||||
|
||||
|
||||
class GatewayCapabilities(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
health: bool
|
||||
readiness: bool
|
||||
version: str | None
|
||||
gateway_crud: bool
|
||||
gateway_test: bool
|
||||
|
||||
|
||||
class SanitizedGatewayInventory(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
generated_at: datetime
|
||||
capabilities: GatewayCapabilities
|
||||
gateways: tuple[dict[str, Any], ...]
|
||||
|
||||
@field_validator("generated_at")
|
||||
@classmethod
|
||||
def require_aware_timestamp(cls, value: datetime) -> datetime:
|
||||
if value.tzinfo is None or value.utcoffset() is None:
|
||||
raise ValueError("inventory timestamp must be timezone-aware")
|
||||
return value.astimezone(UTC)
|
||||
Reference in new issue
Block a user