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,262 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml # type: ignore[import-untyped]
|
||||
|
||||
from services.control_api.contextforge import ContextForgeClient, ContextForgeSettings
|
||||
from services.control_api.contextforge.models import VirtualServerDesiredState
|
||||
from services.runtime.common import deterministic_id
|
||||
|
||||
PROJECT_SLUG = "gitea-all"
|
||||
ENVIRONMENT = "prod"
|
||||
GATEWAY_SLUG = "itworx-gitea"
|
||||
SERVER_NAME = "gitea-all-prod-readonly"
|
||||
REQUIRED_RECOVERY_TOOL = "gitea.pullrequest.sync"
|
||||
RECONCILE_ATTEMPTS = 4
|
||||
RECONCILE_DELAY_SECONDS = 2.0
|
||||
|
||||
|
||||
def _entities(value: Any, key: str) -> list[dict[str, Any]]:
|
||||
if isinstance(value, list):
|
||||
return [item for item in value if isinstance(item, dict)]
|
||||
if isinstance(value, dict) and isinstance(value.get(key), list):
|
||||
return [item for item in value[key] if isinstance(item, dict)]
|
||||
return []
|
||||
|
||||
|
||||
def _published_tool_name(item: dict[str, Any]) -> str:
|
||||
# ContextForge clients invoke customName when present. Prefer the actual
|
||||
# published name over originalName so the canary validates the same surface
|
||||
# ChatGPT sees rather than the gateway's import-time identifier.
|
||||
for key in ("customName", "custom_name", "name", "originalName", "original_name"):
|
||||
value = item.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
return ""
|
||||
|
||||
|
||||
def _published_prompt_name(item: dict[str, Any]) -> str:
|
||||
for key in ("customName", "custom_name", "name", "originalName", "original_name"):
|
||||
value = item.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
return ""
|
||||
|
||||
|
||||
def _load_surface(catalog_root: Path) -> tuple[list[str], list[str]]:
|
||||
document = yaml.safe_load(
|
||||
(catalog_root / "projects" / f"{PROJECT_SLUG}.yaml").read_text(encoding="utf-8")
|
||||
)
|
||||
if not isinstance(document, dict) or not isinstance(document.get("spec"), dict):
|
||||
raise RuntimeError("gitea-all catalog document is invalid")
|
||||
servers = document["spec"].get("virtualServers", [])
|
||||
server = next(
|
||||
(
|
||||
item
|
||||
for item in servers
|
||||
if isinstance(item, dict)
|
||||
and item.get("slug") == SERVER_NAME
|
||||
and item.get("environment") == ENVIRONMENT
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(server, dict):
|
||||
raise RuntimeError("gitea-all production virtual server is missing")
|
||||
tools = server.get("toolAllowPatterns", [])
|
||||
prompts = server.get("promptAllowPatterns", [])
|
||||
if not isinstance(tools, list) or not all(isinstance(item, str) for item in tools):
|
||||
raise RuntimeError("gitea-all tool surface is invalid")
|
||||
if not isinstance(prompts, list) or not all(isinstance(item, str) for item in prompts):
|
||||
raise RuntimeError("gitea-all prompt surface is invalid")
|
||||
if any(any(token in item for token in ("*", "?", "[")) for item in tools):
|
||||
raise RuntimeError("gitea-all post-deploy surface must remain explicit")
|
||||
if REQUIRED_RECOVERY_TOOL not in tools:
|
||||
raise RuntimeError("gitea pull-request recovery tool is not published")
|
||||
return list(tools), list(prompts)
|
||||
|
||||
|
||||
def _global_tools(client: ContextForgeClient) -> list[dict[str, Any]]:
|
||||
value = client._request( # noqa: SLF001 - fixed ContextForge inventory endpoint
|
||||
"GET",
|
||||
"/tools",
|
||||
params={"limit": 0, "include_pagination": "false"},
|
||||
retryable=True,
|
||||
)
|
||||
return _entities(value, "tools")
|
||||
|
||||
|
||||
def _global_prompts(client: ContextForgeClient) -> list[dict[str, Any]]:
|
||||
value = client._request( # noqa: SLF001 - fixed ContextForge inventory endpoint
|
||||
"GET",
|
||||
"/prompts",
|
||||
params={"limit": 0, "include_pagination": "false"},
|
||||
retryable=True,
|
||||
)
|
||||
return _entities(value, "prompts")
|
||||
|
||||
|
||||
def _select_tool_ids(tools: list[dict[str, Any]], names: list[str]) -> tuple[str, ...]:
|
||||
by_name: dict[str, set[str]] = {}
|
||||
for item in tools:
|
||||
external_id = item.get("id")
|
||||
name = _published_tool_name(item)
|
||||
if not isinstance(external_id, str) or not external_id or not name:
|
||||
continue
|
||||
by_name.setdefault(name, set()).add(external_id)
|
||||
|
||||
missing = sorted(set(names) - by_name.keys())
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
"ContextForge global inventory is missing required published tools: "
|
||||
+ ", ".join(missing[:8])
|
||||
)
|
||||
ambiguous = sorted(name for name in names if len(by_name.get(name, set())) != 1)
|
||||
if ambiguous:
|
||||
raise RuntimeError(
|
||||
"ContextForge global inventory has ambiguous published tools: "
|
||||
+ ", ".join(ambiguous[:8])
|
||||
)
|
||||
return tuple(sorted(next(iter(by_name[name])) for name in names))
|
||||
|
||||
|
||||
def _select_prompt_ids(prompts: list[dict[str, Any]], patterns: list[str]) -> tuple[str, ...]:
|
||||
selected: set[str] = set()
|
||||
for item in prompts:
|
||||
external_id = item.get("id")
|
||||
name = _published_prompt_name(item)
|
||||
if not isinstance(external_id, str) or not external_id or not name:
|
||||
continue
|
||||
if any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns):
|
||||
selected.add(external_id)
|
||||
return tuple(sorted(selected))
|
||||
|
||||
|
||||
def _live_server_tools(client: ContextForgeClient, external_id: str) -> list[dict[str, Any]]:
|
||||
value = client._request( # noqa: SLF001 - fixed managed virtual-server verifier
|
||||
"GET",
|
||||
f"/servers/{external_id}/tools",
|
||||
params={"include_pagination": "false"},
|
||||
retryable=True,
|
||||
)
|
||||
return _entities(value, "tools")
|
||||
|
||||
|
||||
def _desired_state(
|
||||
client: ContextForgeClient,
|
||||
catalog_root: Path,
|
||||
) -> tuple[VirtualServerDesiredState, set[str]]:
|
||||
required_tools, prompt_patterns = _load_surface(catalog_root)
|
||||
gateway = client._find(GATEWAY_SLUG) # noqa: SLF001 - fixed managed gateway only
|
||||
if gateway is None:
|
||||
raise RuntimeError("managed Gitea ContextForge gateway is missing")
|
||||
|
||||
# Canonicalize the managed Gitea gateway first. Then select the complete
|
||||
# virtual-server surface from ContextForge's global registry so tools from
|
||||
# other managed gateways (for example project.status.get) are preserved.
|
||||
client.ensure_canonical_tool_names(client.list_gateway_tools(gateway))
|
||||
tools = _global_tools(client)
|
||||
try:
|
||||
selected_tool_ids = _select_tool_ids(tools, required_tools)
|
||||
except RuntimeError:
|
||||
# One bounded refresh repairs stale Gitea discovery without repeatedly
|
||||
# mutating ContextForge on a deterministic catalog error.
|
||||
client.refresh_gateway(gateway)
|
||||
client.ensure_canonical_tool_names(client.list_gateway_tools(gateway))
|
||||
tools = _global_tools(client)
|
||||
selected_tool_ids = _select_tool_ids(tools, required_tools)
|
||||
|
||||
selected_prompt_ids = _select_prompt_ids(_global_prompts(client), prompt_patterns)
|
||||
desired = VirtualServerDesiredState(
|
||||
external_id=deterministic_id("virtual-server", f"{PROJECT_SLUG}/{ENVIRONMENT}"),
|
||||
name=SERVER_NAME,
|
||||
description="ITWorx managed project and environment scoped MCP server",
|
||||
associated_tool_ids=selected_tool_ids,
|
||||
associated_prompt_ids=selected_prompt_ids,
|
||||
)
|
||||
return desired, set(required_tools)
|
||||
|
||||
|
||||
def _reconcile_once(
|
||||
client: ContextForgeClient,
|
||||
catalog_root: Path,
|
||||
) -> dict[str, object]:
|
||||
desired, required_tools = _desired_state(client, catalog_root)
|
||||
server = client.ensure_virtual_server(desired)
|
||||
|
||||
# ContextForge v1.0.6 may return camelCase association fields. Re-apply the
|
||||
# exact desired state and verify the server-facing tools endpoint rather than
|
||||
# trusting the shape of a successful update response.
|
||||
client._request( # noqa: SLF001 - fixed server, exact catalog-derived associations
|
||||
"PUT",
|
||||
f"/servers/{server.external_id}",
|
||||
json_body={
|
||||
"name": desired.name,
|
||||
"description": desired.description,
|
||||
"associated_tools": list(desired.associated_tool_ids),
|
||||
"associated_prompts": list(desired.associated_prompt_ids),
|
||||
"visibility": desired.visibility,
|
||||
},
|
||||
)
|
||||
|
||||
live_tools = _live_server_tools(client, server.external_id)
|
||||
live_ids = tuple(sorted(str(item.get("id", "")) for item in live_tools if item.get("id")))
|
||||
live_names = {_published_tool_name(item) for item in live_tools if _published_tool_name(item)}
|
||||
if live_ids != desired.associated_tool_ids:
|
||||
raise RuntimeError("ContextForge virtual-server tool association read-back mismatch")
|
||||
missing_live = sorted(required_tools - live_names)
|
||||
if missing_live:
|
||||
raise RuntimeError(
|
||||
"ContextForge virtual server is missing required published tools: "
|
||||
+ ", ".join(missing_live[:8])
|
||||
)
|
||||
if REQUIRED_RECOVERY_TOOL not in live_names:
|
||||
raise RuntimeError("Gitea pull-request recovery tool is not live")
|
||||
|
||||
return {
|
||||
"status": "pass",
|
||||
"server": SERVER_NAME,
|
||||
"toolCount": len(live_ids),
|
||||
"promptCount": len(desired.associated_prompt_ids),
|
||||
"pullRequestSyncLive": True,
|
||||
}
|
||||
|
||||
|
||||
def reconcile_gitea_public_surface(
|
||||
client: ContextForgeClient,
|
||||
catalog_root: Path,
|
||||
*,
|
||||
attempts: int = RECONCILE_ATTEMPTS,
|
||||
delay_seconds: float = RECONCILE_DELAY_SECONDS,
|
||||
) -> dict[str, object]:
|
||||
if not 1 <= attempts <= 6:
|
||||
raise ValueError("reconcile attempts must be between 1 and 6")
|
||||
last_error: RuntimeError | None = None
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
return _reconcile_once(client, catalog_root)
|
||||
except RuntimeError as exc:
|
||||
last_error = exc
|
||||
if attempt + 1 < attempts:
|
||||
time.sleep(delay_seconds)
|
||||
assert last_error is not None
|
||||
raise last_error
|
||||
|
||||
|
||||
def main() -> None:
|
||||
catalog_root = Path(os.environ.get("HUB_CATALOG_ROOT", "/opt/hub/catalog")).resolve()
|
||||
client = ContextForgeClient(ContextForgeSettings.from_env())
|
||||
try:
|
||||
result = reconcile_gitea_public_surface(client, catalog_root)
|
||||
finally:
|
||||
client.close()
|
||||
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user