What is MarkdownAI
A Reddit commenter looked at this and said: "I mean I get it but you've basically made jinja/handlebars/eex with a lil flava."
This is a reasonable first impression and a completely wrong conclusion.
Jinja, Handlebars, and EEx are template engines. You write a template with
{{ user.name }}, run a build step, and get an HTML file. The template is gone.
The output goes to a browser. That's the whole job.
MarkdownAI is different in every dimension that matters.
The consumer is an AI, not a browser
Jinja renders for browsers that display HTML. MarkdownAI renders for Claude, which reads Markdown. A browser needs bytes fast. Claude needs accurate facts and focused context - it doesn't benefit from stale data delivered quickly.
Read time, not build time
Template engines run when you deploy. MarkdownAI runs when Claude opens the file. That
means every @query, @env, @http, and
@db directive resolves against the real current state of your system - not
the state from your last build. The document doesn't store values. It fetches them.
The MCP server does the computation so Claude doesn't have to
This is the part that actually matters. When Claude reads a MarkdownAI document through
the MCP integration, every directive resolves in the server layer before Claude sees any
content. The @if conditions have already been evaluated. The database queries
have already run. The environment variables have already been substituted. Claude receives
resolved facts - not a list of conditions it needs to think through.
Without MarkdownAI, Claude hits a doc and has to figure out what's true. It stops to run a shell command. It stops again to check an env var. It stops again to verify a condition. Each stop costs context and interrupts the actual work. With MarkdownAI, those interruptions don't happen. The document arrives pre-resolved.
Something nobody talks about: every time Claude stops to check an environment variable, verify a file exists, confirm a port is open -- that's roughly 2 seconds gone. Tool call out, wait, result back, Claude re-orients, continues.
In a real workflow that's not 1 check. It's 15. That's 30 seconds of dead time and 15 context interruptions where Claude has to re-establish where it was.
MarkdownAI's MCP layer does all of that before Claude touches the session. Environment is pre-validated, state is pre-loaded, constraints are already in context. Claude reads phase 1 and immediately works.
No stops. No re-orientation. No context bloat from housekeeping that a script could handle in milliseconds.
Phases are not template partials
The @phase directive is where the comparison to template engines fully breaks
down. A MarkdownAI phase is a named, lazy-loaded chunk of a workflow document. A 20-phase
runbook doesn't load all 20 phases into Claude's context at once - the MCP server serves
one phase at a time. Claude reads phase 1, works through it, then calls
next_phase to advance. The server returns phase 2. Claude never holds the
full workflow in context. The document manages state. Claude follows steps.
This means a complex deployment runbook, a multi-step debugging workflow, or a long onboarding sequence can be arbitrarily large without ever flooding the AI's context window. Each phase is self-contained, each transition is explicit, and Claude never has to juggle what's relevant now versus what comes later.
Template engines have no concept of this because browsers don't have context windows.
The Problem With Documentation
Here is something that happens at every company: someone writes great documentation. It is accurate. It is detailed. It is helpful.
Then the code changes.
The database schema gets a new field. The API endpoint moves. The environment variable gets renamed. The service goes from port 3000 to 8080. But the docs stay exactly as they were - frozen at the moment someone last had time to update them. Within weeks, the documentation is wrong. Within months, it actively misleads. People stop trusting it. Eventually, they stop reading it.
This is not a process failure. This is physics. Static text cannot track a moving system.
Add @markdownai to the first line of any .md file and it becomes
a live document. Instead of writing values that will go stale, you write directives that
fetch the current value every time the document is rendered. Your database record count is
queried when someone reads the doc, not when you wrote it. Your API response is real. Your
environment variables are live. Your file tree reflects what is actually on disk right now.
Run mai render. The document executes. The output is clean, standard Markdown
with everything resolved. The result is documentation that cannot lie - because it does not
store facts, it fetches them.
How It Works
One line changes everything. Add @markdownai to the top of any
.md file and the mai tool treats it as a live document.
Everything after that line is standard Markdown, extended with directives. Directives
become their results. Conditions are evaluated. Macros expand. Data sources are queried.
The Six Packages
MarkdownAI is a six-package monorepo. Each layer has a single, well-defined job:
| Package | Name | Role |
|---|---|---|
parser/ |
@markdownai/parser |
Converts .md files to an AST - inert, no execution |
engine/ |
@markdownai/engine |
Walks the AST, runs directives, assembles output |
renderer/ |
@markdownai/renderer |
Formats data into 11 output styles (table, bar chart, flow, etc.) |
mcp/ |
@markdownai/mcp |
MCP server - serves live document execution to Claude and other AI tools |
core/ |
@markdownai/core |
The mai binary and all CLI commands |
vscode/ |
markdownai (VS Code) |
Language detection, syntax highlighting, snippets, completions, hover, diagnostics |
The parser is intentionally inert - it never executes anything. Security enforcement lives in the engine, not in individual directives. This means security gates apply regardless of which directive triggers the operation.
Quick Start
Install
npm install -g @markdownai/core
Create a live document
Save this as status.md:
@markdownai
# Project Status
**Branch:** {{ @query git branch --show-current }}
**Tests:** {{ @query pnpm test 2>&1 | tail -1 }}
**Last commit:** {{ @query git log --oneline -1 }}
## Source Files
@list ./src/ match="**/*.ts" | sort | @render type="table" columns="name,size"
## Environment
@if env.NODE_ENV == "production"
Running in **production** mode.
@else
Running in **{{ env.NODE_ENV }}** mode.
@endif
Render it
mai render status.md
Every directive runs. The output is clean Markdown with real data from your system - not values someone typed in last month.
Language Features
MarkdownAI extends standard Markdown with a small set of directives. They compose naturally - you can pipe data through transforms, embed expressions in prose, define reusable blocks, and conditionally show sections based on any runtime value.
Inline Interpolation
Embed live values directly in prose. Works in paragraphs, headings, and table cells.
Use {{ env.VAR }}, {{ @query git branch --show-current }},
or {{ @date format="YYYY-MM-DD" }} anywhere in your document.
Unset variables evaluate to empty string rather than an error.
Environment Variables
Declare the variables your document depends on. required causes
mai validate to fail if the variable is unset. masked
prevents the value from appearing in rendered output. fallback=value
provides a default when the variable is absent.
Macros
Define reusable blocks once with @define and call them anywhere with
@call. Macros can accept named parameters, contain any directives,
and be shared across documents by importing them from a shared file.
Includes and Imports
Pull in file content with @include - supports a line range for embedding
source code in docs. Use @import to import only definitions (macros,
connections, env defaults) from a shared file without rendering its content.
Conditionals
Full conditional system. Check env vars, file existence, string comparisons, and
compound logic with && and ||. The same expression
operators work in @if conditions, data query where filters,
and {{ }} interpolations.
Pipe and Render
Chain transforms before rendering: sort, grep,
head, tail, uniq, wc -l. All
built-in transforms are cross-platform pure Node.js. Feed the result into any of 11
output formats via @render.
Data Sources
MarkdownAI can pull from the filesystem, structured files, databases, HTTP APIs, and shell commands. Every data source is jailed by default - see the Security section for how the gates work.
@list
List filesystem entries, or pull structured data from JSON, YAML, and CSV files.
Filter with glob patterns, where clauses, and depth limits. Specify
columns, sort order, and output format inline.
@list ./src/ match="**/*.ts"
@list ./config.yaml path=$.services
@list ./data.csv where="status == active"
@read
Extract a specific value from a structured file using dot-notation paths. Supports JSON, YAML, TOML, and CSV. Useful for pulling a single config value, a version number, or a record field into prose.
@read ./package.json path=$.version
@read ./config.yaml path=$.server.port
@read ./data.csv column=email where="active == true"
@query
Run a shell command and use its output. Supports a label= option to
store the result for reuse in conditions and interpolations. Disabled by default -
requires an explicit allowlist in your security config.
@query git branch --show-current
@query bash -c "find src -name '*.ts' | wc -l"
@query bash -c "git status --porcelain" label=dirty
@http
Make HTTP requests and embed the response. Assert status codes with
expected=, pass headers and bodies, and cache responses to avoid
hammering external APIs. All @http calls are blocked by default
until you add domains to the allowlist.
@http GET https://api.example.com/status
@http GET {{ env.API_ENDPOINT }}/health expected=200
@http GET https://api.example.com/data @cache persist ttl=3600
@db and @connect
Register a database connection with @connect, then query it using
MarkdownAI's database-agnostic query language. A document querying Postgres looks
identical to one querying MongoDB. Supports SQLite, PostgreSQL, MySQL, MSSQL, and
MongoDB.
@connect primary type="postgres" uri=env.POSTGRES_URI
@db using="primary" find="users" where="active==true"
@db using="primary" aggregate="sales" group="region" sum="revenue" | @render type="bar"
@phase and @graph
Structure a document as a sequential workflow with named phases and
@on complete transitions. The MCP server exposes
list_phases, resolve_phase, and next_phase
tools so AI tools can navigate the workflow programmatically. Use
@graph to build a dependency graph from document relationships.
Security
Security is a first-class concern in MarkdownAI, not an afterthought. Every external operation is jailed by default and must be explicitly enabled. The security layer lives in the engine - it applies regardless of which directive triggers the operation.
📁 Filesystem Confinement
All file access (@include, @read, @list,
@tree) is confined to the document root - by default, the directory
containing the document being rendered. No directive can read files above this root
using path traversal. Content masking runs before any output is written, so sensitive
values matching your configured patterns are replaced with [MASKED].
🔒 Shell Execution Jail
@query shell execution is disabled by default (allowShell: false).
Enable it with an explicit allowlist:
mai security shell add "git *". All executions are subject to deny
patterns, jailRoot confinement, and optional audit logging. Commands
not matching the allowlist are blocked even with the master switch on.
🗃 Database Query Jail
Database access is read-only by default. Write operations, DDL statements, and
full-table scans on large collections are blocked unless explicitly permitted.
Configure allowed operations, blocked SQL keywords, and maximum result sizes via
mai security db commands.
🌐 HTTP Request Jail
All @http calls are blocked by default. Enable HTTP and add specific
domains to the allowlist:
mai security http enable, then
mai security http add-domain api.example.com. Wildcard subdomains
are supported. Requests to non-allowlisted domains fail regardless of configuration.
Immutable built-in rules. Some rules are hardcoded and cannot be disabled by any configuration - not by security policy, not by environment variables, not by document directives. Cloud metadata endpoints are always blocked (169.254.169.254, metadata.google.internal, and all similar credential-theft endpoints). Path traversal sequences are always blocked in jailed contexts. Content masking always runs before caching - credentials can never be stored in plain text. These rules are implemented as frozen, readonly arrays in the engine code.
Caching
Add @cache to any directive to avoid redundant fetches on repeated renders.
Cache modes range from in-memory session caching to persistent disk storage with expiry.
The mock mode is particularly useful for testing - your document runs with real directives
but against predictable fixture data.
| Mode | Behavior |
|---|---|
@cache session |
Store in memory for the current mai run. Gone when the process exits. |
@cache session ttl=N |
Session cache that expires after N seconds within the run. |
@cache persist |
Write to disk. Survives process restarts and re-renders. |
@cache persist ttl=N |
Disk cache with expiry. Stale entries are refetched automatically. |
@cache mock=./fixture.json |
Always return data from a local file - never call the live source. For testing. |
Cache Management
mai cache clear # clear all cached entries
mai cache clear my-doc.md # clear for one document
mai cache show # inspect what is currently cached
mai cache seed my-doc.md # pre-populate by running all fetches ahead of time
Content masking always runs before caching. Sensitive values that match your configured
secret patterns are replaced with [MASKED] before anything is written to
the cache. Credentials can never be stored in plain text, even in session memory.
Output Formats
The renderer supports 11 output formats. All are plain ASCII that renders correctly everywhere - in terminals, GitHub, VS Code preview, and AI context windows.
| Format | Output |
|---|---|
list | Unordered bullet list |
numbered | Ordered numbered list |
links | Clickable markdown links |
table | Grid table with headers |
code | Fenced code block |
inline | Embedded scalar value |
bar | Horizontal ASCII bar chart |
flow | ASCII flow diagram with arrows |
tree | Indented ASCII tree |
timeline | Left-to-right ASCII timeline |
json | Pretty-printed JSON |
Pipe Chains
Formats become most useful when combined with the pipe operator. A data fetch, a few transforms, and a render sink - all on one line:
@list ./src/ | sort | @render type="tree"
@query bash -c "df -h" | @render type="table"
@db using="primary" aggregate="sales" group="name" sum="revenue" | @render type="bar"
@query bash -c "git log --oneline -20" | grep "feat" | @render type="numbered"
AI Integration
MarkdownAI was designed with AI-native workflows in mind. Every feature is built to serve both humans reading rendered output and AI tools consuming live document context. There are three distinct integration points.
💻 MCP Server (9 tools)
Run mai serve to start an MCP server. Claude, Cursor, and any
MCP-compatible tool can query documents, execute directives, navigate phases, and get
live data - without reading raw source files. The 9 exposed tools cover the full
document lifecycle: read_file, list_phases,
resolve_phase, next_phase, call_macro,
get_env, execute_directive, get_constraints,
and invalidate_cache.
🔌 PreToolUse Hook
Run mai init to install a PreToolUse hook into your AI client. When the
AI reads a .md file starting with @markdownai, the hook
intercepts the read and routes it through mai render first. The AI always
receives the rendered, executed output - live data, not the last time someone manually
updated the file.
🤖 AI-Native Features
@prompt embeds instructions for AI consumers that never appear in human
output. @constraint expresses rules as structured, machine-readable blocks.
@consumer=ai targets sections to AI readers only - token-efficient format
mode strips narrative prose, reducing consumption by up to 35% compared to the same
document in human mode.
Skill Context Variables
When a MarkdownAI document is used as a Claude Code skill file, the full slash command invocation context is available inside the document. This enables genuine engine-evaluated dispatch - the engine routes to the correct section before Claude even sees the file.
| Variable | Description |
|---|---|
ARGUMENTS / args | Full raw $ARGUMENTS string |
argsList | Positional args, shell-style parsed |
arg0 arg1 arg2 arg3 | Shorthand positionals |
CLAUDE_EFFORT | low, medium, high, xhigh, or max |
CLAUDE_SESSION_ID | Current Claude Code session ID |
CLAUDE_SKILL_DIR | Directory containing the skill file |
@markdownai
@if ARGUMENTS.startsWith("audit")
@include ./audit-mode.md
@elseif ARGUMENTS.startsWith("build")
@include ./build-mode.md
@elseif ARGUMENTS.startsWith("plan")
@include ./plan-mode.md
@endif
CLI Reference
All commands share these universal flags:
| Flag | Description |
|---|---|
--env <file> | Load a .env file for environment variables |
--cwd <path> | Run as if in a different directory |
--verbose | Show warnings in output |
--strict | Treat warnings as errors |
--silent | Suppress all output except fatal errors and security alerts |
Commands
| Command | Description |
|---|---|
mai render <file> | Execute and print rendered markdown to stdout |
mai render <file> -o <path> | Execute and write result to a file |
mai validate <file> | Check for errors and warnings; exits 1 on error |
mai parse <file> | Output the document AST as JSON |
mai eval "<expression>" | Evaluate a single expression against the environment |
mai strip <file> | Remove all directives; output plain Markdown |
mai serve | Start the MCP server |
mai init | Install the PreToolUse hook into your AI client |
mai build <file> -o <output> | Render and write to disk (alias for render -o) |
mai watch <file> -o <output> | Watch for changes and re-render automatically |
mai cache clear [file] | Clear cache for one document or all |
mai cache show [file] | Inspect cache entries |
mai cache seed <file> | Pre-populate cache by running all fetches |
mai security init | Create or import a security policy |
mai security show | Display the active security policy |
mai security shell enable | Enable shell command execution |
mai security shell add <pattern> | Add a command pattern to the allowlist |
mai security http enable | Enable HTTP requests |
mai security http add-domain <domain> | Add a domain to the allowlist |
mai security db add <connection> | Register a database connection |
mai security db test "<query>" | Test whether a query would be permitted |
Directive Reference
Every directive supported by MarkdownAI, grouped by category.
Header and Activation
| Directive | Description |
|---|---|
@markdownai | Activate MarkdownAI processing. Must be on line 1 (or first line after YAML frontmatter). |
@markdownai v1.0 | Pin to a specific specification version. |
@markdownai shell-inline="passthrough" | Let Claude Code's !`cmd` syntax pass through unintercepted. |
Environment and Variables
| Directive | Description |
|---|---|
@env VAR required | Declare a required variable. mai validate fails if unset. |
@env VAR required masked | Required and suppress from output. |
@env VAR fallback=value | Declare a default when variable is not set. |
{{ env.VAR }} | Inline interpolation of any environment variable. |
{{ var ?? "default" }} | Interpolation with explicit fallback. |
Macros and Composition
| Directive | Description |
|---|---|
@define name ... @end | Define a reusable macro block. |
@call name | Expand a macro at this location. |
@call name param=value | Expand a macro with named parameters. |
@include ./path.md | Include another file's content verbatim. |
@include ./file.ts lines=N-M | Include specific line range. |
@import ./path.md | Import definitions (macros, connections, env defaults) from another file. |
@import ./path.md @cache session | Import with session caching. |
Conditionals
| Directive | Description |
|---|---|
@if <expr> | Include content if expression is true. |
@elseif <expr> | Alternate branch. |
@else | Fallback branch. |
@endif | Close conditional block. |
Operators: ==, !=, <, >,
<=, >=, &&, ||,
!, startsWith, endsWith, includes,
file.exists, file.isFile, file.isDir
Data Sources
| Directive | Description |
|---|---|
@list <path> | List filesystem entries or structured data from JSON/YAML/CSV. |
@read <path> | Read and extract a value from a structured file. |
@tree <path> | Render a directory tree. |
@date format="..." | Current date/time in any format. |
@count <path> "<pattern>" | Count files matching a pattern. |
@connect <name> type="<db>" uri=env.VAR | Register a named database connection. |
@db using="<name>" find="<collection>" | Query a collection or table. |
@db using="<name>" raw="<query>" | Run a native query. |
@query <command> | Execute a shell command and use its stdout. |
@query <command> label=name | Execute and store result in named label. |
@http <METHOD> <url> | Make an HTTP request and use the response body. |
@http <METHOD> <url> expected=<code> | Assert response status code. |
Pipeline and Rendering
| Directive | Description |
|---|---|
| sort / sort -n / sort -r | Sort alphabetically, numerically, or in reverse. |
| grep <pattern> / grep -v / grep -i | Include, exclude, or case-insensitive match. |
| head -N / tail -N | Keep first or last N lines. |
| uniq | Deduplicate consecutive lines. |
| wc -l | Count lines. |
@render type="<format>" | Render accumulated pipeline result in the specified format. |
Phases and Graphs
| Directive | Description |
|---|---|
@phase <name> | Open a named phase block. |
@on complete ... @end | Actions to take when a phase completes. |
@graph | Build a dependency graph from document relationships. |
Caching
| Directive | Description |
|---|---|
@cache session | Cache in memory for the current run. |
@cache session ttl=N | Session cache, expires after N seconds. |
@cache persist | Cache to disk across runs. |
@cache persist ttl=N | Disk cache with expiry. |
@cache mock=./file.json | Always use fixture file, never call live source. |
AI-Native
| Directive | Description |
|---|---|
@consumer=ai | Tag a block for AI consumers only. |
@consumer=human | Tag a block for human readers only. |
@prompt ... @end | Embed instructions for AI consumers (not in human output). |
@constraint <name> ... @end | Declare a machine-readable rule. |
@note ... @end | Human-readable source comment, always stripped. Add visible to render as a blockquote. |
VS Code Extension
The MarkdownAI VS Code extension provides full IDE support for .md files
that begin with @markdownai. Open the Extensions panel
(Ctrl+Shift+X / Cmd+Shift+X), search for
MarkdownAI, and click Install. Or install from the
VS Code Marketplace.
Live Preview
Open any MarkdownAI file and click the preview icon in the editor title bar, or run MarkdownAI: Open MarkdownAI Preview from the Command Palette. The panel opens to the side, runs the engine on the saved file, and shows rendered output. It refreshes automatically every time you save.
Language Detection
Any .md file whose first line is @markdownai is
automatically re-tagged as the markdownai language type, activating all
extension features. Files with YAML frontmatter before @markdownai are
also detected.
Syntax Highlighting
TextMate grammar covers all MarkdownAI directives: @markdownai,
@define, @phase, @if, @include,
@import, @env, @call, @list,
@read, @db, @http, @query,
@render, @prompt, @constraint,
@cache, and {{ }} interpolations.
Snippets and Completion
15+ tab-triggered snippets for all directives. Type @def and expand
to a complete @define/@end block, @if to
a full conditional, @phase to a phase skeleton. As you type
@, completions show all valid directives with descriptions.
Navigation
Hover over any @define name or @call to see the macro
definition inline. Press F12 on any @call name to jump to
the @define that declares it, even across files linked by
@import. Right-click any @define name for Find All References.
Diagnostics
The extension checks documents as you type and reports:
@call to undefined macros (error),
@include/@import pointing to missing files (error),
@env variables without a fallback (warning, configurable),
and undefined macros in @call positions (warning, configurable).
Architecture
MarkdownAI is a six-package npm workspaces monorepo, all TypeScript with strict mode throughout. Each package has a single, well-bounded responsibility.
| Package | Role |
|---|---|
@markdownai/parser |
AST production only. Parses .md files to a typed node tree. Intentionally inert - no execution, no I/O. |
@markdownai/engine |
Walks the AST, runs directives, resolves env variables, manages the pipe chain, applies caching, and enforces security gates. |
@markdownai/renderer |
Formats data into 11 output styles. Accepts structured data from the engine and produces ASCII-safe Markdown. |
@markdownai/mcp |
MCP server with 9 tools. Serves live document execution, phase navigation, and constraint access to AI tools. |
@markdownai/core |
The mai binary. Wires the other packages together and exposes all CLI commands. |
markdownai (VS Code) |
Language detection, TextMate grammar, snippets, completions, hover, go-to-definition, find references, diagnostics, live preview. |
The separation between parser and engine is intentional. The parser never executes anything - you can safely parse any document in any environment. Security enforcement sits in the engine, between the directive runner and every external system. 754 tests across all packages, including unit tests for every directive, E2E CLI pipeline tests, MCP protocol conformance tests, and a dedicated AI-native feature test suite.
Installation
MarkdownAI installs globally via npm. One command puts mai on your path.
Node.js 18 or higher required.
Install the CLI
npm install -g @markdownai/core
mai --version
Install the VS Code extension
Open Extensions (Ctrl+Shift+X), search MarkdownAI, click Install. Gives you syntax highlighting, snippets, completions, hover, diagnostics, and live preview.
Install the PreToolUse hook
Lets your AI client (Claude Code, Cursor) automatically render MarkdownAI documents before reading them. Claude always sees live data, not stale source files.
mai init
Initialize a security policy
Before using data sources that touch external systems, set up a security policy for
your project. This creates .markdownai/security.json and walks you
through enabling the gates you actually need.
cd your-project
mai security init
Platform note: Works on macOS, Linux, and Windows. WSL is recommended
on Windows for the shell command features (@query with shell execution
enabled). Built-in pipe transforms (sort, grep, head, tail, uniq, wc -l) are
cross-platform pure Node.js - no shell required.