import json from pathlib import Path from typing import Dict, List from docforge.models import Project, Module, DocObject class MCPRenderer: """ Renderer that generates MCP-compatible documentation resources. This renderer converts doc-forge project models into structured JSON resources suitable for consumption by systems implementing the Model Context Protocol (MCP). """ name = "mcp" def generate_sources(self, project: Project, out_dir: Path) -> None: """ Generate MCP documentation resources for a project. The renderer serializes each module into a JSON resource and produces supporting metadata files such as ``nav.json`` and ``index.json``. Args: project: Documentation project model to render. out_dir: Directory where MCP resources will be written. """ modules_dir = out_dir / "modules" modules_dir.mkdir(parents=True, exist_ok=True) nav: List[Dict[str, str]] = [] for module in project.get_all_modules(): self._write_module(module, modules_dir) nav.append({ "module": module.path, "resource": f"doc://modules/{module.path}", }) # Write nav.json (out_dir / "nav.json").write_text( self._json(nav), encoding="utf-8", ) # Write index.json index = { "project": project.name, "type": "docforge-model", "modules_count": len(nav), "source": "docforge", } (out_dir / "index.json").write_text( self._json(index), encoding="utf-8", ) def _write_module(self, module: Module, modules_dir: Path) -> None: """ Serialize a module into an MCP resource file. Args: module: Module instance to serialize. modules_dir: Directory where module JSON files are stored. """ payload = { "module": module.path, "content": self._render_module(module), } out = modules_dir / f"{module.path}.json" out.parent.mkdir(parents=True, exist_ok=True) out.write_text(self._json(payload), encoding="utf-8") def _render_module(self, module: Module) -> Dict: """ Convert a Module model into MCP-compatible structured data. Args: module: Module instance to convert. Returns: Dictionary representing the module and its documented objects. """ data: Dict = { "path": module.path, "docstring": module.docstring, "objects": {}, } for obj in module.get_all_objects(): data["objects"][obj.name] = self._render_object(obj) return data def _render_object(self, obj: DocObject) -> Dict: """ Recursively convert a DocObject into structured MCP data. Args: obj: Documentation object to convert. Returns: Dictionary describing the object and any nested members. """ data: Dict = { "name": obj.name, "kind": obj.kind, "path": obj.path, "signature": obj.signature, "docstring": obj.docstring, } members = list(obj.get_all_members()) if members: data["members"] = { member.name: self._render_object(member) for member in members } return data @staticmethod def _json(data: Dict) -> str: """ Serialize data to formatted JSON. Args: data: Dictionary to serialize. Returns: JSON string formatted with indentation and UTF-8 compatibility. """ return json.dumps(data, indent=2, ensure_ascii=False)