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
-
cache: Explicit response-cache policy (
long,medium,short, optionallypublic,private, orshared)¶Note:
All frontmatter keys are case-insensitive and normalized to lowercase.
Cache declarations are conservative. none, no-cache, and malformed values disable
caching; malformed values are logged as content-author diagnostics. Ordinary non-template
Markdown without cache defaults to long/public, while rendered content omits caching
unless it declares a policy. shared is an alias for public.
long, medium, and short map to 24 hours, 15 minutes, and 2 minutes. A policy
without a scope is public. Partial rendering combines policies using no-cache first,
then the shortest TTL and private scope. Template interpolation alone does not require
private scope; use it when the rendered output varies by project or feature state.
Interactive elicitation properties¶
See Elicitation Authoring for the complete author-facing declaration, branching, protocol, and continuation-state reference.
elicitation is a composable frontmatter property for an interactive command or selected skill
entrypoint. Before rendering either body, Guide collects its own declaration and declarations from
eligible partials explicitly named in includes. This pre-render step lets a frontmatter-only
partial provide a shared form without emitting its body.
Relevant frontmatter values are rendered from the normal template context before Guide evaluates
them. This includes nested elicitation declarations and document properties, while requires-*
remains a gating declaration evaluated before rendering.
The normal renderer remains unchanged: listing a partial does not interpolate it. Use {{> name}}
when its body should appear. requires-* applies before a partial contributes input properties, and
duplicate form identifiers or schema property names across contributors are rejected with a
source-aware diagnostic. Ordinary content rendering and non-entrypoint skill members do not evaluate
elicitation, because they cannot safely run an MCP request/response interaction.
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
Recommendations¶
Use the recommend lambda at an instruction boundary to point an agent to a
related Guide capability:
{{#recommend}}skill:workflow-review{{/recommend}}
Valid types are skill, command, content, and tool; an unprefixed value
is content. The rendered document contains a fluent reference and one compact
JSON footnote for each unique recommendation. The footnote supplies the URI
and, where applicable, the configured tool invocation. Recommendation targets
are validated while rendering: blank values, unknown type labels, and targets
that do not exist make the template fail to render.
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