MCP Server Template for Cursor IDE

""" Template loader for MCP Simple Tool. This module provides functions to load and render templates from the package. """ import functools import os import re from typing import Any, Dict, List, Optional, Tuple import yaml from jinja2 import Environment, FileSystemLoader, select_autoescape from packaging import version # Cache for template content to avoid repeated file I/O _template_cache: Dict[str, str] = {} # Cache for template metadata _metadata_cache: Dict[str, Dict[str, Any]] = {} # Cache for template version registry _version_registry: Dict[str, Dict[str, str]] = {} def _get_templates_dir() -> str: """ Get the absolute path to the templates directory. Returns: str: The absolute path to the templates directory. """ # Use importlib.resources to get the path to the templates directory # This works even when the package is installed return os.path.dirname(os.path.abspath(__file__)) @functools.lru_cache(maxsize=32) def get_template_env() -> Environment: """ Get the Jinja2 environment for rendering templates. Returns: Environment: The Jinja2 environment. """ templates_dir = _get_templates_dir() return Environment( loader=FileSystemLoader(templates_dir), autoescape=select_autoescape(["html", "xml"]), trim_blocks=True, lstrip_blocks=True, ) def _parse_template_metadata(content: str) -> Tuple[Dict[str, Any], str]: """ Parse the YAML front matter from a template. Args: content: The template content. Returns: Tuple[Dict[str, Any], str]: A tuple containing the metadata and the template content. """ # Check if the template has YAML front matter if content.startswith("---"): # Find the end of the front matter end_index = content.find("---", 3) if end_index != -1: # Extract the front matter front_matter = content[3:end_index].strip() # Parse the front matter as YAML try: metadata = yaml.safe_load(front_matter) # Extract the template content template_content = content[end_index + 3 :].strip() return metadata, template_content except yaml.YAMLError: # If the front matter is not valid YAML, return empty metadata pass # If no front matter or invalid YAML, return empty metadata and the original content return {}, content def _build_version_registry() -> None: """ Build the version registry for all templates. This scans the templates directory and builds a registry of all available versions. """ global _version_registry if _version_registry: # Registry already built return templates_dir = _get_templates_dir() prompts_dir = os.path.join(templates_dir, "prompts") # Check if the prompts directory exists if not os.path.isdir(prompts_dir): return # Scan the prompts directory for template directories for template_name in os.listdir(prompts_dir): template_dir = os.path.join(prompts_dir, template_name) # Skip if not a directory if not os.path.isdir(template_dir): continue # Initialize the version registry for this template _version_registry[template_name] = {} # Scan the template directory for version files version_files = [] for filename in os.listdir(template_dir): # Check if the filename matches the old version pattern (e.g., 1.0.0.md) if re.match(r"^\d+\.\d+\.\d+\.md$", filename): version_str = filename[:-3] # Remove the .md extension version_files.append((version_str, filename)) # Check if the filename matches the new version pattern (e.g., change_v1.0.0.md) elif re.match(r"^[a-z_]+_v\d+\.\d+\.\d+\.md$", filename): # Extract the version from the filename (e.g., "1.0.0" from "change_v1.0.0.md") match = re.search(r"_v(\d+\.\d+\.\d+)\.md$", filename) if match: version_str = match.group(1) version_files.append((version_str, filename)) # Sort the version files by version number (newest first) version_files.sort(key=lambda x: version.parse(x[0]), reverse=True) # Add the versions to the registry for version_str, filename in version_files: _version_registry[template_name][version_str] = filename # Set the latest version if version_files: latest_version = version_files[0][0] _version_registry[template_name]["latest"] = latest_version def get_template_versions(template_name: str) -> List[str]: """ Get all available versions for a template. Args: template_name: The name of the template. Returns: List[str]: A list of available versions, sorted from newest to oldest. """ _build_version_registry() if template_name not in _version_registry: return [] # Get all versions except "latest" versions = [v for v in _version_registry[template_name].keys() if v != "latest"] # Sort the versions by version number (newest first) versions.sort(key=lambda x: version.parse(x), reverse=True) return versions def get_latest_version(template_name: str) -> Optional[str]: """ Get the latest version for a template. Args: template_name: The name of the template. Returns: Optional[str]: The latest version, or None if the template does not exist. """ _build_version_registry() if template_name not in _version_registry: return None return _version_registry[template_name].get("latest") def load_template(template_path: str) -> str: """ Load a template from the package. Args: template_path: The path to the template, relative to the templates directory. Returns: str: The template content. Raises: FileNotFoundError: If the template file does not exist. """ # Check if the template is already cached if template_path in _template_cache: return _template_cache[template_path] # Get the absolute path to the template templates_dir = _get_templates_dir() full_path = os.path.join(templates_dir, template_path) # Check if the template file exists if not os.path.isfile(full_path): raise FileNotFoundError(f"Template file not found: {template_path}") # Load the template content with open(full_path, "r") as f: template_content = f.read() # Cache the template content _template_cache[template_path] = template_content return template_content def get_template_metadata(template_path: str) -> Dict[str, Any]: """ Get the metadata for a template. Args: template_path: The path to the template, relative to the templates directory. Returns: Dict[str, Any]: The template metadata. Raises: FileNotFoundError: If the template file does not exist. """ # Check if the metadata is already cached if template_path in _metadata_cache: return _metadata_cache[template_path] # Load the template content content = load_template(template_path) # Parse the metadata metadata, _ = _parse_template_metadata(content) # Cache the metadata _metadata_cache[template_path] = metadata return metadata def render_template(template_path: str, **kwargs: Any) -> str: """ Render a template with the given variables. Args: template_path: The path to the template, relative to the templates directory. **kwargs: The variables to pass to the template. Returns: str: The rendered template. Raises: FileNotFoundError: If the template file does not exist. jinja2.exceptions.TemplateError: If there is an error rendering the template. """ env = get_template_env() template = env.get_template(template_path) return template.render(**kwargs) def render_prompt_template( template_name: str, version_str: str = "latest", **kwargs: Any ) -> str: """ Render a prompt template with the given variables. Args: template_name: The name of the prompt template. version_str: The version of the template to use. Defaults to "latest". **kwargs: The variables to pass to the template. Returns: str: The rendered prompt template. Raises: FileNotFoundError: If the template file does not exist. jinja2.exceptions.TemplateError: If there is an error rendering the template. ValueError: If the specified version does not exist. """ _build_version_registry() # Check if the template exists if template_name not in _version_registry: raise FileNotFoundError(f"Template not found: {template_name}") # Resolve the version if version_str == "latest": version_str = _version_registry[template_name].get("latest") if not version_str: raise ValueError(f"No versions found for template: {template_name}") elif version_str not in _version_registry[template_name]: # Try to find the closest version available_versions = get_template_versions(template_name) if not available_versions: raise ValueError(f"No versions found for template: {template_name}") # Find the highest version that is less than or equal to the requested version requested_ver = version.parse(version_str) for v in available_versions: if version.parse(v) <= requested_ver: version_str = v break else: # If no suitable version is found, use the oldest version version_str = available_versions[-1] # Get the filename from the registry filename = _version_registry[template_name][version_str] # Build the template path template_path = f"prompts/{template_name}/{filename}" # Load the template content content = load_template(template_path) # Parse the metadata and template content metadata, template_content = _parse_template_metadata(content) # Create a new template with just the content (without the front matter) env = get_template_env() template = env.from_string(template_content) # Render the template return template.render(**kwargs)