updated doc strings

This commit is contained in:
2026-03-07 15:44:02 +05:30
parent 73b15cc3ab
commit 17d39a3e88
21 changed files with 680 additions and 330 deletions

View File

@@ -1,14 +1,27 @@
"""
# CLI Layer
Command line interface entry point for doc-forge.
The `docforge.cli` package provides the command-line interface for interacting
with doc-forge.
This module exposes the primary CLI entry function used by the
``doc-forge`` command. The actual command implementation resides in
``docforge.cli.main``, while this module provides a stable import path
for external tools and the package entry point configuration.
## Available Commands
The CLI is responsible for orchestrating documentation workflows such as
generating renderer sources, building documentation sites, exporting
machine-readable documentation bundles, and starting development or MCP
servers.
- **build**: Build documentation (MkDocs site or MCP resources).
- **serve**: Serve documentation (MkDocs or MCP).
- **tree**: Visualize the introspected project structure.
Typical usage
-------------
The CLI is normally invoked through the installed command:
doc-forge <command> [options]
Programmatic invocation is also possible:
from docforge.cli import main
main()
"""
from .main import main

View File

@@ -9,8 +9,10 @@ from docforge.cli import mcp_utils
@click.group()
def cli() -> None:
"""
doc-forge CLI: A tool for introspecting Python projects and generating
documentation.
Root command group for the doc-forge CLI.
Provides commands for building, serving, and inspecting
documentation generated from Python source code.
"""
pass
@@ -21,14 +23,12 @@ def cli() -> None:
@click.option("--module-is-source", is_flag=True, help="Module is source folder and to be treated as root folder")
@click.option("--module", help="Python module to document")
@click.option("--project-name", help="Project name override")
# MkDocs specific
@click.option("--site-name", help="MkDocs site name")
@click.option("--docs-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Directory for MD sources")
@click.option("--nav", "nav_file", type=click.Path(path_type=Path), default=Path("docforge.nav.yml"),
help="Nav spec path")
@click.option("--template", type=click.Path(path_type=Path), help="MkDocs template path")
@click.option("--mkdocs-yml", type=click.Path(path_type=Path), default=Path("mkdocs.yml"), help="Output config path")
# MCP specific
@click.option("--out-dir", type=click.Path(path_type=Path), default=Path("mcp_docs"), help="MCP output directory")
def build(
mcp: bool,
@@ -44,25 +44,36 @@ def build(
out_dir: Path,
) -> None:
"""
Build documentation (MkDocs site or MCP resources).
Build documentation artifacts.
This command orchestrates the full build process:
1. Introspects the code (Griffe)
2. Renders sources (MkDocs Markdown or MCP JSON)
3. (MkDocs only) Generates config and runs the final site build.
This command performs the full documentation build pipeline:
1. Introspects the Python project using Griffe
2. Generates renderer-specific documentation sources
3. Optionally builds the final documentation output
Depending on the selected options, the build can target:
- MkDocs static documentation sites
- MCP structured documentation resources
Args:
mcp: Use the MCP documentation builder.
mkdocs: Use the MkDocs documentation builder.
module_is_source: Module is the source folder and to be treated as the root folder.
module: The dotted path of the module to the document.
mcp: Enable MCP documentation generation.
mkdocs: Enable MkDocs documentation generation.
module_is_source: Treat the specified module directory as the
project root.
module: Python module import path to document.
project_name: Optional override for the project name.
site_name: (MkDocs) The site display name. Defaults to module name.
docs_dir: (MkDocs) Target directory for Markdown sources.
nav_file: (MkDocs) Path to the docforge.nav.yml specification.
template: (MkDocs) Optional custom mkdocs.yml template.
mkdocs_yml: (MkDocs) Target path for the generated mkdocs.yml.
out_dir: (MCP) Target directory for MCP JSON resources.
site_name: Display name for the MkDocs site.
docs_dir: Directory where Markdown documentation sources
will be generated.
nav_file: Path to the navigation specification file.
template: Optional custom MkDocs configuration template.
mkdocs_yml: Output path for the generated MkDocs configuration.
out_dir: Output directory for generated MCP resources.
Raises:
click.UsageError: If required options are missing or conflicting.
"""
if not mcp and not mkdocs:
raise click.UsageError("Must specify either --mcp or --mkdocs")
@@ -111,14 +122,22 @@ def serve(
out_dir: Path,
) -> None:
"""
Serve documentation (MkDocs or MCP).
Serve generated documentation locally.
Depending on the selected mode, this command starts either:
- A MkDocs development server for browsing documentation
- An MCP server exposing structured documentation resources
Args:
mcp: Serve MCP resources via an MCP server.
mkdocs: Serve the MkDocs site using the built-in development server.
module: The dotted path of the module to serve.
mkdocs_yml: (MkDocs) Path to the mkdocs.yml configuration.
out_dir: (MCP) Path to the mcp_docs/ directory.
mcp: Serve documentation using the MCP server.
mkdocs: Serve the MkDocs development site.
module: Python module import path to serve via MCP.
mkdocs_yml: Path to the MkDocs configuration file.
out_dir: Root directory containing MCP documentation resources.
Raises:
click.UsageError: If invalid or conflicting options are provided.
"""
if mcp and mkdocs:
raise click.UsageError("Cannot specify both --mcp and --mkdocs")
@@ -148,11 +167,15 @@ def tree(
project_name: Optional[str],
) -> None:
"""
Visualize the project structure in the terminal.
Display the documentation object tree for a module.
This command introspects the specified module and prints a
hierarchical representation of the discovered documentation
objects, including modules, classes, functions, and members.
Args:
module: The module import path to recursively introspect.
project_name: Optional override for the project name shown at the root.
module: Python module import path to introspect.
project_name: Optional name to display as the project root.
"""
loader = GriffeLoader()
project = loader.load_project([module], project_name)
@@ -167,11 +190,14 @@ def tree(
def _print_object(obj, indent: str) -> None:
"""
Recursive helper to print doc objects and their members to the console.
Recursively print a documentation object and its members.
This helper function traverses the documentation object graph
and prints each object with indentation to represent hierarchy.
Args:
obj: The DocObject instance to print.
indent: Current line indentation (e.g., '').
obj: Documentation object to print.
indent: Current indentation prefix used for nested members.
"""
click.echo(f"{indent}├── {obj.name}")
for member in obj.get_all_members():

View File

@@ -1,14 +1,23 @@
"""
Main entry point for the doc-forge CLI. This module delegates all command
execution to docforge.cli.commands.
Command-line entry point for the doc-forge CLI.
This module exposes the executable entry point that initializes the
Click command group defined in ``docforge.cli.commands``.
"""
from docforge.cli.commands import cli
def main() -> None:
"""
CLI Entry point. Boots the click application.
Run the doc-forge command-line interface.
This function initializes and executes the Click CLI application.
It is used as the console entry point when invoking ``doc-forge``
from the command line.
"""
cli()
if __name__ == "__main__":
main()

View File

@@ -4,14 +4,22 @@ from docforge.loaders import GriffeLoader, discover_module_paths
from docforge.renderers import MCPRenderer
from docforge.servers import MCPServer
def generate_resources(module: str, project_name: str | None, out_dir: Path) -> None:
"""
Generate MCP-compatible documentation resources.
Generate MCP documentation resources from a Python module.
The function performs project introspection, builds the internal
documentation model, and renders MCP-compatible JSON resources
to the specified output directory.
Args:
module: The dotted path of the primary module to document.
project_name: Optional override for the project name.
out_dir: Directory where the MCP JSON resources and nav will be written.
module: Python module import path used as the entry point for
documentation generation.
project_name: Optional override for the project name used in
generated documentation metadata.
out_dir: Directory where MCP resources (index.json, nav.json,
and module data) will be written.
"""
loader = GriffeLoader()
discovered_paths = discover_module_paths(module)
@@ -20,13 +28,23 @@ def generate_resources(module: str, project_name: str | None, out_dir: Path) ->
renderer = MCPRenderer()
renderer.generate_sources(project, out_dir)
def serve(module: str, mcp_root: Path) -> None:
"""
Serve MCP documentation from a pre-built bundle.
Start an MCP server for a pre-generated documentation bundle.
The server exposes documentation resources such as project metadata,
navigation structure, and module documentation through MCP endpoints.
Args:
module: The dotted path of the primary module to serve.
mcp_root: Path to the directory containing index.json, nav.json, and modules/.
module: Python module import path used to identify the served
documentation instance.
mcp_root: Path to the directory containing the MCP documentation
bundle (index.json, nav.json, and modules/).
Raises:
click.ClickException: If the MCP documentation bundle is missing
required files or directories.
"""
if not mcp_root.exists():
raise click.ClickException(f"mcp_docs directory not found: {mcp_root}")

View File

@@ -6,6 +6,7 @@ from docforge.loaders import GriffeLoader, discover_module_paths
from docforge.renderers import MkDocsRenderer
from docforge.nav import load_nav_spec, resolve_nav, MkDocsNavEmitter
def generate_sources(
module: str,
docs_dir: Path,
@@ -13,13 +14,20 @@ def generate_sources(
module_is_source: bool | None = None,
) -> None:
"""
Generate Markdown source files for the specified module.
Generate MkDocs Markdown sources for a Python module.
This function introspects the specified module, builds the internal
documentation model, and renders Markdown documentation files for
use with MkDocs.
Args:
module: The dotted path of the primary module to document.
project_name: Optional override for the project name.
module: Python module import path used as the entry point for
documentation generation.
docs_dir: Directory where the generated Markdown files will be written.
module_is_source: Module is the source folder and to be treated as the root folder.
project_name: Optional override for the project name used in
documentation metadata.
module_is_source: If True, treat the specified module directory as
the project root rather than a nested module.
"""
loader = GriffeLoader()
discovered_paths = discover_module_paths(module)
@@ -38,16 +46,33 @@ def generate_sources(
module_is_source,
)
def generate_config(docs_dir: Path, nav_file: Path, template: Path | None, out: Path, site_name: str) -> None:
def generate_config(
docs_dir: Path,
nav_file: Path,
template: Path | None,
out: Path,
site_name: str,
) -> None:
"""
Generate an mkdocs.yml configuration file.
Generate an ``mkdocs.yml`` configuration file.
The configuration is created by combining a template configuration
with a navigation structure derived from the docforge navigation
specification.
Args:
docs_dir: Path to the directory containing documentation Markdown files.
nav_file: Path to the docforge.nav.yml specification.
template: Optional path to an mkdocs.yml template (overrides built-in).
out: Path where the final mkdocs.yml will be written.
site_name: The display name for the documentation site.
docs_dir: Directory containing generated documentation Markdown files.
nav_file: Path to the ``docforge.nav.yml`` navigation specification.
template: Optional path to a custom MkDocs configuration template.
If not provided, a built-in template will be used.
out: Destination path where the generated ``mkdocs.yml`` file
will be written.
site_name: Display name for the generated documentation site.
Raises:
click.FileError: If the navigation specification or template
file cannot be found.
"""
if not nav_file.exists():
raise click.FileError(str(nav_file), hint="Nav spec not found")
@@ -74,12 +99,19 @@ def generate_config(docs_dir: Path, nav_file: Path, template: Path | None, out:
out.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
def build(mkdocs_yml: Path) -> None:
"""
Build the documentation site using MkDocs.
Build the MkDocs documentation site.
This function loads the MkDocs configuration and runs the MkDocs
build command to generate the final static documentation site.
Args:
mkdocs_yml: Path to the mkdocs.yml configuration file.
mkdocs_yml: Path to the ``mkdocs.yml`` configuration file.
Raises:
click.ClickException: If the configuration file does not exist.
"""
if not mkdocs_yml.exists():
raise click.ClickException(f"mkdocs.yml not found: {mkdocs_yml}")
@@ -89,12 +121,19 @@ def build(mkdocs_yml: Path) -> None:
mkdocs_build(load_config(str(mkdocs_yml)))
def serve(mkdocs_yml: Path) -> None:
"""
Serve the documentation site with live-reload using MkDocs.
Start an MkDocs development server with live reload.
The server watches documentation files and automatically reloads
the site when changes are detected.
Args:
mkdocs_yml: Path to the mkdocs.yml configuration file.
mkdocs_yml: Path to the ``mkdocs.yml`` configuration file.
Raises:
click.ClickException: If the configuration file does not exist.
"""
if not mkdocs_yml.exists():
raise click.ClickException(f"mkdocs.yml not found: {mkdocs_yml}")

View File

@@ -1,17 +1,23 @@
"""
# Loader Layer
Loader layer for doc-forge.
The `docforge.loaders` package is responsible for discovering Python source files
and extracting their documentation using static analysis.
The ``docforge.loaders`` package is responsible for discovering Python modules
and extracting documentation data using static analysis.
## Core Features
Overview
--------
- **Discovery**: Automatically find all modules and packages in a project
directory.
- **Introspection**: Uses `griffe` to parse docstrings, signatures, and
hierarchical relationships without executing the code.
- **Filtering**: Automatically excludes private members (prefixed with `_`) to
ensure clean public documentation.
This layer converts Python source code into an intermediate documentation
model used by doc-forge. It performs module discovery, introspection, and
initial filtering before the data is passed to the core documentation models.
Core capabilities include:
- **Module discovery** Locate Python modules and packages within a project.
- **Static introspection** Parse docstrings, signatures, and object
hierarchies using the ``griffe`` library without executing the code.
- **Public API filtering** Exclude private members (names prefixed with
``_``) to produce clean public documentation structures.
"""
from .griffe_loader import GriffeLoader, discover_module_paths

View File

@@ -1,6 +1,9 @@
"""
This module provides the GriffeLoader, which uses the 'griffe' library to
introspect Python source code and populate the doc-forge Project models.
Utilities for loading and introspecting Python modules using Griffe.
This module provides the ``GriffeLoader`` class and helper utilities used to
discover Python modules, introspect their structure, and convert the results
into doc-forge documentation models.
"""
import logging
@@ -25,19 +28,27 @@ def discover_module_paths(
project_root: Path | None = None,
) -> List[str]:
"""
Discover all Python modules under a package via filesystem traversal.
Discover Python modules within a package directory.
Rules:
- Directory with __init__.py is treated as a package.
- Any .py file is treated as a module.
- All paths are converted to dotted module paths.
The function scans the filesystem for ``.py`` files inside the specified
package and converts them into dotted module import paths.
Discovery rules:
- Directories containing ``__init__.py`` are treated as packages.
- Each ``.py`` file is treated as a module.
- Results are returned as dotted import paths.
Args:
module_name: The name of the package to discover.
project_root: The root directory of the project. Defaults to current working directory.
module_name: Top-level package name to discover modules from.
project_root: Root directory used to resolve module paths. If not
provided, the current working directory is used.
Returns:
A sorted list of dotted module paths.
A sorted list of unique dotted module import paths.
Raises:
FileNotFoundError: If the specified package directory does not exist.
"""
if project_root is None:
@@ -64,12 +75,19 @@ def discover_module_paths(
class GriffeLoader:
"""
Loads Python modules and extracts documentation using the Griffe introspection engine.
Load Python modules using Griffe and convert them into doc-forge models.
This loader uses the Griffe introspection engine to analyze Python source
code and transform the extracted information into ``Project``, ``Module``,
and ``DocObject`` instances used by doc-forge.
"""
def __init__(self) -> None:
"""
Initialize the GriffeLoader.
Initialize the Griffe-backed loader.
Creates an internal Griffe loader instance with dedicated collections
for modules and source lines.
"""
self._loader = _GriffeLoader(
modules_collection=ModulesCollection(),
@@ -77,21 +95,32 @@ class GriffeLoader:
)
def load_project(
self,
module_paths: List[str],
project_name: Optional[str] = None,
skip_import_errors: bool = None,
self,
module_paths: List[str],
project_name: Optional[str] = None,
skip_import_errors: bool = None,
) -> Project:
"""
Load multiple modules and combine them into a single Project models.
Load multiple modules and assemble them into a Project model.
Each module path is introspected and converted into a ``Module``
instance. All modules are then aggregated into a single ``Project``
object.
Args:
module_paths: A list of dotted paths to the modules to load.
project_name: Optional name for the project. Defaults to the first module name.
skip_import_errors: If True, modules that fail to import will be skipped.
module_paths: List of dotted module import paths to load.
project_name: Optional override for the project name. Defaults
to the top-level name of the first module.
skip_import_errors: If True, modules that fail to load will be
skipped instead of raising an error.
Returns:
A Project instance containing the loaded modules.
A populated ``Project`` instance containing the loaded modules.
Raises:
ValueError: If no module paths are provided.
ImportError: If a module fails to load and
``skip_import_errors`` is False.
"""
if not module_paths:
raise ValueError("At least one module path must be provided")
@@ -116,13 +145,16 @@ class GriffeLoader:
def load_module(self, path: str) -> Module:
"""
Load a single module and convert its introspection data into the docforge models.
Load and convert a single Python module.
The module is introspected using Griffe and then transformed into
a doc-forge ``Module`` model.
Args:
path: The dotted path of the module to load.
path: Dotted import path of the module.
Returns:
A Module instance.
A populated ``Module`` instance.
"""
self._loader.load(path)
griffe_module = self._loader.modules_collection[path]
@@ -135,13 +167,16 @@ class GriffeLoader:
def _convert_module(self, obj: Object) -> Module:
"""
Convert a Griffe Object (module) into a docforge Module.
Convert a Griffe module object into a doc-forge Module.
All public members of the module are recursively converted into
``DocObject`` instances.
Args:
obj: The Griffe Object representing the module.
obj: Griffe object representing the module.
Returns:
A populated Module instance.
A populated ``Module`` model.
"""
module = Module(
path=obj.path,
@@ -158,13 +193,17 @@ class GriffeLoader:
def _convert_object(self, obj: Object) -> DocObject:
"""
Recursively convert a Griffe Object into a DocObject hierarchy.
Convert a Griffe object into a doc-forge DocObject.
The conversion preserves the object's metadata such as name,
kind, path, signature, and docstring. Child members are processed
recursively.
Args:
obj: The Griffe Object to convert.
obj: Griffe object representing a documented Python object.
Returns:
A DocObject instance.
A ``DocObject`` instance representing the converted object.
"""
kind = obj.kind.value
signature = self._safe_signature(obj)
@@ -193,13 +232,13 @@ class GriffeLoader:
def _safe_docstring(self, obj: Object) -> Optional[str]:
"""
Safely retrieve the docstring value from a Griffe object.
Safely extract a docstring from a Griffe object.
Args:
obj: The Griffe Object to inspect.
obj: Griffe object to inspect.
Returns:
The raw docstring string, or None if missing or unresolvable.
The raw docstring text if available, otherwise ``None``.
"""
try:
return obj.docstring.value if obj.docstring else None
@@ -208,13 +247,14 @@ class GriffeLoader:
def _safe_signature(self, obj: Object) -> Optional[str]:
"""
Safely retrieve the signature string from a Griffe object.
Safely extract the signature of a Griffe object.
Args:
obj: The Griffe Object to inspect.
obj: Griffe object to inspect.
Returns:
The string representation of the signature, or None.
String representation of the object's signature if available,
otherwise ``None``.
"""
try:
if hasattr(obj, "signature") and obj.signature:

View File

@@ -1,17 +1,28 @@
"""
# Model Layer
Model layer for doc-forge.
The `docforge.models` package provides the core data structures used to represent
Python source code in a documentation-focused hierarchy.
The ``docforge.models`` package defines the core data structures used to
represent Python source code as a structured documentation model.
## Key Components
Overview
--------
- **Project**: The root container for all documented modules.
- **Module**: Represents a Python module or package, containing members.
- **DocObject**: A recursive structure for classes, functions, and attributes.
The model layer forms the central intermediate representation used throughout
doc-forge. Python modules and objects discovered during introspection are
converted into a hierarchy of documentation models that can later be rendered
into different documentation formats.
These classes are designed to be renderer-agnostic, allowing the same internal
representation to be transformed into various output formats (currently MkDocs).
Key components:
- **Project** Root container representing an entire documented codebase.
- **Module** Representation of a Python module or package containing
documented members.
- **DocObject** Recursive structure representing Python objects such as
classes, functions, methods, and attributes.
These models are intentionally **renderer-agnostic**, allowing the same
documentation structure to be transformed into multiple output formats
(e.g., MkDocs, MCP, or other renderers).
"""
from .project import Project

View File

@@ -1,7 +1,10 @@
"""
This module defines the Module class, which represents a Python module or package
in the doc-forge documentation models. It acts as a container for top-level
documented objects.
Documentation model representing a Python module or package.
This module defines the ``Module`` class used in the doc-forge documentation
model. A ``Module`` acts as a container for top-level documented objects
(classes, functions, variables, and other members) discovered during
introspection.
"""
from typing import Dict, Iterable, Optional
@@ -11,12 +14,17 @@ from docforge.models.object import DocObject
class Module:
"""
Represents a documented Python module or package.
Representation of a documented Python module or package.
A ``Module`` stores metadata about the module itself and maintains a
collection of top-level documentation objects discovered during
introspection.
Attributes:
path: Dotted import path of the module.
docstring: Module-level docstring content.
members: Dictionary mapping object names to their DocObject representations.
docstring: Module-level documentation string, if present.
members: Mapping of object names to their corresponding
``DocObject`` representations.
"""
def __init__(
@@ -25,11 +33,11 @@ class Module:
docstring: Optional[str] = None,
) -> None:
"""
Initialize a new Module.
Initialize a Module instance.
Args:
path: The dotted path of the module.
docstring: The module's docstring, if any.
path: Dotted import path identifying the module.
docstring: Module-level documentation text, if available.
"""
self.path = path
self.docstring = docstring
@@ -40,27 +48,32 @@ class Module:
Add a documented object to the module.
Args:
obj: The object to add.
obj: Documentation object to register as a top-level
member of the module.
"""
self.members[obj.name] = obj
def get_object(self, name: str) -> DocObject:
"""
Retrieve a member object by name.
Retrieve a documented object by name.
Args:
name: The name of the object.
name: Name of the object to retrieve.
Returns:
The requested DocObject.
The corresponding ``DocObject`` instance.
Raises:
KeyError: If no object with the given name exists.
"""
return self.members[name]
def get_all_objects(self) -> Iterable[DocObject]:
"""
Get all top-level objects in the module.
Return all top-level documentation objects in the module.
Returns:
An iterable of DocObject instances.
An iterable of ``DocObject`` instances representing the
module's public members.
"""
return self.members.values()

View File

@@ -1,7 +1,10 @@
"""
This module defines the DocObject class, the fundamental recursive unit of the
doc-forge documentation models. A DocObject represents a single Python entity
(class, function, method, or attribute) and its nested members.
Documentation model representing individual Python objects.
This module defines the ``DocObject`` class, the fundamental recursive unit of
the doc-forge documentation model. Each ``DocObject`` represents a Python
entity such as a class, function, method, or attribute, and may contain nested
members that form a hierarchical documentation structure.
"""
from typing import Dict, Iterable, Optional
@@ -9,15 +12,20 @@ from typing import Dict, Iterable, Optional
class DocObject:
"""
Represents a documented Python object (class, function, method, etc.).
Representation of a documented Python object.
A ``DocObject`` models a single Python entity discovered during
introspection. Objects may contain nested members, allowing the structure
of modules, classes, and other containers to be represented recursively.
Attributes:
name: Local name of the object.
kind: Type of object (e.g., 'class', 'function', 'attribute').
path: Full dotted import path to the object.
signature: Callable signature, if applicable.
docstring: Raw docstring content extracted from the source.
members: Dictionary mapping member names to their DocObject representations.
kind: Type of object (for example ``class``, ``function``,
``method``, or ``attribute``).
path: Fully qualified dotted path to the object.
signature: Callable signature if the object represents a callable.
docstring: Raw docstring text extracted from the source code.
members: Mapping of member names to child ``DocObject`` instances.
"""
def __init__(
@@ -29,48 +37,54 @@ class DocObject:
docstring: Optional[str] = None,
) -> None:
"""
Initialize a new DocObject.
Initialize a DocObject instance.
Args:
name: The local name of the object.
kind: The kind of object (e.g., 'class', 'function').
path: The full dotted path to the object.
signature: The object's signature (for callable objects).
docstring: The object's docstring, if any.
name: Local name of the object.
kind: Object type identifier (for example ``class`` or ``function``).
path: Fully qualified dotted path of the object.
signature: Callable signature if applicable.
docstring: Documentation string associated with the object.
"""
self.name = name
self.kind = kind
self.path = path
self.signature = signature
self.docstring = docstring
self.members: Dict[str, 'DocObject'] = {}
self.members: Dict[str, "DocObject"] = {}
def add_member(self, obj: 'DocObject') -> None:
def add_member(self, obj: "DocObject") -> None:
"""
Add a child member to this object (e.g., a method to a class).
Add a child documentation object.
This is typically used when attaching methods to classes or
nested objects to their parent containers.
Args:
obj: The child DocObject to add.
obj: Documentation object to add as a member.
"""
self.members[obj.name] = obj
def get_member(self, name: str) -> 'DocObject':
def get_member(self, name: str) -> "DocObject":
"""
Retrieve a child member by name.
Retrieve a member object by name.
Args:
name: The name of the member.
name: Name of the member to retrieve.
Returns:
The requested DocObject.
The corresponding ``DocObject`` instance.
Raises:
KeyError: If the member does not exist.
"""
return self.members[name]
def get_all_members(self) -> Iterable['DocObject']:
def get_all_members(self) -> Iterable["DocObject"]:
"""
Get all members of this object.
Return all child members of the object.
Returns:
An iterable of child DocObject instances.
An iterable of ``DocObject`` instances representing nested members.
"""
return self.members.values()

View File

@@ -1,6 +1,9 @@
"""
This module defines the Project class, the top-level container for a documented
project. It aggregates multiple Module instances into a single named entity.
Documentation model representing a project.
This module defines the ``Project`` class, the top-level container used by
doc-forge to represent a documented codebase. A ``Project`` aggregates multiple
modules and provides access to them through a unified interface.
"""
from typing import Dict, Iterable
@@ -10,29 +13,32 @@ from docforge.models.module import Module
class Project:
"""
Represents a documentation project, serving as a container for modules.
Representation of a documentation project.
A ``Project`` serves as the root container for all modules discovered during
introspection. Each module is stored by its dotted import path.
Attributes:
name: Name of the project.
modules: Dictionary mapping module paths to Module instances.
modules: Mapping of module paths to ``Module`` instances.
"""
def __init__(self, name: str) -> None:
"""
Initialize a new Project.
Initialize a Project instance.
Args:
name: The name of the project.
name: Name used to identify the documentation project.
"""
self.name = name
self.modules: Dict[str, Module] = {}
def add_module(self, module: Module) -> None:
"""
Add a module to the project.
Register a module in the project.
Args:
module: The module to add.
module: Module instance to add to the project.
"""
self.modules[module.path] = module
@@ -41,27 +47,30 @@ class Project:
Retrieve a module by its dotted path.
Args:
path: The dotted path of the module (e.g., 'pkg.mod').
path: Fully qualified dotted module path (for example ``pkg.module``).
Returns:
The requested Module.
The corresponding ``Module`` instance.
Raises:
KeyError: If the module does not exist in the project.
"""
return self.modules[path]
def get_all_modules(self) -> Iterable[Module]:
"""
Get all modules in the project.
Return all modules contained in the project.
Returns:
An iterable of Module objects.
An iterable of ``Module`` instances.
"""
return self.modules.values()
def get_module_list(self) -> list[str]:
"""
Get the list of all module dotted paths.
Return the list of module import paths.
Returns:
A list of module paths.
A list containing the dotted paths of all modules in the project.
"""
return list(self.modules.keys())

View File

@@ -1,17 +1,22 @@
"""
# Navigation Layer
Navigation layer for doc-forge.
The `docforge.nav` package manages the mapping between the logical documentation
structure and the physical files on disk.
The ``docforge.nav`` package manages the relationship between the logical
documentation structure defined by the user and the physical documentation
files generated on disk.
## Workflow
Workflow
--------
1. **Spec Definition**: Users define navigation intent in `docforge.nav.yml`.
2. **Resolution**: `resolve_nav` matches patterns in the spec to generated `.md` files.
3. **Emission**: `MkDocsNavEmitter` produces the final YAML structure for `mkdocs.yml`.
1. **Specification** Users define navigation intent in ``docforge.nav.yml``.
2. **Resolution** ``resolve_nav`` expands patterns and matches them against
generated Markdown files.
3. **Emission** ``MkDocsNavEmitter`` converts the resolved structure into
the YAML navigation format required by ``mkdocs.yml``.
This abstraction allows doc-forge to support complex grouping and ordering
independently of the source code's physical layout.
This layer separates documentation organization from the underlying source
code layout, enabling flexible grouping, ordering, and navigation structures
independent of module hierarchy.
"""
from .spec import NavSpec, load_nav_spec

View File

@@ -1,7 +1,9 @@
"""
This module provides the MkDocsNavEmitter, which converts a ResolvedNav instance
into the specific YAML-ready list structure expected by the MkDocs 'nav'
configuration.
MkDocs navigation emitter.
This module provides the ``MkDocsNavEmitter`` class, which converts a
``ResolvedNav`` instance into the navigation structure required by the
MkDocs ``nav`` configuration.
"""
from pathlib import Path
@@ -12,19 +14,24 @@ from docforge.nav.resolver import ResolvedNav
class MkDocsNavEmitter:
"""
Emitter responsible for transforming a ResolvedNav into an MkDocs-compatible
navigation structure.
Emit MkDocs navigation structures from resolved navigation data.
The emitter transforms a ``ResolvedNav`` object into the YAML-compatible
list structure expected by the MkDocs ``nav`` configuration field.
"""
def emit(self, nav: ResolvedNav) -> List[Dict[str, Any]]:
"""
Generate a list of navigation entries for mkdocs.yml.
Generate a navigation structure for ``mkdocs.yml``.
Args:
nav: The resolved navigation data.
nav: Resolved navigation data describing documentation groups
and their associated Markdown files.
Returns:
A list of dictionary mappings representing the MkDocs navigation.
A list of dictionaries representing the MkDocs navigation layout.
Each dictionary maps a navigation label to a page or a list of
pages.
"""
result: List[Dict[str, Any]] = []
@@ -45,16 +52,18 @@ class MkDocsNavEmitter:
def _to_relative(self, path: Path, docs_root: Path | None) -> str:
"""
Convert a filesystem path to a path relative to the documentation root.
This handles both absolute and relative filesystem paths, ensuring the
output is compatible with MkDocs navigation requirements.
Convert a filesystem path into a documentation-relative path.
This method normalizes paths so they can be used in MkDocs navigation.
It handles both absolute and relative filesystem paths and ensures the
resulting path is relative to the documentation root.
Args:
path: The path to convert.
docs_root: The root directory for documentation.
path: Filesystem path to convert.
docs_root: Root directory of the documentation sources.
Returns:
A string representing the relative POSIX-style path.
POSIX-style path relative to the documentation root.
"""
if docs_root and path.is_absolute():
try:

View File

@@ -1,7 +1,8 @@
"""
This module contains the logic for resolving a NavSpec against the physical
filesystem. It expands globs and validates that all referenced documents
actually exist on disk.
Navigation resolution utilities.
This module resolves a ``NavSpec`` against the filesystem by expanding glob
patterns and validating that referenced documentation files exist.
"""
from pathlib import Path
@@ -14,12 +15,15 @@ from docforge.nav.spec import NavSpec
class ResolvedNav:
"""
Represents a navigation structure where all patterns and paths have been
resolved against the actual filesystem contents.
Resolved navigation structure.
A ``ResolvedNav`` represents navigation data after glob patterns have been
expanded and paths validated against the filesystem.
Attributes:
home: Resolved relative path to the home page.
groups: Mapping of group titles to lists of absolute or relative Path objects.
home: Relative path to the documentation home page.
groups: Mapping of navigation group titles to lists of resolved
documentation file paths.
"""
def __init__(
@@ -32,9 +36,9 @@ class ResolvedNav:
Initialize a ResolvedNav instance.
Args:
home: The relative path to the project home page.
groups: A mapping of group names to their resolved filesystem paths.
docs_root: The root documentation directory.
home: Relative path to the home page within the documentation root.
groups: Mapping of group titles to resolved documentation file paths.
docs_root: Root directory of the documentation source files.
"""
self.home = home
self.groups = groups
@@ -42,15 +46,20 @@ class ResolvedNav:
def all_files(self) -> Iterable[Path]:
"""
Get an iterable of all resolved files in the navigation structure.
Iterate over all files referenced by the navigation structure.
Returns:
An iterable of Path objects.
An iterable of ``Path`` objects representing documentation files.
Raises:
RuntimeError: If the home page is defined but the documentation
root is not available for resolution.
"""
if self.home:
if self._docs_root is None:
raise RuntimeError("docs_root is required to resolve home path")
yield self._docs_root / self.home
for paths in self.groups.values():
for p in paths:
yield p
@@ -61,34 +70,37 @@ def resolve_nav(
docs_root: Path,
) -> ResolvedNav:
"""
Create a ResolvedNav by processing a NavSpec against the filesystem.
This expands globs and validates the existence of referenced files.
Resolve a navigation specification against the filesystem.
The function expands glob patterns defined in a ``NavSpec`` and verifies
that referenced documentation files exist within the documentation root.
Args:
spec: The navigation specification to resolve.
docs_root: The root directory for documentation files.
spec: Navigation specification describing documentation layout.
docs_root: Root directory containing documentation Markdown files.
Returns:
A ResolvedNav instance.
A ``ResolvedNav`` instance containing validated navigation paths.
Raises:
FileNotFoundError: If a pattern doesn't match any files or the docs_root doesn't exist.
FileNotFoundError: If the documentation root does not exist or a
navigation pattern does not match any files.
"""
if not docs_root.exists():
raise FileNotFoundError(docs_root)
def resolve_pattern(pattern: str) -> List[Path]:
"""
Resolve a single glob pattern relative to the docs_root.
Resolve a glob pattern relative to the documentation root.
Args:
pattern: The glob pattern to resolve.
pattern: Glob pattern used to match documentation files.
Returns:
A sorted list of matching Path objects.
A sorted list of matching ``Path`` objects.
Raises:
FileNotFoundError: If the pattern doesn't match any files.
FileNotFoundError: If the pattern does not match any files.
"""
full = docs_root / pattern
matches = sorted(
@@ -100,7 +112,7 @@ def resolve_nav(
return matches
# Resolve home
# Resolve home page
home: str | None = None
if spec.home:
home_path = docs_root / spec.home
@@ -108,7 +120,7 @@ def resolve_nav(
raise FileNotFoundError(spec.home)
home = spec.home
# Resolve groups
# Resolve navigation groups
resolved_groups: Dict[str, List[Path]] = {}
for group, patterns in spec.groups.items():

View File

@@ -1,7 +1,9 @@
"""
This module defines the NavSpec class, which represents the user's intent for
documentation navigation as defined in a YAML specification (usually
docforge.nav.yml).
Navigation specification model.
This module defines the ``NavSpec`` class, which represents the navigation
structure defined by the user in the doc-forge navigation specification
(typically ``docforge.nav.yml``).
"""
from pathlib import Path
@@ -12,11 +14,16 @@ import yaml
class NavSpec:
"""
Parsed representation of the docforge navigation specification file.
Parsed representation of a navigation specification.
A ``NavSpec`` describes the intended documentation navigation layout before
it is resolved against the filesystem.
Attributes:
home: Path to the home document (e.g., 'index.md').
groups: Mapping of group titles to lists of path patterns/globs.
home: Relative path to the documentation home page (for example
``index.md``).
groups: Mapping of navigation group titles to lists of file patterns
or glob expressions.
"""
def __init__(
@@ -25,11 +32,12 @@ class NavSpec:
groups: Dict[str, List[str]],
) -> None:
"""
Initialize a NavSpec.
Initialize a NavSpec instance.
Args:
home: The path to the home document.
groups: A mapping of group names to lists of path patterns (globs).
home: Relative path to the home document.
groups: Mapping of group names to lists of path patterns
(glob expressions).
"""
self.home = home
self.groups = groups
@@ -37,17 +45,18 @@ class NavSpec:
@classmethod
def load(cls, path: Path) -> "NavSpec":
"""
Load a NavSpec from a YAML file.
Load a navigation specification from a YAML file.
Args:
path: The filesystem path to the YAML file.
path: Filesystem path to the navigation specification file.
Returns:
A NavSpec instance.
A ``NavSpec`` instance representing the parsed configuration.
Raises:
FileNotFoundError: If the path does not exist.
ValueError: If the file content is not a valid NavSpec mapping.
FileNotFoundError: If the specified file does not exist.
ValueError: If the file contents are not a valid navigation
specification.
"""
if not path.exists():
raise FileNotFoundError(path)
@@ -78,33 +87,45 @@ class NavSpec:
def all_patterns(self) -> List[str]:
"""
Get all path patterns referenced in the specification.
Return all path patterns referenced by the specification.
Returns:
A list of all patterns (home plus all groups).
A list containing the home document (if defined) and all
group pattern entries.
"""
patterns: List[str] = []
if self.home:
patterns.append(self.home)
for items in self.groups.values():
patterns.extend(items)
return patterns
def load_nav_spec(path: Path) -> NavSpec:
"""
Utility function to load a NavSpec from a file.
Load a navigation specification file.
This helper function reads a YAML navigation file and constructs a
corresponding ``NavSpec`` instance.
Args:
path: Path to the navigation specification file.
Returns:
A loaded NavSpec instance.
A ``NavSpec`` instance representing the parsed specification.
Raises:
FileNotFoundError: If the specification file does not exist.
ValueError: If the YAML structure is invalid.
"""
if not path.exists():
raise FileNotFoundError(path)
data = yaml.safe_load(path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ValueError("Nav spec must be a YAML mapping")

View File

@@ -1,20 +1,28 @@
"""
# Renderers Layer
Renderers layer for doc-forge.
The `docforge.renderers` package handles the transformation of the internal
documentation models into physical files formatted for specific documentation
engines.
The ``docforge.renderers`` package transforms the internal documentation
models into files formatted for specific documentation systems.
## Current Implementations
Overview
--------
- **MkDocsRenderer**: Generates Markdown files utilizing the `mkdocstrings`
syntax. It automatically handles package/module hierarchy and generates
`index.md` files for packages.
Renderers consume the doc-forge project model and generate output suitable
for documentation tools or machine interfaces.
## Extending
Current implementations:
To add a new renderer, implement the `DocRenderer` protocol defined in
`docforge.renderers.base`.
- **MkDocsRenderer** Produces Markdown files compatible with MkDocs and
the ``mkdocstrings`` plugin. It automatically handles package hierarchy
and generates ``index.md`` files for packages.
- **MCPRenderer** Emits structured JSON resources designed for consumption
by Model Context Protocol (MCP) clients.
Extending
---------
New renderers can be added by implementing the ``DocRenderer`` protocol
defined in ``docforge.renderers.base``.
"""
from .mkdocs_renderer import MkDocsRenderer

View File

@@ -1,7 +1,9 @@
"""
This module defines the base interfaces and configuration containers for
doc-forge renderers. All renderer implementations should adhere to the
DocRenderer protocol.
Renderer base interfaces and configuration models.
This module defines the base protocol and configuration container used by
doc-forge renderers. Concrete renderer implementations should implement the
``DocRenderer`` protocol.
"""
from pathlib import Path
@@ -14,12 +16,22 @@ class RendererConfig:
"""
Configuration container for documentation renderers.
Args:
out_dir: The directory where documentation files should be written.
project: The introspected project models to be rendered.
A ``RendererConfig`` instance groups together the project model and the
output directory used during rendering.
Attributes:
out_dir: Directory where generated documentation files will be written.
project: Documentation project model to be rendered.
"""
def __init__(self, out_dir: Path, project: Project) -> None:
"""
Initialize a RendererConfig instance.
Args:
out_dir: Target directory where documentation files should be written.
project: Introspected project model to render.
"""
self.out_dir = out_dir
self.project = project
@@ -27,6 +39,9 @@ class RendererConfig:
class DocRenderer(Protocol):
"""
Protocol defining the interface for documentation renderers.
Implementations of this protocol are responsible for transforming a
``Project`` model into renderer-specific documentation sources.
"""
name: str
@@ -37,10 +52,11 @@ class DocRenderer(Protocol):
out_dir: Path,
) -> None:
"""
Generate renderer-specific source files for the given project.
Generate renderer-specific documentation sources.
Args:
project: The project models containing modules and objects.
out_dir: Target directory for the generated files.
project: Project model containing modules and documentation objects.
out_dir: Directory where generated documentation sources
should be written.
"""
...

View File

@@ -7,18 +7,25 @@ from docforge.models import Project, Module, DocObject
class MCPRenderer:
"""
Renderer that emits MCP-native JSON resources from docforge models.
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-compatible JSON resources and navigation for the project.
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: The project model to render.
out_dir: Target directory for the generated JSON files.
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)
@@ -54,11 +61,11 @@ class MCPRenderer:
def _write_module(self, module: Module, modules_dir: Path) -> None:
"""
Serialize a module into an MCP JSON resource on disk.
Serialize a module into an MCP resource file.
Args:
module: The module instance to serialize.
modules_dir: The directory where the module JSON file should be written.
module: Module instance to serialize.
modules_dir: Directory where module JSON files are stored.
"""
payload = {
"module": module.path,
@@ -71,13 +78,13 @@ class MCPRenderer:
def _render_module(self, module: Module) -> Dict:
"""
Render a Module into MCP-friendly structured data.
Convert a Module model into MCP-compatible structured data.
Args:
module: The module instance to render.
module: Module instance to convert.
Returns:
A dictionary following the MCP documentation resource schema.
Dictionary representing the module and its documented objects.
"""
data: Dict = {
"path": module.path,
@@ -92,13 +99,13 @@ class MCPRenderer:
def _render_object(self, obj: DocObject) -> Dict:
"""
Recursively render a DocObject into structured MCP data.
Recursively convert a DocObject into structured MCP data.
Args:
obj: The documented object (class, func, etc.) to render.
obj: Documentation object to convert.
Returns:
A dictionary representing the object and its members.
Dictionary describing the object and any nested members.
"""
data: Dict = {
"name": obj.name,
@@ -119,4 +126,13 @@ class MCPRenderer:
@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)

View File

@@ -1,13 +1,16 @@
"""
MkDocsRenderer
MkDocs renderer implementation.
Generates Markdown source files compatible with MkDocs Material
and mkdocstrings, ensuring:
This module defines the ``MkDocsRenderer`` class, which generates Markdown
documentation sources compatible with MkDocs Material and the mkdocstrings
plugin.
- Root index.md always exists
- Parent package indexes are created automatically
- Child modules are linked in parent index files
- README.md can be generated from the root package docstring
The renderer ensures a consistent documentation structure by:
- Creating a root ``index.md`` if one does not exist
- Generating package index pages automatically
- Linking child modules within parent package pages
- Optionally generating ``README.md`` from the root package docstring
"""
from pathlib import Path
@@ -16,8 +19,10 @@ from docforge.models import Project, Module
class MkDocsRenderer:
"""
Renderer that generates Markdown source files formatted for the MkDocs
'mkdocstrings' plugin.
Renderer that produces Markdown documentation for MkDocs.
Generated pages use mkdocstrings directives to reference Python modules,
allowing MkDocs to render API documentation dynamically.
"""
name = "mkdocs"
@@ -25,6 +30,7 @@ class MkDocsRenderer:
# -------------------------
# Public API
# -------------------------
def generate_sources(
self,
project: Project,
@@ -32,18 +38,17 @@ class MkDocsRenderer:
module_is_source: bool | None = None,
) -> None:
"""
Produce a set of Markdown files in the output directory based on the
provided Project models.
Generate Markdown documentation files for a project.
This method renders a documentation structure from the provided
project model and writes the resulting Markdown files to the
specified output directory.
Args:
project:
The project model to render.
out_dir:
Target directory for generated Markdown.
module_is_source:
If True, treat the module as the root folder.
project: Project model containing modules to document.
out_dir: Directory where generated Markdown files will be written.
module_is_source: If True, treat the specified module as the
documentation root rather than nesting it inside a folder.
"""
out_dir.mkdir(parents=True, exist_ok=True)
self._ensure_root_index(project, out_dir)
@@ -51,7 +56,7 @@ class MkDocsRenderer:
modules = list(project.get_all_modules())
paths = {m.path for m in modules}
# Package detection (level-agnostic)
# Detect packages (modules with children)
packages = {
p for p in paths
if any(other.startswith(p + ".") for other in paths)
@@ -72,22 +77,23 @@ class MkDocsRenderer:
module_is_source: bool | None = None,
) -> None:
"""
Generate README.md from the root package docstring.
Generate a ``README.md`` file from the root module docstring.
Behavior:
- If module_is_source is True:
README.md is generated at project root (docs_dir.parent)
- If ``module_is_source`` is True, ``README.md`` is written to the
project root directory.
- If False, README generation is currently not implemented.
- If module_is_source is False:
TODO: generate README.md inside respective module folders
Args:
project: Project model containing documentation metadata.
docs_dir: Directory containing generated documentation sources.
module_is_source: Whether the module is treated as the project
source root.
"""
# -------------------------
# Only implement source-root mode
# -------------------------
if not module_is_source:
# TODO: support per-module README generation
# Future: support README generation per module
return
readme_path = docs_dir.parent / "README.md"
@@ -127,7 +133,13 @@ class MkDocsRenderer:
def _find_root_module(self, project: Project) -> Module | None:
"""
Find the root module matching the project name.
Locate the root module corresponding to the project name.
Args:
project: Project model to inspect.
Returns:
The root ``Module`` if found, otherwise ``None``.
"""
for module in project.get_all_modules():
if module.path == project.name:
@@ -142,14 +154,18 @@ class MkDocsRenderer:
module_is_source: bool | None = None,
) -> None:
"""
Write a single module's documentation file. Packages are written as
'index.md' inside their respective directories.
Write documentation for a single module.
Package modules are rendered as ``index.md`` files inside their
corresponding directories, while leaf modules are written as
standalone Markdown pages.
Args:
module: The module to write.
packages: A set of module paths that are identified as packages.
out_dir: The base output directory.
module_is_source: Module is the source folder and to be treated as the root folder.
module: Module to render.
packages: Set of module paths identified as packages.
out_dir: Base directory for generated documentation files.
module_is_source: Whether the module acts as the documentation
root directory.
"""
parts = module.path.split(".")
@@ -160,15 +176,12 @@ class MkDocsRenderer:
module_name, parts = parts[0], parts
if module.path in packages:
# Package → directory/index.md
dir_path = out_dir.joinpath(*parts)
dir_path.mkdir(parents=True, exist_ok=True)
md_path = dir_path / "index.md"
link_target = f"{parts[-1]}/" if parts else None
else:
# Leaf module → parent_dir/<name>.md
dir_path = out_dir.joinpath(*parts[:-1])
dir_path.mkdir(parents=True, exist_ok=True)
@@ -186,14 +199,14 @@ class MkDocsRenderer:
def _render_markdown(self, title: str, module_path: str) -> str:
"""
Generate the Markdown content for a module file.
Generate Markdown content for a module documentation page.
Args:
title: The display title for the page.
module_path: The dotted path of the module to document.
title: Page title displayed in the documentation.
module_path: Dotted import path of the module.
Returns:
A string containing the Markdown source.
Markdown source containing a mkdocstrings directive.
"""
return (
f"# {title}\n\n"
@@ -205,6 +218,13 @@ class MkDocsRenderer:
project: Project,
out_dir: Path,
) -> None:
"""
Ensure that the root ``index.md`` page exists.
Args:
project: Project model used for the page title.
out_dir: Documentation output directory.
"""
root_index = out_dir / "index.md"
if not root_index.exists():
@@ -221,6 +241,16 @@ class MkDocsRenderer:
link_target: str,
title: str,
) -> None:
"""
Ensure that parent package index files exist and contain links to
child modules.
Args:
parts: Module path components.
out_dir: Documentation output directory.
link_target: Link target used in the parent index.
title: Display title for the link.
"""
if len(parts) == 1:
parent_index = out_dir / "index.md"
else:

View File

@@ -1,3 +1,11 @@
"""
Server layer for doc-forge.
This module exposes server implementations used to provide live access
to generated documentation resources. Currently, it includes the MCP
documentation server.
"""
from .mcp_server import MCPServer
__all__ = [

View File

@@ -9,16 +9,21 @@ from mcp.server.fastmcp import FastMCP
class MCPServer:
"""
MCP server for serving a pre-built MCP documentation bundle.
MCP server for serving a pre-generated documentation bundle.
The server exposes documentation resources and diagnostic tools through
MCP endpoints backed by JSON files generated by the MCP renderer.
"""
def __init__(self, mcp_root: Path, name: str) -> None:
"""
Initialize the MCPServer.
Initialize the MCP server.
Args:
mcp_root: Path to the directory containing pre-built MCP JSON resources.
name: Name of the MCP server.
mcp_root: Directory containing the generated MCP documentation
bundle (for example ``index.json``, ``nav.json``, and
``modules/``).
name: Identifier used for the MCP server instance.
"""
self.mcp_root = mcp_root
self.app = FastMCP(name)
@@ -32,19 +37,24 @@ class MCPServer:
def _read_json(self, path: Path) -> Any:
"""
Read and parse a JSON file, returning diagnostic errors if missing.
Load and parse a JSON file.
If the file does not exist, a structured error response is returned
instead of raising an exception.
Args:
path: Path to the JSON file.
path: Path to the JSON file to read.
Returns:
The parsed JSON data or an error dictionary.
Parsed JSON data if the file exists, otherwise an error dictionary
describing the missing resource.
"""
if not path.exists():
return {
"error": "not_found",
"path": str(path),
}
return json.loads(path.read_text(encoding="utf-8"))
# ------------------------------------------------------------------
@@ -53,8 +63,16 @@ class MCPServer:
def _register_resources(self) -> None:
"""
Register MCP resources for index, nav, and individual modules.
Register MCP resource endpoints.
The server exposes documentation resources through the following
endpoints:
- ``docs://index`` Project metadata
- ``docs://nav`` Navigation structure
- ``docs://modules/{module}`` Individual module documentation
"""
@self.app.resource("docs://index")
def index():
return self._read_json(self.mcp_root / "index.json")
@@ -70,26 +88,35 @@ class MCPServer:
)
# ------------------------------------------------------------------
# MCP tools (optional / diagnostic)
# MCP tools
# ------------------------------------------------------------------
def _register_tools(self) -> None:
"""
Register high-level MCP tools for diagnostics.
Register optional MCP diagnostic tools.
These tools provide lightweight endpoints useful for verifying that
the MCP server is operational.
"""
@self.app.tool()
def ping() -> str:
"""Return a simple health check response."""
return "pong"
# ------------------------------------------------------------------
# Server lifecycle
# ------------------------------------------------------------------
def run(self, transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http") -> None:
def run(
self,
transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http",
) -> None:
"""
Start the MCP server.
Args:
transport: MCP transport (default: streamable-http)
transport: Transport mechanism used by the MCP server. Supported
options include ``stdio``, ``sse``, and ``streamable-http``.
"""
self.app.run(transport=transport)