Source code for egile_mcp_starter.generator

"""Project generator for MCP servers using cookiecutter."""

from pathlib import Path
from typing import Any, Dict, Optional

try:
    from cookiecutter.main import cookiecutter  # type: ignore
except ImportError:
    cookiecutter = None

from .plugins.registry import get_registry


[docs] class MCPProjectGenerator: """Generator for MCP server projects using the FASTMCP framework."""
[docs] def __init__( self, output_dir: str = ".", no_input: bool = False, config_file: Optional[str] = None, default_config: bool = False, verbose: bool = False, template: str = "mcp", project_name: Optional[str] = None, ): """Initialize the MCP project generator. Args: output_dir: Directory where the project will be created no_input: Skip interactive prompts and use defaults config_file: Path to cookiecutter config file default_config: Use default configuration values verbose: Enable verbose output template: Name of the template to use (default: "mcp") project_name: Override the project name """ self.output_dir = Path(output_dir).resolve() self.no_input = no_input or default_config self.config_file = config_file self.verbose = verbose self.template_name = template self.project_name = project_name # Get the template registry self.registry = get_registry() # Validate template exists if not self.registry.get_plugin(template): available = ", ".join(self.registry.get_plugin_names()) raise ValueError( f"Template '{template}' not found. Available templates: {available}" )
[docs] def generate(self) -> Path: """Generate a new MCP server project. Returns: Path to the generated project directory Raises: Exception: If project generation fails """ if cookiecutter is None: raise Exception( "cookiecutter is not installed. Please install it with: " "pip install cookiecutter" ) # Get the template plugin plugin = self.registry.get_plugin(self.template_name) if not plugin: raise Exception(f"Template '{self.template_name}' not found") template_dir = plugin.get_template_path() if self.verbose: print(f"🔨 Generating MCP server project in: {self.output_dir}") print(f"📁 Using template: {plugin.name} ({plugin.description})") print(f"📂 Template directory: {template_dir}") try: # Get default context from plugin default_context = plugin.get_default_context() # Override project name if provided if self.project_name: default_context["project_name"] = self.project_name if self.verbose: print(f"🏷️ Project name override: {self.project_name}") # Apply pre-generation hook if not self.no_input: # In interactive mode, cookiecutter will handle the prompts context = default_context else: # In non-interactive mode, use defaults context = default_context context = plugin.pre_generate_hook(context) # Use cookiecutter to generate the project if self.no_input: project_path = cookiecutter( str(template_dir), output_dir=str(self.output_dir), no_input=True, extra_context=context, config_file=self.config_file, ) else: project_path = cookiecutter( str(template_dir), output_dir=str(self.output_dir), no_input=False, config_file=self.config_file, ) project_path_obj = Path(project_path) # Apply post-generation hook plugin.post_generate_hook(project_path_obj, context) return project_path_obj except Exception as e: raise Exception(f"Failed to generate MCP server project: {e}") from e
[docs] def get_default_context(self) -> Dict[str, Any]: """Get the default context variables for the template. Returns: Dictionary of default template variables """ plugin = self.registry.get_plugin(self.template_name) if plugin: return plugin.get_default_context() # Fallback to original defaults return { "project_name": "my-mcp-server", "project_slug": "my_mcp_server", "project_description": "A Model Context Protocol server built with FASTMCP", "author_name": "Your Name", "author_email": "your.email@example.com", "version": "0.1.0", "python_version": "3.11", "use_docker": "y", "use_github_actions": "y", "use_pre_commit": "y", "license": "MIT", }
[docs] def list_available_templates(self) -> Dict[str, str]: """List all available templates. Returns: Dictionary mapping template names to descriptions """ return { plugin.name: plugin.description for plugin in self.registry.list_plugins() }