from __future__ import annotations from dataclasses import dataclass from pathlib import Path import yaml @dataclass(frozen=True) class MCPBundle: root: Path name: str port: int @dataclass(frozen=True) class RepoDoc: name: str description: str | None lib: str | None api: str | None wiki: str | None mcp_bundle: str | None # source-relative path in the repo (collect use) mcp_server: str | None # MCP server / namespace name mcp_port: int | None @dataclass(frozen=True) class ServiceConfig: name: str html_port: int health_port: int repos: tuple[RepoDoc, ...] @property def mcp_servers(self) -> dict[str, MCPBundle]: """Return only repos that expose an MCP server.""" return { r.name: MCPBundle(root=Path("mcp") / r.name, name=r.mcp_server, port=r.mcp_port) for r in self.repos if r.mcp_port is not None } def load_config(path: Path | str = "config.yml") -> ServiceConfig: p = Path(path) data = yaml.safe_load(p.read_text(encoding="utf-8")) service = data.get("service") or {} repos: list[RepoDoc] = [] for entry in data.get("repos", []): docs = entry.get("docs") or {} mcp = docs.get("mcp") repos.append(RepoDoc( name=entry["name"], description=entry.get("description"), lib=docs.get("lib"), api=docs.get("api"), wiki=docs.get("wiki"), mcp_bundle=mcp["bundle"] if mcp else None, mcp_server=mcp["server"] if mcp else None, mcp_port=mcp["port"] if mcp else None, )) return ServiceConfig( name=service.get("name", "aetos-docs"), html_port=service.get("html_port", 9000), health_port=service.get("health_port", 8000), repos=tuple(repos), )