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

@@ -0,0 +1,33 @@
"""Replaceable ContextForge gateway adapter boundary."""
from .client import ContextForgeClient
from .errors import ContextForgeError
from .fake import FakeContextForge
from .models import (
ContextForgeSettings,
GatewayCapabilities,
GatewayDesiredState,
GatewayPrompt,
GatewaySnapshot,
GatewayTestResult,
GatewayTool,
SanitizedGatewayInventory,
VirtualServerDesiredState,
VirtualServerHandle,
)
__all__ = [
"ContextForgeClient",
"ContextForgeError",
"ContextForgeSettings",
"FakeContextForge",
"GatewayCapabilities",
"GatewayDesiredState",
"GatewayPrompt",
"GatewaySnapshot",
"GatewayTestResult",
"GatewayTool",
"SanitizedGatewayInventory",
"VirtualServerDesiredState",
"VirtualServerHandle",
]
+625
View File
@@ -0,0 +1,625 @@
"""Typed, retry-bounded ContextForge HTTP adapter."""
from __future__ import annotations
import hashlib
import json
import os
import time
from base64 import b64encode
from collections.abc import Mapping
from datetime import UTC, datetime
from typing import Any, Literal, Protocol, cast
from uuid import UUID
import httpx
from .errors import ContextForgeError
from .models import (
ContextForgeSettings,
GatewayCapabilities,
GatewayDesiredState,
GatewayHandle,
GatewayPrompt,
GatewaySnapshot,
GatewayTestResult,
GatewayTool,
SanitizedGatewayInventory,
VirtualServerDesiredState,
VirtualServerHandle,
)
class GatewayAdapter(Protocol):
def health(self) -> dict[str, Any]: ...
def capabilities(self) -> GatewayCapabilities: ...
def list_gateways(
self, *, cursor: str | None = None, limit: int = 50
) -> tuple[GatewaySnapshot, ...]: ...
def ensure_gateway(self, desired: GatewayDesiredState) -> GatewayHandle: ...
def update_gateway(
self, handle: GatewayHandle, desired: GatewayDesiredState
) -> GatewayHandle: ...
def delete_gateway(self, handle: GatewayHandle) -> None: ...
def refresh_gateway(self, handle: GatewayHandle) -> None: ...
def list_gateway_tools(self, handle: GatewayHandle) -> tuple[GatewayTool, ...]: ...
def ensure_canonical_tool_names(
self, tools: tuple[GatewayTool, ...]
) -> tuple[GatewayTool, ...]: ...
def list_gateway_prompts(self, handle: GatewayHandle) -> tuple[GatewayPrompt, ...]: ...
def test_gateway(self, handle: GatewayHandle) -> GatewayTestResult: ...
class ContextForgeClient:
"""ContextForge API client; raw gateway IDs stay inside this adapter."""
def __init__(
self,
settings: ContextForgeSettings,
*,
transport: httpx.BaseTransport | None = None,
environ: Mapping[str, str] | None = None,
) -> None:
self.settings = settings
self._environ = environ if environ is not None else os.environ
self._http = httpx.Client(
base_url=str(settings.base_url).rstrip("/"),
timeout=settings.timeout_seconds,
transport=transport,
)
self._capabilities: GatewayCapabilities | None = None
def close(self) -> None:
self._http.close()
def _auth_headers(self) -> dict[str, str]:
token = self.settings.credential(self._environ)
scheme = self.settings.auth_scheme
if scheme == "Basic":
encoded = b64encode(f"{self.settings.basic_username}:{token}".encode()).decode("ascii")
credential = f"Basic {encoded}"
else:
credential = f"Bearer {token}"
return {"Accept": "application/json", "Authorization": credential}
def authorization_header(self) -> str:
"""Build the outbound credential only at the request boundary."""
return self._auth_headers()["Authorization"]
def _request(
self,
method: str,
path: str,
*,
params: Mapping[str, str | int] | None = None,
json_body: Mapping[str, Any] | None = None,
retryable: bool = False,
) -> Any:
attempts = self.settings.retries if retryable else 0
for attempt in range(attempts + 1):
try:
response = self._http.request(
method,
path,
headers=self._auth_headers(),
params=params,
json=json_body,
)
except httpx.TimeoutException as exc:
if attempt < attempts:
time.sleep(min(0.25 * (attempt + 1), 1.0))
continue
raise ContextForgeError(
"gateway_timeout", "Gateway request timed out.", retryable=True
) from exc
except httpx.HTTPError as exc:
if attempt < attempts:
time.sleep(min(0.25 * (attempt + 1), 1.0))
continue
raise ContextForgeError(
"gateway_unavailable", "Gateway could not be reached.", retryable=True
) from exc
if response.status_code >= 400:
error = ContextForgeError.from_status(response.status_code)
if error.retryable and attempt < attempts:
time.sleep(min(0.25 * (attempt + 1), 1.0))
continue
raise error
if len(response.content) > self.settings.max_response_bytes:
raise ContextForgeError(
"gateway_response_too_large", "Gateway response exceeded the output budget."
)
if not response.content:
return {}
try:
return response.json()
except (ValueError, json.JSONDecodeError) as exc:
raise ContextForgeError(
"gateway_invalid_response", "Gateway returned an invalid response."
) from exc
raise ContextForgeError("gateway_unavailable", "Gateway request failed.", retryable=True)
def health(self) -> dict[str, Any]:
value = self._request("GET", "/health", retryable=True)
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Gateway health response was invalid."
)
return {str(key): value[key] for key in value if key in {"status", "version"}}
def readiness(self) -> dict[str, Any]:
value = self._request("GET", "/ready", retryable=True)
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Gateway readiness response was invalid."
)
return {str(key): value[key] for key in value if key in {"status"}}
def version(self) -> str | None:
value = self._request("GET", "/version", retryable=True)
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Gateway version response was invalid."
)
raw = value.get("version") or value.get("release")
if raw is None and isinstance(value.get("app"), dict):
raw = value["app"].get("version")
return str(raw) if raw is not None else None
def capabilities(self) -> GatewayCapabilities:
if self._capabilities is not None:
return self._capabilities
health_ok = False
readiness_ok = False
version_value: str | None = None
try:
self.health()
health_ok = True
except ContextForgeError:
pass
try:
self.readiness()
readiness_ok = True
except ContextForgeError:
pass
try:
version_value = self.version()
except ContextForgeError:
pass
crud_ok = False
if health_ok:
try:
self.list_gateways(limit=1)
crud_ok = True
except ContextForgeError:
pass
self._capabilities = GatewayCapabilities(
health=health_ok,
readiness=readiness_ok,
version=version_value,
gateway_crud=crud_ok,
gateway_test=health_ok,
)
return self._capabilities
@staticmethod
def _snapshot(payload: Mapping[str, Any]) -> tuple[str, GatewaySnapshot]:
external_id = payload.get("id")
name = payload.get("name") or payload.get("slug")
if (
not isinstance(external_id, str)
or not external_id
or not isinstance(name, str)
or not name
):
raise ContextForgeError(
"gateway_invalid_response", "Gateway resource identity was invalid."
)
snapshot = GatewaySnapshot(
slug=name,
url=str(payload.get("url", "")),
description=str(payload.get("description", ""))[:1000],
transport=str(payload.get("transport", "STREAMABLEHTTP")),
enabled=bool(payload.get("enabled", True)),
status=str(payload.get("status", "unknown")),
passthrough_headers=tuple(
str(item).lower()
for item in payload.get(
"passthroughHeaders", payload.get("passthrough_headers", [])
)
),
)
return external_id, snapshot
def list_gateways(
self, *, cursor: str | None = None, limit: int = 50
) -> tuple[GatewaySnapshot, ...]:
if not 1 <= limit <= 100:
raise ValueError("gateway list limit must be between 1 and 100")
params: dict[str, str | int] = {"limit": limit, "include_pagination": "false"}
if cursor:
params["cursor"] = cursor
value = self._request("GET", "/gateways", params=params, retryable=True)
entities = (
value
if isinstance(value, list)
else value.get("gateways", [])
if isinstance(value, dict)
else []
)
if not isinstance(entities, list):
raise ContextForgeError(
"gateway_invalid_response", "Gateway inventory response was invalid."
)
return tuple(self._snapshot(item)[1] for item in entities if isinstance(item, dict))
def _find(self, slug: str) -> GatewayHandle | None:
value = self._request(
"GET",
"/gateways",
params={"limit": 100, "include_pagination": "false"},
retryable=True,
)
entities = (
value
if isinstance(value, list)
else value.get("gateways", [])
if isinstance(value, dict)
else []
)
for item in entities if isinstance(entities, list) else []:
if isinstance(item, dict) and (item.get("name") or item.get("slug")) == slug:
external_id, snapshot = self._snapshot(item)
return GatewayHandle(external_id=external_id, snapshot=snapshot)
return None
@staticmethod
def _desired_payload(desired: GatewayDesiredState) -> dict[str, Any]:
return {
"name": desired.slug,
"url": str(desired.url),
"description": desired.description,
"transport": desired.transport,
"enabled": desired.enabled,
"passthrough_headers": list(desired.passthrough_headers),
}
def create_gateway(self, desired: GatewayDesiredState) -> GatewayHandle:
value = self._request("POST", "/gateways", json_body=self._desired_payload(desired))
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Gateway create response was invalid."
)
external_id, snapshot = self._snapshot(value)
return GatewayHandle(external_id=external_id, snapshot=snapshot)
def ensure_gateway(self, desired: GatewayDesiredState) -> GatewayHandle:
existing = self._find(desired.slug)
if existing is None:
return self.create_gateway(desired)
# ContextForge can deactivate a gateway after repeated upstream failures
# while leaving its configured ``enabled`` flag set. Treat runtime status
# as part of the desired state so a recovered connector is explicitly
# reactivated instead of retaining a stale, offline tool inventory.
runtime_status = existing.snapshot.status.strip().lower()
if (
existing.snapshot.url != str(desired.url)
or existing.snapshot.description != desired.description
or existing.snapshot.transport != desired.transport
or existing.snapshot.enabled != desired.enabled
or existing.snapshot.passthrough_headers != desired.passthrough_headers
or runtime_status not in {"active", "healthy", "online", "ready"}
):
return self.update_gateway(existing, desired)
return existing
def update_gateway(self, handle: GatewayHandle, desired: GatewayDesiredState) -> GatewayHandle:
value = self._request(
"PUT",
f"/gateways/{handle.external_id}",
json_body=self._desired_payload(desired),
)
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Gateway update response was invalid."
)
external_id, snapshot = self._snapshot(value)
return GatewayHandle(external_id=external_id, snapshot=snapshot)
def delete_gateway(self, handle: GatewayHandle) -> None:
self._request("DELETE", f"/gateways/{handle.external_id}")
def list_gateway_tools(self, handle: GatewayHandle) -> tuple[GatewayTool, ...]:
value = self._request(
"GET",
"/tools",
params={
"gateway_id": handle.external_id,
"limit": 0,
"include_pagination": "false",
},
retryable=True,
)
entities = (
value
if isinstance(value, list)
else value.get("tools", [])
if isinstance(value, dict)
else []
)
tools: list[GatewayTool] = []
for item in entities if isinstance(entities, list) else []:
if not isinstance(item, dict):
continue
external_id = item.get("id")
name = (
item.get("originalName")
or item.get("original_name")
or item.get("customName")
or item.get("name")
)
published_name = item.get("customName") or item.get("custom_name") or item.get("name")
if isinstance(external_id, str) and isinstance(name, str):
if not isinstance(published_name, str):
published_name = name
raw_input_schema = item.get("inputSchema", item.get("input_schema"))
tools.append(
GatewayTool(
external_id=external_id,
name=name,
published_name=published_name,
input_schema=(
raw_input_schema if isinstance(raw_input_schema, dict) else None
),
)
)
return tuple(sorted(tools, key=lambda item: item.name))
def ensure_canonical_tool_names(
self, tools: tuple[GatewayTool, ...]
) -> tuple[GatewayTool, ...]:
"""Publish connector names verbatim instead of ContextForge prefixes.
ContextForge makes discovered tool names globally unique by prefixing the
gateway slug. The Hub catalog and ChatGPT app expose the connector's
canonical MCP names, so leaving that prefix in place makes every routed
``tools/call`` miss even though discovery itself is healthy.
"""
canonical: list[GatewayTool] = []
for tool in tools:
if tool.published_name == tool.name:
canonical.append(tool)
continue
value = self._request(
"PUT", f"/tools/{tool.external_id}", json_body={"customName": tool.name}
)
if not isinstance(value, dict) or value.get("customName") != tool.name:
raise ContextForgeError(
"gateway_invalid_response", "Gateway tool rename was not applied."
)
canonical.append(tool.model_copy(update={"published_name": tool.name}))
return tuple(canonical)
def refresh_gateway(self, handle: GatewayHandle) -> None:
value = self._request(
"POST",
f"/gateways/{handle.external_id}/tools/refresh",
params={"include_resources": "true", "include_prompts": "true"},
)
if not isinstance(value, dict) or value.get("success") is not True:
raise ContextForgeError(
"gateway_invalid_response", "Gateway refresh did not complete successfully."
)
def list_gateway_prompts(self, handle: GatewayHandle) -> tuple[GatewayPrompt, ...]:
value = self._request(
"GET",
"/prompts",
params={
"gateway_id": handle.external_id,
"limit": 0,
"include_pagination": "false",
},
retryable=True,
)
entities = (
value
if isinstance(value, list)
else value.get("prompts", [])
if isinstance(value, dict)
else []
)
prompts: list[GatewayPrompt] = []
for item in entities if isinstance(entities, list) else []:
if not isinstance(item, dict):
continue
external_id = item.get("id")
name = (
item.get("originalName")
or item.get("original_name")
or item.get("customName")
or item.get("name")
)
if isinstance(external_id, str) and isinstance(name, str):
prompts.append(GatewayPrompt(external_id=external_id, name=name))
return tuple(sorted(prompts, key=lambda item: item.name))
@staticmethod
def _server_handle(payload: Mapping[str, Any]) -> VirtualServerHandle:
external_id = payload.get("id")
name = payload.get("name")
tool_ids = payload.get(
"associated_tool_ids",
payload.get("associatedToolIds", payload.get("associated_tools", [])),
)
prompt_ids = payload.get(
"associated_prompt_ids",
payload.get("associatedPromptIds", payload.get("associated_prompts", [])),
)
if (
not isinstance(external_id, str)
or not isinstance(name, str)
or not isinstance(tool_ids, list)
or not isinstance(prompt_ids, list)
):
raise ContextForgeError(
"gateway_invalid_response", "Virtual server response was invalid."
)
normalized = tuple(
str(item.get("id")) if isinstance(item, dict) else str(item) for item in tool_ids
)
normalized_prompts = tuple(
str(item.get("id")) if isinstance(item, dict) else str(item) for item in prompt_ids
)
return VirtualServerHandle(
external_id=external_id,
name=name,
associated_tool_ids=tuple(sorted(normalized)),
associated_prompt_ids=tuple(sorted(normalized_prompts)),
enabled=bool(payload.get("enabled", True)),
visibility=cast(
Literal["public", "private", "team"],
str(payload.get("visibility", "private")),
),
)
def _find_server(self, desired: VirtualServerDesiredState) -> VirtualServerHandle | None:
value = self._request(
"GET",
"/servers",
params={"limit": 0, "include_pagination": "false", "include_inactive": "true"},
retryable=True,
)
entities = (
value
if isinstance(value, list)
else value.get("servers", [])
if isinstance(value, dict)
else []
)
for item in entities if isinstance(entities, list) else []:
if not isinstance(item, dict):
continue
candidate = self._server_handle(item)
try:
same_id = UUID(candidate.external_id) == desired.external_id
except ValueError:
same_id = False
if same_id or candidate.name == desired.name:
return candidate
return None
@staticmethod
def _server_payload(desired: VirtualServerDesiredState, *, include_id: bool) -> dict[str, Any]:
payload: dict[str, Any] = {
"name": desired.name,
"description": desired.description,
"associated_tools": list(desired.associated_tool_ids),
"associated_prompts": list(desired.associated_prompt_ids),
"visibility": desired.visibility,
}
if include_id:
payload["id"] = str(desired.external_id)
return payload
def ensure_virtual_server(self, desired: VirtualServerDesiredState) -> VirtualServerHandle:
existing = self._find_server(desired)
if existing is None:
server_payload = self._server_payload(desired, include_id=True)
value = self._request("POST", "/servers", json_body={"server": server_payload})
elif (
existing.name != desired.name
or existing.associated_tool_ids != desired.associated_tool_ids
or existing.associated_prompt_ids != desired.associated_prompt_ids
or existing.visibility != desired.visibility
):
value = self._request(
"PUT",
f"/servers/{existing.external_id}",
json_body=self._server_payload(desired, include_id=False),
)
else:
return existing
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Virtual server response was invalid."
)
return self._server_handle(value)
def test_gateway(self, handle: GatewayHandle) -> GatewayTestResult:
try:
value = self._request("GET", f"/gateways/{handle.external_id}/test", retryable=True)
except ContextForgeError as exc:
if exc.code == "gateway_not_found":
return GatewayTestResult(
reachable=False,
status="unsupported",
detail="Gateway test endpoint unavailable.",
)
raise
if not isinstance(value, dict):
raise ContextForgeError(
"gateway_invalid_response", "Gateway test response was invalid."
)
return GatewayTestResult(
reachable=bool(value.get("reachable", value.get("status") in {"ok", "healthy"})),
status=str(value.get("status", "unknown")),
detail=str(value.get("detail", ""))[:256],
)
def test_gateway_by_slug(self, slug: str) -> GatewayTestResult:
"""Probe one registered gateway without exposing its external ID."""
handle = self._find(slug)
if handle is None:
return GatewayTestResult(
reachable=False,
status="missing",
detail="Connector gateway is not registered.",
)
return self.test_gateway(handle)
def inventory(self) -> SanitizedGatewayInventory:
capabilities = self.capabilities()
value = self._request(
"GET",
"/gateways",
params={"limit": 100, "include_pagination": "false"},
retryable=True,
)
entities = (
value
if isinstance(value, list)
else value.get("gateways", [])
if isinstance(value, dict)
else []
)
inventory: list[dict[str, Any]] = []
for item in entities if isinstance(entities, list) else []:
if not isinstance(item, dict):
continue
external_id = str(item.get("id", ""))
inventory.append(
{
"slug": str(item.get("name") or item.get("slug") or ""),
"external_id_digest": hashlib.sha256(external_id.encode()).hexdigest(),
"url": str(item.get("url", "")),
"status": str(item.get("status", "unknown")),
"enabled": bool(item.get("enabled", True)),
}
)
return SanitizedGatewayInventory(
generated_at=datetime.now(UTC), capabilities=capabilities, gateways=tuple(inventory)
)
@@ -0,0 +1,41 @@
"""Stable, sanitized gateway adapter errors."""
from __future__ import annotations
class ContextForgeError(Exception):
"""An adapter error safe for logs and public service error mapping."""
def __init__(
self,
code: str,
message: str,
*,
status_code: int | None = None,
retryable: bool = False,
) -> None:
super().__init__(message)
self.code = code
self.status_code = status_code
self.retryable = retryable
@classmethod
def from_status(cls, status_code: int) -> ContextForgeError:
mappings = {
401: ("gateway_unauthorized", "Gateway authorization failed.", False),
403: ("gateway_forbidden", "Gateway authorization was denied.", False),
404: ("gateway_not_found", "Gateway resource was not found.", False),
400: ("gateway_invalid_request", "Gateway request was invalid.", False),
422: ("gateway_invalid_request", "Gateway request was invalid.", False),
409: ("gateway_conflict", "Gateway resource conflicts with existing state.", False),
429: ("gateway_rate_limited", "Gateway rate limit reached.", True),
}
code, message, retryable = mappings.get(
status_code,
(
"gateway_unavailable" if status_code >= 500 else "gateway_request_failed",
"Gateway request failed.",
status_code >= 500,
),
)
return cls(code, message, status_code=status_code, retryable=retryable)
+426
View File
@@ -0,0 +1,426 @@
"""Deterministic in-memory ContextForge-compatible target."""
from __future__ import annotations
import json
from copy import deepcopy
from typing import Any
from uuid import UUID, uuid4
import httpx
class FakeContextForge:
"""Fake REST gateway for offline adapter and reconciliation tests."""
def __init__(self) -> None:
self.gateways: dict[str, dict[str, Any]] = {}
self.tools: dict[str, list[dict[str, Any]]] = {}
self.prompts: dict[str, list[dict[str, Any]]] = {}
self.servers: dict[str, dict[str, Any]] = {}
self.requests: list[tuple[str, str, dict[str, str]]] = []
self.fail_next: int | None = None
_gateway_tools = {
"itworx-stitch": (
"stitch.project.list",
"stitch.project.get",
"stitch.project.create",
"stitch.screen.list",
"stitch.screen.get",
"stitch.screen.generate",
"stitch.screen.edit",
"stitch.screen.variants",
),
"itworx-knowledge": (
"knowledge.document.search",
"knowledge.document.get",
"knowledge.source.list",
"knowledge.index.status",
"knowledge.adr.list",
"knowledge.project.summary",
),
"itworx-gitea": (
"gitea.capabilities.get",
"gitea.connection.check",
"gitea.selftest.run",
"gitea.repository.list",
"gitea.repository.get",
"gitea.repository.context",
"gitea.repository.health",
"gitea.branchprotection.get",
"gitea.branchprotection.preview",
"gitea.branch.list",
"gitea.commit.list",
"gitea.commit.get",
"gitea.ref.compare",
"gitea.release.list",
"gitea.issue.list",
"gitea.issue.search",
"gitea.code.search",
"search",
"fetch",
"gitea.file.read",
"gitea.tree.list",
"gitea.changeset.preview",
"gitea.validation.get",
"gitea.validation.failure.get",
"gitea.validation.list",
"gitea.change.status",
"gitea.change.review",
"gitea.change.reconcile",
"gitea.workspace.status",
"gitea.portfolio.health",
"gitea.portfolio.governance.preview",
"gitea.portfolio.governance.verify",
"gitea.orchestration.plan",
"gitea.pullrequest.list",
"gitea.pullrequest.get",
"gitea.pullrequest.feedback",
"gitea.commitstatus.list",
"gitea.repository.create",
"gitea.branchprotection.ensure",
"gitea.portfolio.governance.apply",
"gitea.branch.create",
"gitea.issue.create",
"gitea.issue.update",
"gitea.issue.comment.create",
"gitea.file.create",
"gitea.file.update",
"gitea.file.delete",
"gitea.pullrequest.create",
"gitea.pullrequest.update",
"gitea.pullrequest.close",
"gitea.pullrequest.validation.ensure",
"gitea.pullrequest.sync",
"gitea.pullrequest.merge",
"gitea.branch.delete",
"gitea.changeset.apply",
"gitea.validation.run",
"gitea.workspace.run",
"gitea.orchestration.start",
),
"itworx-unraid": (
"unraid.host.status",
"unraid.storage.list",
"unraid.network.list",
"unraid.container.list",
"unraid.container.inspect",
"unraid.container.health",
"unraid.container.logs",
"unraid.project.summary",
),
"itworx-mobilityops": (
"mobilityops.operations.summary",
"mobilityops.attention.list",
"mobilityops.vehicle.get",
"mobilityops.knowledge.search",
),
"itworx-projectbrain": (
"projectbrain.project.list",
"projectbrain.repository.list",
"projectbrain.portfolio.get",
"projectbrain.portfolio.repository.list",
"projectbrain.portfolio.repository.reconcile",
"projectbrain.portfolio.repository.exclusion.set",
"projectbrain.repository.search",
"projectbrain.repository.fetch",
"projectbrain.context.get",
"projectbrain.workspace.render",
"projectbrain.modules.get",
"projectbrain.brainstorm.save",
"projectbrain.knowledge.save",
"projectbrain.documentation.save",
"projectbrain.memory.save",
"projectbrain.contextpack.create",
"projectbrain.feedback.record",
"projectbrain.delivery.list",
"projectbrain.delivery.get",
"projectbrain.delivery.create",
"projectbrain.delivery.update",
"projectbrain.repository.tree",
"projectbrain.repository.file",
"projectbrain.repository.branches",
"projectbrain.repository.pullrequests",
"projectbrain.delivery.start",
"projectbrain.delivery.file.write",
"projectbrain.delivery.file.delete",
"projectbrain.delivery.changeset.apply",
"projectbrain.delivery.validation.record",
"projectbrain.delivery.validation.runs",
"projectbrain.delivery.validation.sync",
"projectbrain.delivery.complete",
"projectbrain.delivery.merge",
"projectbrain.analysis.start",
"projectbrain.analysis.list",
"projectbrain.analysis.status",
"projectbrain.analysis.results",
"projectbrain.analysis.cancel",
"projectbrain.analysis.render",
"projectbrain.finding.list",
"projectbrain.finding.get",
"projectbrain.finding.issue.sync",
"projectbrain.finding.investigation.start",
"projectbrain.finding.investigation.get",
"projectbrain.finding.investigation.save",
"projectbrain.finding.suppress",
"projectbrain.finding.delivery.create",
"projectbrain.finding.verify",
"projectbrain.autonomy.projects.list",
"projectbrain.autonomy.executor.heartbeat",
"projectbrain.autonomy.state.get",
"projectbrain.autonomy.configure",
"projectbrain.autonomy.roadmap.import",
"projectbrain.autonomy.resume",
"projectbrain.autonomy.work.claim",
"projectbrain.autonomy.work.report",
),
"itworx-appops": (
"server.summary.get",
"server.application.list",
"server.container.list",
"server.safety.findings",
"server.action.list",
"server.operation.preview",
"server.audit.search",
"server.knowledge.query",
"server.operation.request",
"server.operation.status",
"server.operation.execute",
"server.job.status",
"server.job.cancel",
"server.ci.runners.status",
"server.project.list",
"server.project.reconcile",
"server.deployment.preflight",
"server.deployment.capabilities",
"server.deployment.enrollment.preflight",
"server.deployment.target.enroll",
"server.deployment.enrollment.status",
"server.deployment.apply",
"server.deployment.status",
"server.deployment.verify",
"server.deployment.rollback",
),
"itworx-opencode": (
"opencode.capabilities.get",
"opencode.models.list",
"opencode.workspace.ensure",
"opencode.job.preview",
"opencode.job.start",
"opencode.jobs.list",
"opencode.job.status",
"opencode.job.result",
"opencode.job.cancel",
"opencode.delivery.control.repositories.list",
"opencode.delivery.control.plan",
"opencode.delivery.control.start",
"opencode.delivery.control.jobs.list",
"opencode.delivery.control.status",
"opencode.delivery.control.result",
"opencode.delivery.control.retry",
"opencode.delivery.control.cancel",
),
"itworx-opencode-readonly": (
"opencode.analysis.capabilities.get",
"opencode.analysis.models.list",
"opencode.analysis.start",
"opencode.analysis.jobs.list",
"opencode.analysis.status",
"opencode.analysis.result",
"opencode.delivery.repositories.list",
"opencode.delivery.jobs.list",
"opencode.delivery.status",
"opencode.delivery.result",
),
}
_gateway_prompts = {
"itworx-knowledge": ("project-context-pack", "pre-change-impact-review"),
"itworx-gitea": (
"repository-engineer",
"repository-change-review",
"repository-maintenance",
"portfolio-engineer",
"release-readiness",
),
"itworx-unraid": ("production-incident-triage", "deployment-health-review"),
"itworx-mobilityops": ("fleet-operations-morning-brief",),
}
@staticmethod
def _tool_payload(tool_name: str) -> dict[str, Any]:
payload: dict[str, Any] = {"id": str(uuid4()), "name": tool_name}
if tool_name == "gitea.repository.create":
payload["inputSchema"] = {
"type": "object",
"properties": {
field: {"type": field_type}
for field, field_type in (
("name", "string"),
("description", "string"),
("private", "boolean"),
("autoInit", "boolean"),
("defaultBranch", "string"),
)
},
"required": ["name", "description", "private", "autoInit", "defaultBranch"],
}
payload["inputSchema"]["properties"]["autoInit"]["const"] = True
return payload
def transport(self) -> httpx.MockTransport:
return httpx.MockTransport(self._handle)
def _response(self, request: httpx.Request, status: int, payload: Any) -> httpx.Response:
return httpx.Response(status, json=payload, request=request)
def _handle(self, request: httpx.Request) -> httpx.Response:
self.requests.append(
(
request.method,
request.url.path,
{
key: "<redacted>" if key.lower() == "authorization" else value
for key, value in request.headers.items()
},
)
)
if self.fail_next is not None:
status = self.fail_next
self.fail_next = None
return self._response(request, status, {"error": "simulated"})
path = request.url.path
if path.startswith("/v1/"):
path = path[3:]
if request.method == "GET" and path == "/health":
return self._response(request, 200, {"status": "healthy", "version": "1.0.6"})
if request.method == "GET" and path == "/ready":
return self._response(request, 200, {"status": "ready"})
if request.method == "GET" and path == "/version":
return self._response(request, 200, {"version": "1.0.6"})
if path == "/gateways":
if request.method == "GET":
return self._response(
request, 200, {"gateways": list(self.gateways.values()), "nextCursor": None}
)
if request.method == "POST":
payload = json.loads(request.content)
name = str(payload["name"])
if any(item["name"] == name for item in self.gateways.values()):
return self._response(request, 409, {"error": "conflict"})
gateway = {
"id": f"gateway-{uuid4().hex}",
"name": name,
"url": payload["url"],
"description": payload.get("description", ""),
"transport": payload.get("transport", "STREAMABLEHTTP"),
"enabled": payload.get("enabled", True),
"status": "active",
"passthrough_headers": payload.get("passthrough_headers", []),
}
self.gateways[gateway["id"]] = gateway
self.tools[gateway["id"]] = [
self._tool_payload(tool_name) for tool_name in self._gateway_tools.get(name, ())
]
self.prompts[gateway["id"]] = [
{"id": str(uuid4()), "name": prompt_name}
for prompt_name in self._gateway_prompts.get(name, ())
]
return self._response(request, 201, deepcopy(gateway))
if path == "/tools" and request.method == "GET":
gateway_id = request.url.params.get("gateway_id", "")
return self._response(request, 200, deepcopy(self.tools.get(gateway_id, [])))
if path == "/prompts" and request.method == "GET":
gateway_id = request.url.params.get("gateway_id", "")
return self._response(request, 200, deepcopy(self.prompts.get(gateway_id, [])))
if path == "/servers":
if request.method == "GET":
return self._response(request, 200, deepcopy(list(self.servers.values())))
if request.method == "POST":
payload = json.loads(request.content)
payload = payload.get("server", payload)
external_id = UUID(str(payload["id"])).hex if payload.get("id") else uuid4().hex
server = {
"id": external_id,
"name": payload["name"],
"associated_tool_ids": payload.get("associated_tools", []),
"associated_prompt_ids": payload.get("associated_prompts", []),
"enabled": True,
"visibility": payload.get("visibility", "private"),
}
self.servers[external_id] = server
return self._response(request, 201, deepcopy(server))
parts = path.strip("/").split("/")
if len(parts) == 2 and parts[0] == "tools" and request.method == "PUT":
payload = json.loads(request.content)
for gateway_tools in self.tools.values():
tool = next((item for item in gateway_tools if item.get("id") == parts[1]), None)
if tool is not None:
if "customName" in payload:
tool["customName"] = payload["customName"]
return self._response(request, 200, deepcopy(tool))
return self._response(request, 404, {"error": "not_found"})
if len(parts) >= 2 and parts[0] == "gateways":
stored = self.gateways.get(parts[1])
if stored is None:
return self._response(request, 404, {"error": "not_found"})
if len(parts) == 3 and parts[2] == "test" and request.method == "GET":
return self._response(request, 200, {"reachable": True, "status": "healthy"})
if len(parts) == 4 and parts[2:] == ["tools", "refresh"] and request.method == "POST":
old_tools = len(self.tools.get(parts[1], []))
old_prompts = len(self.prompts.get(parts[1], []))
self.tools[parts[1]] = [
self._tool_payload(tool_name)
for tool_name in self._gateway_tools.get(str(stored["name"]), ())
]
self.prompts[parts[1]] = [
{"id": str(uuid4()), "name": prompt_name}
for prompt_name in self._gateway_prompts.get(str(stored["name"]), ())
]
return self._response(
request,
200,
{
"success": True,
"tools_added": max(0, len(self.tools[parts[1]]) - old_tools),
"tools_removed": 0,
"prompts_added": max(0, len(self.prompts[parts[1]]) - old_prompts),
"prompts_removed": 0,
},
)
if request.method == "GET":
return self._response(request, 200, deepcopy(stored))
if request.method == "PUT":
payload = json.loads(request.content)
stored.update(
{
"name": payload["name"],
"url": payload["url"],
"description": payload.get("description", ""),
"transport": payload.get("transport", "STREAMABLEHTTP"),
"enabled": payload.get("enabled", True),
"status": "active" if payload.get("enabled", True) else "inactive",
"passthrough_headers": payload.get("passthrough_headers", []),
}
)
return self._response(request, 200, deepcopy(stored))
if request.method == "DELETE":
del self.gateways[parts[1]]
return self._response(request, 204, {})
if len(parts) == 2 and parts[0] == "servers":
stored_server = self.servers.get(parts[1])
if stored_server is None:
return self._response(request, 404, {"error": "not_found"})
if request.method == "PUT":
payload = json.loads(request.content)
stored_server.update(
{
"name": payload["name"],
"associated_tool_ids": payload.get("associated_tools", []),
"associated_prompt_ids": payload.get("associated_prompts", []),
"visibility": payload.get("visibility", "private"),
}
)
return self._response(request, 200, deepcopy(stored_server))
return self._response(request, 404, {"error": "not_found"})
+234
View File
@@ -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)