import json from pathlib import Path from typing import Dict, List from docforge.models import Project, Module, DocObject class MCPRenderer: """ Renderer that emits MCP-native JSON resources from docforge models. """ name = "mcp" def generate_sources(self, project: Project, out_dir: Path) -> None: """ Generate MCP-compatible JSON resources and navigation for the project. """ 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"mcp://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 JSON resource. """ 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: """ Render a Module into MCP-friendly structured data. """ 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 render a DocObject into structured MCP data. """ 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: return json.dumps(data, indent=2, ensure_ascii=False)