Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 

Repository files navigation

md-review

A Claude Code plugin for iterating on Markdown documents by pointing at them instead of describing them.

/md-review:review some-doc.md renders the file in a preview, you click a paragraph or select a phrase and leave a comment, press Send to Claude, and the comments arrive in the chat with exact line numbers and the verbatim source text of each anchor. Claude applies them, the page reloads itself, and the next round starts.

Install

git clone <this-repo> ~/projects/md-review

Then in Claude Code:

/plugin marketplace add ~/projects/md-review
/plugin install md-review@md-review

Requires Node 18+ (uses only the standard library — no dependencies, no build step) and Claude Code 2.1.212 or later for the backgrounding behaviour described below.

How it works

Two processes in one: an MCP server on stdio and a loopback-only HTTP server.

/md-review:review doc.md
   │
   ├─ open_review(path) ────────► renders doc.md, returns http://localhost:7377
   ├─ preview_start(url) ───────► page opens in the Claude Code Browser pane
   └─ await_comments() ─────────► blocks…
                                     you comment, press Send
                                  ◄── { comments: [{ line_start, source, … }] }
        Claude edits the file
   ├─ (page reloads itself, comments cleared)
   └─ await_comments() ─────────► next round

The load-bearing detail is await_comments. It simply does not return until you press Send. Claude Code moves any main-conversation MCP call past two minutes into a background task and delivers the result as a task notification, so the session is never frozen while you read — however long you take, the comments land the moment you submit them. .mcp.json sets timeout: 3600000, which is both the wall-clock ceiling for one round and a floor on the MCP idle timeout.

Between rounds the server polls the file's mtime; when Claude edits it, the open page reloads over SSE and clears the previous round's comments.

Using the review page

Comments sit in a right-hand margin aligned with the passage they belong to, the way Google Docs does it; when a card would overlap the one above it, it is pushed down. Below ~900px of available width there is no room for a margin, so cards fall back inline.

Control Does
click a block Comment on it
click a block Edit its Markdown source, saved straight to the file
select text Offers "Comment on selection", quoting that exact phrase
/ ⇧↵ in a comment Save / newline
esc Cancel the open comment or edit
+ / - / 0 Zoom in, out, reset — bare keys, no modifier
⌘↵ Save an open source edit, or send — where the host passes it through

Modes are chosen with a modifier rather than hover buttons on purpose: a control in the left margin sits outside the document's box, so moving the mouse toward it fired mouseleave and took the button away before it could be clicked.

Saving leans on plain keys and buttons rather than chords. The desktop app claims -combinations for its own interface, and some embedded-browser input paths deliver a chord with an empty key, so ⌘↵ is treated as a bonus: saves a comment, and a source edit has a Save button.

Zoom lives in the page rather than the browser because the Claude Code desktop app claims ⌘+ / ⌘- for its own interface, so the keystroke never reaches an embedded page. The level persists across the reloads between review rounds.

Editing it yourself

opens the block's Markdown source in a textarea and writes it back to the file on save — no round-trip through Claude, for the tweaks that are faster to just make. It edits the source, not the rendered HTML: an HTML-to-Markdown conversion would quietly reformat the rest of the file, while splicing a line range preserves every byte you did not touch.

Writes are guarded, because Claude and unrelated editors touch the same file. The page sends the line range and the original text; the server writes only if that text is still there, relocating it by a unique text match when the file has shifted, and refusing outright when the passage is gone or ambiguous. Your edits are reported to Claude in the next round as manual_edits so it does not redo them. There is no undo — the file is the record, so keep it in version control.

What Claude receives

Each comment is anchored twice — by line number and by verbatim text — so edits stay reliable even if the file moved underneath:

{
  "kind": "rewrite",
  "line_start": 42, "line_end": 44, "lines": "42-44",
  "source": "The onboarding flow is far too slow.",
  "selected_text": "far too slow",
  "comment": "quantify this"
}

kind is one of comment, rewrite, cut, expand, question, approve. A line_start of 0 means the comment lost its anchor because the passage changed after it was written; selected_text still holds the original.

Changing it

Installing copies the plugin into a version-keyed cache pinned to a git commit (~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/). Editing this repo does not change an installed plugin — a new session keeps loading the cached copy, at the version that was current when you installed it.

To ship a change:

# 1. bump "version" in BOTH plugins/md-review/.claude-plugin/plugin.json
#    and .claude-plugin/marketplace.json, then commit
claude plugin marketplace update md-review
claude plugin update md-review@md-review

Then restart Claude Code — an MCP server is only loaded at session start. Verify with diff -r plugins/md-review ~/.claude/plugins/cache/md-review/md-review/<version>.

Old version directories are left behind in the cache; they are inert and safe to delete.

Layout

plugins/md-review/
├── .claude-plugin/plugin.json
├── .mcp.json                  # stdio server, 1h per-call timeout
├── server/
│   ├── index.mjs              # MCP + HTTP, the blocking wait
│   ├── markdown.mjs           # line-anchored Markdown → HTML
│   └── page.html              # the review UI
└── skills/review/SKILL.md     # /md-review:review

Markdown support

The renderer exists to make review anchors trustworthy, not to be a CommonMark implementation. It covers ATX and setext headings, paragraphs, fenced code, blockquotes, nested ordered/unordered lists, task lists, tables with alignment, thematic breaks, YAML frontmatter (collapsed), and inline bold, italic, strikethrough, code, links, images and [[wikilinks]]. Raw HTML blocks are shown escaped rather than executed. Anything unrecognised still renders as a paragraph and stays commentable, so no part of a document becomes unreachable.

Commentable units are block-level elements plus individual list items and table rows — each carries its own source line range.

Configuration

Variable Default Purpose
MD_REVIEW_PORT 7377 First port tried; scans upward if busy
MD_REVIEW_KEEPALIVE_MS 25000 SSE keepalive and MCP progress-ping interval

The HTTP server binds 127.0.0.1 only.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages