"""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()
}