2026-07-29 14:54:10 -04:00
|
|
|
"""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
|
|
|
|
|
|
2026-07-29 15:42:45 -04:00
|
|
|
from ._version import __version__
|
2026-07-29 14:54:10 -04:00
|
|
|
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")
|
2026-07-29 15:42:45 -04:00
|
|
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
2026-07-29 14:54:10 -04:00
|
|
|
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()
|