"""Documentation synchronization checks for MCP tool catalog.""" from __future__ import annotations import re from pathlib import Path from mcp_server.tools import DocsPath, tool_catalog _REFERENCE_DOC = DocsPath / "reference" / "mcp-server.md" def check_catalog_parity() -> dict[str, object]: """Compare documented anchors with the live tool catalog and report drift.""" missing_in_docs: list[str] = [] missing_in_catalog: list[str] = [] mismatches: list[dict[str, str]] = [] docs_tools = _doc_tool_anchors() catalog = tool_catalog() catalog_names = {tool.name for tool in catalog} for tool in catalog: if not _anchor_exists(tool.docs_anchor.path, tool.docs_anchor.anchor): missing_in_docs.append(tool.name) for anchor_key, tool_name in docs_tools.items(): if tool_name not in catalog_names: missing_in_catalog.append(anchor_key) return { "status": ( "ok" if not missing_in_docs and not missing_in_catalog and not mismatches else "drift_detected" ), "missingInDocs": missing_in_docs, "missingInCatalog": missing_in_catalog, "mismatches": mismatches, } def _doc_tool_anchors() -> dict[str, str]: """Derive documented tool anchors from the MCP reference page.""" anchors: dict[str, str] = {} for heading in _heading_texts(_REFERENCE_DOC, prefix="### "): tool_name = heading anchor_id = _slugify_heading(heading) anchors[_anchor_key(_REFERENCE_DOC, anchor_id)] = tool_name return anchors def _anchor_exists(path: Path, anchor: str) -> bool: """Check whether a heading anchor exists in the target doc.""" if not path.exists() or path.is_dir(): return False return anchor in _heading_anchors(path) def _heading_anchors(path: Path) -> set[str]: """Collect slugified heading anchors from a markdown file.""" anchors: set[str] = set() for heading in _heading_texts(path, prefix="#"): anchors.add(_slugify_heading(heading)) return anchors def _heading_texts(path: Path, prefix: str) -> list[str]: """Return heading text lines matching a prefix.""" if not path.exists(): return [] headings: list[str] = [] for line in path.read_text().splitlines(): if not line.startswith(prefix): continue heading = line.lstrip("#").strip() if heading: headings.append(heading) return headings def _slugify_heading(text: str) -> str: """Normalize heading text to a GitHub-style anchor.""" slug = text.strip().lower() slug = re.sub(r"[^a-z0-9\s-]", " ", slug) slug = re.sub(r"\s+", "-", slug) return re.sub(r"-+", "-", slug).strip("-") def _anchor_key(path: Path, anchor: str) -> str: """Build a stable key for an anchor path pair.""" return f"{path}#{anchor}"