LLM prompts grow into copy-pasted walls of text that drift across agents, models, and environments. MDS gives you variables, functions, imports, and conditionals so you can write prompts once and compose them everywhere, compiled to clean Markdown.
Built for AI engineers who manage prompt libraries across agents, models, and environments.
Install via npm (Node or browser):
npm install @mdscript/mdsOr install the CLI (Rust):
cargo install mds-cliCreate a prompt template (system.mds):
---
model: claude-sonnet
tools: [search, calculator]
---
@import "./safety.mds" as guard
@import "./personas.mds" as persona
{persona.code_reviewer("TypeScript")}
{guard.safety_rules()}
## Available Tools
@for tool in tools:
- **{tool}**
@end
@if model == "claude-sonnet":
Use extended thinking for complex tasks.
@end
Compile it:
mds build system.mds # writes system.md
mds build system.mds -o - # stdoutUnlike general-purpose template engines, MDS is Markdown-native: no delimiters to escape, no runtime to configure. The compiler catches undefined variables, import cycles, and arity mismatches at build time, not in production.
- Variables: YAML frontmatter or runtime
--set KEY=VALUEflags - Conditionals:
@if/@elseif/@else/@endwith negation and equality comparisons - Loops:
@for item in list:iteration over arrays and objects - Functions:
@definereusable blocks with parameters - Imports/Exports: modular prompt libraries with alias, merge, and selective imports
- Messages:
@message role: … @endblocks compile to a JSON[{role, content}]array via--format messages - Security: path traversal guards, symlink rejection, file size limits
- Rich errors: source-span diagnostics with line/column context
mds build [FILE] [OPTIONS] Compile an MDS template to Markdown
mds watch [FILE|DIR] [OPTIONS] Watch and auto-recompile on save
mds check [FILE] [OPTIONS] Validate without rendering
mds init [FILENAME] Create a starter MDS file
Global options:
-q, --quiet Suppress status messages (applies to all commands)
Build/Watch options:
-o, --output <PATH> Output file, or "-" for stdout (build and single-file watch only;
rejected in directory watch mode — use --out-dir instead)
--out-dir <DIR> Output directory (build/single-file watch: <stem>.md; dir-mode watch: mirrors source subtree)
--vars <FILE> JSON file with variable overrides (reloaded each rebuild)
--set KEY=VALUE Set a single variable (repeatable)
--format <FORMAT> markdown (default) or messages (JSON chat array;
messages is single-file only — rejected in directory watch mode)
Watch-only options:
--clear Clear terminal before each rebuild (only when stderr is a TTY)
--debounce <MS> Debounce window in milliseconds (default: 100)
--poll-interval <MS> Liveness-probe interval in milliseconds (default: 1000).
0 disables self-heal (native events only). Clamped to ≥50ms.
The watcher self-heals after a watched dir/root is deleted and
recreated; --poll-interval controls how quickly it detects recovery.
Exit codes:
0 Success (or clean Ctrl+C in watch mode)
1 Template error (syntax, undefined variable, arity mismatch)
2 I/O error (file not found, not an MDS file), or invalid CLI argument (clap parse error)
3 Resource limit exceeded
Watch a single file and recompile whenever it (or any of its imports) changes:
mds watch system.mds # recompiles to system.md on every save
mds watch system.mds -o - # stream output to stdout
mds watch system.mds --clear # clear terminal before each rebuild
mds watch system.mds --vars vars.json # with variable overridesWatch an entire directory:
mds watch src/ # compile each .mds next to its source
mds watch src/ --out-dir dist # mirror source subtree under dist/
# src/a/b/foo.mds → dist/a/b/foo.md (not dist/foo.md)Breaking change (next release): Directory mode with
--out-dirormds.json output_dirnow mirrors the source subtree instead of writing flat stems. Old flat outputs are orphaned and must be removed manually.
Single-file mode tracks transitive imports: editing any @import-ed file triggers a
recompile of the entry. Directory mode tracks a reverse-dependency graph: editing a
shared partial rebuilds all transitive importers automatically.
-
_-prefixed files are partials: tracked in the dependency graph and their importers are rebuilt when edited, but the partial itself never emits its own.mdoutput. -
Cross-root imports: if a file imports a partial located outside the watched root (e.g.
../shared/_x.mds), editing that external partial rebuilds its in-root importers. The external file is never compiled to its own output. -
Status lines and warnings go to stderr (pipe-safe). Compiled content only goes to stdout when
-o -. -
--quietsuppresses status and warnings; compile errors still print and the watcher keeps running. -
Ctrl+C exits with code 0 and prints
Stopped watching. -
--varsfile is reloaded from disk on every rebuild; edits to it trigger a recompile.
Import .mds templates directly in Vite, Rollup, and Webpack projects:
import systemPrompt from './prompts/system.mds';
// systemPrompt is the compiled Markdown string| Package | Bundler | Version |
|---|---|---|
@mdscript/vite-plugin |
Vite | ^5 || ^6 || ^7 || ^8 |
@mdscript/rollup-plugin |
Rollup | ^3 || ^4 |
@mdscript/webpack-loader |
Webpack | ^5 |
All plugins require @mdscript/mds as a peer dependency and accept { vars?: Record<string, unknown> } for runtime template variables. See each package README for configuration details.
TypeScript module declarations (.mds → string) are provided by @mdscript/bundler-utils/mds.
import { init, compile, compileFile, compileMessages, isMdsError } from '@mdscript/mds';
await init();
// Compile a string
const { output } = compile('---\nname: World\n---\nHello {name}!\n');
// Override variables at runtime
const result = compile(source, { vars: { env: 'production' } });
// Compile a file (resolves @import directives)
const { output, dependencies } = await compileFile('./prompts/system.mds');
// Compile @message blocks to a structured chat array
const { messages, warnings } = compileMessages(source);
// messages: [{ role: 'system', content: '...' }, { role: 'user', content: '...' }]
// Error handling
try {
compile('Hello {undefined_var}!');
} catch (err) {
if (isMdsError(err)) console.error(err.code, err.span);
}@mdscript/mds uses a native addon on Node.js with an automatic WASM fallback, and runs in the browser via WASM.
let output = mds::compile(Path::new("template.mds"), None)?;
let output = mds::compile_str("---\nname: World\n---\nHello {name}!\n")?;Runnable templates, a Node.js API demo, and Vite/Rollup/Webpack integration apps
live in examples/.
See spec.md for the full MDS v0.2.0 language specification.
Contributions are welcome! See CONTRIBUTING.md for the local workflow and quality gates.
Please report vulnerabilities privately via GitHub's private vulnerability reporting, not public issues. See SECURITY.md for the security model, built-in resource limits, and supported versions.
MIT. See LICENSE.