"""Runnable, read-only MCP binding for fixed in-repository reference adapters.""" from __future__ import annotations import argparse import importlib from pathlib import Path from typing import cast from mcp.server.fastmcp import FastMCP from ._version import __version__ from .adapter_sdk import ( AdapterImplementation, AdapterLoader, AdapterProject, AdapterProjectSettings, ) from .errors import DocForgeError from .mcp_server import create_read_only_server from .reference_config import ReferenceAdapterConfigV1, load_reference_adapter_config REFERENCE_MCP_MODULE = "docforge.reference_mcp" _FIXED_PROVIDERS = { "python": ("docforge.adapters.python", "PythonReferenceAdapter"), "javascript": ("docforge.adapters.javascript", "JavaScriptReferenceAdapter"), "typescript": ("docforge.adapters.javascript", "JavaScriptReferenceAdapter"), "cpp": ("docforge.adapters.cpp", "CppReferenceAdapter"), } __all__ = [ "REFERENCE_MCP_MODULE", "create_reference_project", "create_reference_server", "main", ] def create_reference_project(project_root: str | Path) -> AdapterProject: """Construct one configured adapter project from fixed internal providers.""" config = load_reference_adapter_config(project_root) return _project_from_config(config) def create_reference_server( project_root: str | Path, *, no_ast: bool = False, diagnostics: bool = False, capability_mode: str | None = None, manual_projection_policy: str | None = None, portable_graph_policy: str | None = None, live_viewer_policy: str | None = None, ) -> FastMCP: """Create the fixed read-only MCP surface for one reference project.""" config = load_reference_adapter_config(project_root) project = _project_from_config(config) return create_read_only_server( project, binding_metadata={ "server_module": REFERENCE_MCP_MODULE, "adapter_mode": "reference", "reference_language": config.language, "reference_config_hash": config.config_hash, }, no_ast=no_ast, diagnostics=diagnostics, capability_mode=capability_mode, manual_projection_policy=manual_projection_policy, portable_graph_policy=portable_graph_policy, live_viewer_policy=live_viewer_policy, ) def _project_from_config(config: ReferenceAdapterConfigV1) -> AdapterProject: loader = _adapter_loader(config) cache_root = config.project_root / ".docforge" / "cache" / "reference-adapter" / config.language return AdapterProject( loader, cache_root=cache_root, settings=AdapterProjectSettings( descriptor_path=config.config_path, implementation=AdapterImplementation(files=(config.config_path,)), ), ) def _adapter_loader(config: ReferenceAdapterConfigV1) -> AdapterLoader: module_name, class_name = _FIXED_PROVIDERS[config.language] try: module = importlib.import_module(module_name) except ModuleNotFoundError as error: if error.name != module_name: raise DocForgeError( "optional_dependency_missing", "The selected reference adapter dependency is not installed", language=config.language, missing_module=error.name, install=_install_target(config.language), ) from error raise DocForgeError( "reference_adapter_unavailable", "The selected fixed reference adapter is not available", language=config.language, ) from error constructor = getattr(module, class_name, None) if not callable(constructor): raise DocForgeError( "reference_adapter_unavailable", "The selected fixed reference adapter is not available", language=config.language, ) arguments: dict[str, object] = { "source_roots": tuple( path.relative_to(config.project_root).as_posix() for path in config.source_roots ), "project_id": config.project_id, "title": config.title, } if config.language == "cpp": if config.compilation_database is None: raise DocForgeError( "invalid_reference_config", "C++ reference adapters require compilation_database", ) arguments["compilation_database"] = config.compilation_database.relative_to( config.project_root ).as_posix() loader = constructor(config.project_root, **arguments) if not callable(getattr(loader, "load_projection", None)): raise DocForgeError( "invalid_adapter", "The selected fixed provider does not implement the adapter contract", language=config.language, ) return cast(AdapterLoader, loader) def _install_target(language: str) -> str: if language in {"javascript", "typescript"}: return f"docforge[{language}]" if language == "cpp": return "docforge[cpp]" return "docforge" def main() -> None: """Run the fixed reference binding over stdio.""" parser = argparse.ArgumentParser(prog="python -m docforge.reference_mcp") parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--project-root", type=Path, required=True) parser.add_argument( "--no-ast", action="store_true", help=( "Preserve the existing adapter and forbid AST, Tree-sitter, compiler-AST, " "and function-Logic extraction changes" ), ) parser.add_argument( "--diagnostics", action="store_true", help="Attach bounded request-local stage timings and counters", ) parser.add_argument( "--capability-mode", choices=("read",), help="Expose the fixed read-only project-bound capability surface", ) parser.add_argument( "--manual-render-policy", choices=("auto", "explicit", "disabled"), ) parser.add_argument( "--portable-graph-policy", choices=("explicit", "disabled"), ) parser.add_argument( "--live-viewer-policy", choices=("on-demand", "disabled"), ) arguments = parser.parse_args() create_reference_server( arguments.project_root, no_ast=arguments.no_ast, diagnostics=arguments.diagnostics, capability_mode=arguments.capability_mode, manual_projection_policy=arguments.manual_render_policy, portable_graph_policy=arguments.portable_graph_policy, live_viewer_policy=arguments.live_viewer_policy, ).run(transport="stdio") if __name__ == "__main__": main()