from pathlib import Path from typing import Iterable from docforge.models import Project, Module, DocObject class MCPRenderer: """ Renderer that emits documentation as MCP resources. """ name = "mcp" def generate_sources(self, project: Project, out_dir: Path) -> None: """ Generate MCP-compatible resources for the project. Each module is rendered as a standalone MCP document. """ out_dir.mkdir(parents=True, exist_ok=True) for module in project.get_all_modules(): self._write_module(module, out_dir) def _write_module(self, module: Module, out_dir: Path) -> None: """ Render a module and all contained objects. """ resource_path = self._module_resource_path(module) content = self._render_module(module) file_path = out_dir / resource_path file_path.parent.mkdir(parents=True, exist_ok=True) file_path.write_text(content, encoding="utf-8") def _render_module(self, module: Module) -> str: """ Render a module into MCP-friendly Markdown. """ lines: list[str] = [] lines.append(f"# Module `{module.path}`\n") if module.docstring: lines.append(module.docstring.strip() + "\n") for obj in module.get_all_objects(): lines.extend(self._render_object(obj, level=2)) return "\n".join(lines).strip() + "\n" def _render_object(self, obj: DocObject, level: int) -> Iterable[str]: """ Recursively render DocObjects. """ prefix = "#" * level lines: list[str] = [] lines.append(f"{prefix} {obj.kind} `{obj.name}`") if obj.signature: lines.append(f"```python\n{obj.signature}\n```") if obj.docstring: lines.append(obj.docstring.strip()) for member in obj.get_all_members(): lines.extend(self._render_object(member, level + 1)) return lines def _module_resource_path(self, module: Module) -> Path: """ Convert a module path into an MCP resource path. Example: docforge.models.module -> docforge/models/module.md """ return Path(module.path.replace(".", "/") + ".md")