Template Rendering Guide¶
This guide explains how to work with templates and frontmatter in mcp-guide.
Overview¶
mcp-guide uses Mustache/Chevron templates with YAML frontmatter for dynamic content generation. The rendering system provides three core functions for different use cases:
- render_template() - Render complete templates with partials support
- process_frontmatter() - Parse and process frontmatter with requirements checking
- process_file() - Read and process non-template files
Template Files¶
Templates are Mustache files (.mustache extension) with optional YAML frontmatter:
---
type: agent/instruction
description: Brief description of the template
requires-feature: workflow
---
# Template Content
Hello {{name}}!
{{#items}}
- {{value}}
{{/items}}
Note: lists are represented internally as objects:
python { value: str, # value of the item first: bool, # true if first in list, otherwise false last: bool # true if last in list, otherwise false }This allows smart formatting since you can add a prefix to the first item,{{#first}}prefix{{/first}}{{value}}and add text between each item{{value}}{{^last}}, {{/last}}
Frontmatter Fields¶
Common frontmatter fields:
- type: Content type (
user/information,agent/information,agent/instruction) - description: Brief description (rendered as template)
- instruction: Custom instruction for agents (rendered as template)
- requires-*: Conditional requirements (e.g.,
requires-workflow: true) -
includes: List of partial templates to include¶
Note:
All frontmatter keys are case-insensitive and normalized to lowercase.
Rendering Templates¶
render_template()¶
Use render_template() for complete template rendering with partials support:
from mcp_guide.render import render_template
from mcp_guide.render.context import TemplateContext
result = await render_template(
file_info=file_info,
base_dir=Path("templates"),
project_flags={"feature": True},
context=TemplateContext({"name": "World"}),
)
if result:
print(result.content)
print(result.instruction)
Returns: RenderedContent if successful, None if filtered by requires-* directives
Raises:
- RuntimeError: Template rendering fails
- FileNotFoundError: Template file not found
- PermissionError: Insufficient permissions
- UnicodeDecodeError: Invalid UTF-8
Process:
1. Parse frontmatter
2. Check requires-* directives against project_flags
3. Build context: base → frontmatter vars → caller context
4. Render template with Chevron and partials
process_frontmatter()¶
Use process_frontmatter() for frontmatter processing without file I/O:
from mcp_guide.render.frontmatter import process_frontmatter
processed = await process_frontmatter(
content=raw_content,
requirements_context={"feature": True},
render_context=TemplateContext({"name": "World"}),
)
if processed:
print(processed.frontmatter)
print(processed.content)
Returns: ProcessedFrontmatter if requirements met, None if filtered
Process:
1. Parse frontmatter from content
2. Check requires-* directives
3. Render instruction and description fields as templates
process_file()¶
Use process_file() for non-template files:
from mcp_guide.render.frontmatter import process_file
processed = await process_file(
file_info=file_info,
base_dir=Path("."),
requirements_context={"feature": True},
render_context=TemplateContext({"name": "World"}),
)
if processed:
print(processed.content)
Returns: ProcessedFrontmatter if requirements met, None if filtered
Note: For non-template files only. Templates must use render_template() for proper partials support.
Requirements Checking¶
Use requires-* frontmatter fields to conditionally include content:
---
type: agent/instruction
requires-feature: workflow
requires-openspec: true
---
Content is filtered out (returns None) if any requirement is not met.
Exception Handling¶
The rendering API raises exceptions for errors rather than returning None:
try:
result = await render_template(
file_info=file_info,
base_dir=base_dir,
project_flags=project_flags,
context=context,
)
if result is None:
# Filtered by requires-* (not an error)
continue
# Use rendered content
process(result.content)
except (FileNotFoundError, PermissionError, UnicodeDecodeError) as e:
# File I/O errors
logger.error(f"Failed to read template: {e}")
except Exception as e:
# Unexpected errors
logger.exception(f"Unexpected error rendering template")
Key Points:
- None return = filtered by requirements (not an error)
- Exceptions = actual errors that need handling
- Use logger.exception() for unexpected errors
- Use logger.error() for expected file I/O errors
Batch Processing¶
When processing multiple files, catch exceptions per-file to prevent one error from terminating the batch:
results = []
for file_info in files:
try:
rendered = await render_template(...)
if rendered is None:
continue # Filtered by requires-*
results.append(rendered)
except (FileNotFoundError, PermissionError, UnicodeDecodeError) as e:
logger.error(f"File error: {file_info.path}: {e}")
continue
except Exception as e:
logger.exception(f"Unexpected error: {file_info.path}")
continue
Return Types¶
RenderedContent¶
@dataclass
class RenderedContent(Content):
template_path: Path
template_name: str
@property
def template_type(self) -> str
@property
def instruction(self) -> Optional[str]
Extends Content (frontmatter, content, lengths) with template metadata.
ProcessedFrontmatter¶
@dataclass
class ProcessedFrontmatter:
frontmatter: Frontmatter
content: str
frontmatter_length: int
content_length: int
Contains parsed frontmatter and content without template-specific metadata.
Frontmatter Constants¶
from mcp_guide.render.content import (
FM_INSTRUCTION,
FM_TYPE,
FM_DESCRIPTION,
FM_REQUIRES_PREFIX,
FM_CATEGORY,
FM_USAGE,
FM_ALIASES,
FM_INCLUDES,
)
Use these constants when accessing frontmatter fields to ensure consistency.
Best Practices¶
- Use render_template() for templates - Always use
render_template()for.mustachefiles to ensure partials work correctly - Use process_file() for non-templates - Use
process_file()for markdown and other non-template files - Handle None returns - Check for
Nonereturns (filtered content) before using results - Catch specific exceptions - Catch file I/O exceptions separately from unexpected errors
- Use case-insensitive keys - Frontmatter keys are normalized to lowercase
- Declare argrequired - Use
argrequiredfrontmatter field for flags that need space-separated values
See Also¶
- Command Authoring Guide - Creating custom commands
- Content Management - Working with content