Markdown viewer and reviewer with live reload, persistent annotations, and AI agent integration.
Open .md files in the browser, annotate inline, approve sections, and export structured feedback as YAML — all from the terminal. Works standalone or as an MCP server for AI agents (Claude Code, Cursor, etc.).
- A CLI tool that renders Markdown in the browser with live reload
- An annotation system where you select text and add tagged comments (bug, question, suggestion, nitpick)
- A review workflow with section-level approval (approve/reject per heading)
- An MCP server that lets AI agents open files, read annotations, and resolve feedback programmatically
- Not a Markdown editor — you edit in your own editor, mdProbe renders and annotates
- Not a static site generator — it runs a local server for live preview
- Not exclusive to AI — works perfectly as a standalone review tool
npm install -g @henryavila/mdprobe
mdprobe setup
mdprobe README.mdOr run without installing:
npx @henryavila/mdprobe README.mdRequirements: Node.js 20+, a modern browser (see Browser Requirements).
Select any text in the browser, choose a tag, write a comment, and save.
| Tag | Meaning |
|---|---|
bug |
Something is wrong |
question |
Needs clarification |
suggestion |
Improvement idea |
nitpick |
Minor style/wording |
| State | Meaning |
|---|---|
open |
Active annotation, anchored confidently |
drifted |
Source text changed; annotation was re-located with fuzzy match but requires human confirmation (shown with dashed amber underline) |
orphan |
Anchor failed completely after all recovery steps; surfaced in a side panel section without inline highlight |
resolved |
Addressed; greyed out |
Annotations are stored in .annotations.yaml sidecar files — human-readable, git-friendly. See docs/SCHEMA.md for the full schema reference.
mdprobe README.md # Open a single file
mdprobe docs/ # Discover all .md files recursivelyStarts a server, opens the browser, and watches the source file for changes. Edit in your editor — the browser updates instantly. Press Ctrl+C to stop.
Multiple calls share the same running server: a second mdprobe invocation detects the existing process via lock file, adds its files via POST /api/add-files, and exits — so you never accumulate stale processes.
mdprobe -d docs/ # Start in background, exit immediately
mdprobe CHANGELOG.md # Join the running server, add the file
mdprobe stop # Kill the server and clean the lock file-d/--detach starts the server process detached from the terminal. Subsequent invocations join it normally. Use mdprobe stop (or mdprobe stop --force) to shut it down.
mdprobe spec.md --onceBlocks the process until you click "Finish Review" in the UI. Exits with the list of created annotation files — useful for pipelines that need human sign-off before continuing. --once always creates an isolated server instance (does not join the singleton).
When working with AI agents, the agent uses the MCP tools instead of --once:
Agent writes spec.md
↓
Agent calls mdprobe_view → browser opens, server stays running
↓
Human reads, annotates, approves/rejects sections
↓
Human tells agent via chat: "done reviewing"
↓
Agent calls mdprobe_annotations → reads all feedback
↓
Agent fixes bugs, answers questions, evaluates suggestions
↓
Agent reports changes, asks human to confirm
↓
Agent calls mdprobe_update → resolves annotations
↓
Human sees resolved items in real-time (greyed out)
The server stays running across the entire conversation. Multiple files can be reviewed in the same session.
mdprobe update # interactive: confirms before installing
mdprobe update --yes # skip the confirmation prompt
mdprobe update --dry-run # show what would happen, don't install
mdprobe update --force # reinstall even if already on latestDetects your package manager (npm/pnpm/yarn/bun), stops the running server if any, installs the latest version, and prints "What's new" from the new release's changelog.
When a new release is available, mdProbe also shows a discreet banner at startup. To silence it: export NO_UPDATE_NOTIFIER=1. The banner is automatically suppressed in CI, in pipes, and during --once mode.
GFM tables, syntax highlighting (highlight.js), Mermaid diagrams, math/LaTeX (KaTeX), YAML/TOML frontmatter, raw HTML passthrough, images from source directory.
File changes detected via chokidar, pushed over WebSocket. Debounced at 100ms. Scroll position preserved.
Every heading gets approve/reject buttons. Approving a parent cascades to all children. Progress bar tracks reviewed vs total sections.
When the source file changes after annotations were created, mdProbe runs a 5-step pipeline to re-locate each annotation span:
1. Hash check — file unchanged? use stored offsets directly (~0ms)
2. Exact match — quote text still appears uniquely in source
3. Fuzzy match — Myers bit-parallel, threshold ≥ 0.60 within ±2 kB window
4. Tree path — heading + paragraph fingerprint via mdast
5. Keyword dist — rare-word anchors as last resort
→ confident / drifted / orphan
drifted annotations show a dashed amber underline and require your explicit confirmation (acceptDrift) before being re-anchored as open. orphan annotations appear in a dedicated panel section without inline highlight.
v0.5.0 uses the CSS Custom Highlight API (zero DOM mutation) to render annotation marks. Selections are anchored by UTF-16 character offsets in the raw Markdown source, not by line/column numbers — so cross-block selections, reformatted code, and paragraph-wrapping edits don't break anchors silently.
Five themes based on Catppuccin: Mocha (dark, default), Macchiato, Frappe, Latte, Light.
| Key | Action |
|---|---|
[ |
Toggle left panel (files + TOC) |
] |
Toggle right panel (annotations) |
\ |
Focus mode (hide both panels) |
j / k |
Next / previous annotation |
? |
Help overlay |
Ctrl+Enter |
Save annotation |
mdprobe export spec.md --report # Markdown review report
mdprobe export spec.md --inline # Annotations inserted into source
mdprobe export spec.md --json # Plain JSON (v2 schema)
mdprobe export spec.md --sarif # SARIF 2.1.0 (CI/CD integration)v0.5.0 requires the CSS Custom Highlight API for inline annotation rendering.
| Browser | Minimum version |
|---|---|
| Chrome / Edge | 105+ |
| Firefox | 140+ |
| Safari | 17.2+ |
On older browsers mdProbe shows a modal explaining the limitation and falls back to a read-only annotation list. Inline highlights are disabled.
mdprobe [files...] [options]
Options:
--port <n> Port number (default: 3000, auto-increments if busy)
--once Blocking review — isolated server, exits on "Finish Review"
-d, --detach Start server in background and exit
--no-open Don't auto-open browser
--help, -h Show help
--version, -v Show version
Subcommands:
setup Interactive setup (skill + MCP + hook)
setup --remove Uninstall everything
setup --yes [--author <name>] Non-interactive setup
mcp Start MCP server (stdio, for AI agents)
config [key] [value] Manage configuration
export <path> [flags] Export annotations (--report, --inline, --json, --sarif)
migrate <path> [--dry-run] Batch migrate v1 annotations to v2
stop [--force] Kill singleton server and clean lock file
mdProbe includes an MCP server and a SKILL.md that teaches AI agents the review workflow. This enables a two-way loop: the agent writes Markdown, the human annotates, the agent reads feedback and resolves it.
mdprobe setupInteractive wizard that:
- Installs
SKILL.mdto detected IDEs (Claude Code, Cursor, Gemini) - Registers the MCP server (
mdprobe mcp) in Claude Code (~/.claude.jsonorclaude mcp) and in Cursor (~/.cursor/mcp.jsonwhen that folder exists) - Migrates legacy Claude PostToolUse hooks from older mdprobe versions (if any)
- Configures your author name
Non-interactive: mdprobe setup --yes --author "Your Name"
Remove everything: mdprobe setup --remove
| Tool | Purpose |
|---|---|
mdprobe_view |
Open .md files in the browser |
mdprobe_annotations |
Read annotations and section statuses |
mdprobe_update |
Resolve, reply, add, or delete annotations |
mdprobe_status |
Check if the server is running |
If you prefer not to use mdprobe setup:
Claude Code
claude mcp add --scope user --transport stdio mdprobe -- mdprobe mcpCursor — merge into ~/.cursor/mcp.json (or project .cursor/mcp.json):
{
"mcpServers": {
"mdprobe": { "command": "mdprobe", "args": ["mcp"] }
}
}WSL + Cursor on Windows: Node's home is your Linux home (e.g. /home/you), while the Cursor desktop app reads MCP from the Windows profile (%USERPROFILE%\.cursor\mcp.json). When you run mdprobe setup inside WSL, it writes both ~/.cursor/mcp.json (Linux) and, via /mnt/c/..., the Windows mcp.json with a wsl.exe bridge to your Linux mdprobe binary. WSL_DISTRO_NAME and cmd.exe must be available (normal WSL2 install).
Schema v1 used selectors.position { startLine, startColumn, endLine, endColumn }. v0.5.0 replaces this with range { start, end } (UTF-16 character offsets), which is more precise and survives line-wrapping edits.
Automatic: AnnotationFile.load() detects v1 files and migrates them in-place, writing a .bak backup first (e.g. spec.annotations.yaml.bak).
Batch (recommended before upgrading a large repo):
mdprobe migrate docs/ --dry-run # Preview changes without writing
mdprobe migrate docs/ # Apply migrationRollback: restore the .bak file alongside the .annotations.yaml file.
See docs/SCHEMA.md for the full v2 field reference.
mdProbe ships as an npm package you can embed in your own server — no separate process needed.
- docs/EMBEDDING.md —
createHandler(Express/Node middleware),AnnotationFileclass, export helpers, anchoring utilities - docs/HTTP-API.md — full REST + WebSocket endpoint reference
- docs/SCHEMA.md — annotation YAML schema v2, field-by-field reference
- docs/ARCHITECTURE.md — project structure, rendering pipeline, key design decisions
git clone https://github.com/henryavila/mdprobe.git
cd mdprobe
npm install
npm run build:ui # bundles the in-browser UI into dist/The UI bundle in dist/ is required at runtime — the server serves it directly. Re-run npm run build:ui whenever you change anything under src/ui/.
You can run the CLI from the repo without installing it globally:
# View a single file
node bin/cli.js path/to/file.md
# Pick a port, run in background, skip auto-open
node bin/cli.js path/to/file.md --port 4000 -d --no-open
# Stop the background server
node bin/cli.js stopIf you want your local build to back the mdprobe command everywhere on your machine, link it:
npm link
# now `mdprobe path/to/file.md` runs YOUR checkout
# to undo:
npm unlink -g @henryavila/mdprobeFor UI work, a Vite dev server is also available:
npm run dev:uinpm test # full Vitest suite (unit + integration), single run
npm run test:watch # re-run on file changes
npm run test:unit # unit only
npm run test:integration # integration only
npm run test:coverage # with coverage report
npm run test:e2e # Playwright end-to-end (needs `npx playwright install` once)DOM-touching unit tests use happy-dom and live in tests/unit/*.test.jsx; everything else runs in plain Node. Tests render real markdown via src/renderer.js and assert against real DOM behaviour — keep new tests in that style rather than mocking out the renderer.
- Open an issue first for non-trivial work — design questions, breaking API changes, anything bigger than a small bug fix. A quick "I'm planning to do X" comment saves rework.
- Branch off
main, name it something descriptive (fix/highlight-cross-inline,feat/section-approval-keybind). - Write a failing test first for any bug fix. Tests live next to the code they cover (
tests/unit/for module-level,tests/integration/for cross-module flows). The existing tests show the preferred style: render real markdown, build a real DOM, then assert. - Run the full suite before opening a PR:
npm test npm run build:ui # make sure the UI bundle still builds
- Commit style: Conventional Commits (
fix:,feat:,refactor:,docs:,test:,chore:). Keep the subject under ~70 characters; expand in the body if needed. - PR description should include:
- What changed and why (link the issue).
- How a reviewer can reproduce the original problem and verify the fix manually.
- Anything intentionally out of scope.
- Don't commit build artifacts —
dist/is git-ignored except for the entry HTML; rebuilding shouldn't show up ingit status.
For project structure and architecture details see docs/ARCHITECTURE.md.
MIT © Henry Avila




