Releases: skurekjakub/mcp-interactive-editor
Release list
v0.7.0
Fixed
- The editor never opened in Claude Desktop: the panel sat on "Opening…"
and the agent was told its proposal id was unknown. The message blamed a
server restart, so the advice was always to restart the app, and restarting
never helped. Nothing had restarted. Claude Desktop starts every configured
server from two managers that do not coordinate and leaves both processes
running, so the model's call and the panel's calls reach different copies of
the server — and a proposal the first copy is holding does not exist as far
as the second one is concerned. Proposals now live in a directory both copies
read, derived from the roots the server was started with, so it no longer
matters which copy answers.
Changed
- An approved proposal is written once even when two servers are asked to
write it. The claim that made a second commit wait for the first was a flag
in one process's memory, which two processes do not share. It is now a
directory thatmkdircreates atomically, so the filesystem refuses the second
caller. Servers started against different roots keep separate stores and cannot
see, or write, each other's proposals.
Install: download mcp-interactive-editor.mcpb below and open it, or add the plugin marketplace skurekjakub/mcp-interactive-editor in Claude Code.
sha256 17f64e9bfa7ee69b432d294d9ef561ab41ce4cbdfecb4ad5d2359bd50136fc87
v0.6.2
Added
- A Streamable HTTP transport, behind
--http. A browser cannot spawn a
process, so an in-browser host or inspector could never reach this server —
the only way to see the panel rendered by anything but the preview fixture was
to install a.mcpband reinstall it on every change. It listens on
127.0.0.1, andnpm run dev:httpstarts it against the working directory.
Stdio remains the default, because that is what an installed host spawns.
Fixed
- A panel left open across a server restart said only that its proposal id was
unknown. A host restarts the server whenever the extension is installed or
updated, so every panel already on screen holds an id the new process never
issued — a routine event that read as a dead panel with no next step. It now
says what happened and that the write has to be asked for again. - A port already in use reported the raw listen error instead of saying so,
which matters in a loop where the server is restarted constantly. - CI checked for a missing version bump only on pull requests, so a commit
pushed straight to main could land source changes under a version already
published. It compares against the last release now, on every run.
Install: download mcp-interactive-editor.mcpb below and open it, or add the plugin marketplace skurekjakub/mcp-interactive-editor in Claude Code.
sha256 4835bc031fa6273215539bd59160faf04b49203dcb8fa0387802db71b82f8fba
v0.6.1
Four more audits — bug hunting, refactoring, tooling and test quality — each
verified against the vendored specifications, and every defect below reproduced
before it was changed.
Fixed
- A flag could eat the next flag.
--deny --dry-runtook--dry-runas the
deny pattern and left a server that writes to disk when it was asked to
simulate. A value that begins with a dash and a letter is now refused, naming
the flag that followed and pointing at--flag=valuefor the rare case where
that really is the value. - Two live drafts of one file could both commit. The supersede check
re-resolved the requested path against the working directory while the guard
resolved it against the configured root. Where those differ — which is the
shipped.mcpbconfiguration — two spellings of one file compared unequal,
nothing was superseded, and the older draft landed last over the newer one with
both reporting success. Resolved targets are now compared directly, and without
case folding, which would have closed a live review on a case-sensitive
filesystem. - A CRLF file rewritten to LF showed an empty diff and no findings. Lines are
compared with their terminators stripped, so every line ending in the file
could change while the panel reported no change at all — under a live Save
button. It now says so, and offers to keep the file's own endings. - The trailing-whitespace check was quadratic. A long run of spaces the line
did not end with took minutes rather than milliseconds, on a scan that runs on
every keystroke in the panel and again before every commit. It is a linear pass
now, and preserves CRLF and the final newline. - The diff budget bounded each side rather than the table. A short file
against an enormous one was under the per-side limit on one side and allocated
gigabytes. The bound is on the product now. - A missing path segment lost a character when the walk up reached the
filesystem root, because a root already ends with its separator. The write went
to a path nobody asked for — and, with the wrong root, one still inside it. - Two concurrent commits both cleared the resolved check, which is not
reached until after the write. On POSIX that is one approved proposal, two
writes and two receipts. A proposal is now claimed before the firstawait. - Nothing capped what a tool call carried.
pathandcontenthad no
bounds, so a proposal could be larger than any file this editor would agree to
open, and an over-long path failed atrenamewith a raw errno quoting an
internal temp name. Both are bounded at the schema now. - The model diff was capped in lines, not characters, so a single enormous
line was handed over whole with no truncation note. - The panel round-tripped twice on every mount. Both the claim and the attach
effect wrote the phase they depended on, so each cancelled its own first call
and started again — two re-reads of the file, and two attaches whose stored
baselines could differ. - A cancelled session reported itself as attaching. The phase description
defaulted, so the one phase that means "stop waiting" read as progress. - The panel and the model gave different accounts of a refused path. The
model was told which check refused it; the human got a single collapsed
sentence. Both now read the same explanation. - The commit gate had no type behind it. The SDK's capability helper names a
type its own package does not export, so its return value resolved to the error
type and every property read off it checked against nothing. The shape is
verified explicitly now, and covered by unit tests rather than only by a
subprocess. - CI ran the panel suite on an unsupported Node.
.nvmrcpinned 20.10.0
while jsdom, undici and whatwg-url all require newer. Pinned to 22 with
engine-strictso the mismatch cannot come back quietly.
Changed
- Assembling the editor state, explaining a refusal and describing a commit each
live in one place inshared/. There were four copies of the first, and one of
them had already reintroduced a line-count defect fixed elsewhere. - Command-line parsing moved to
src/cli.ts, reading its environment, working
directory and home directory as arguments — so every decision it makes is
reachable from a unit test rather than only from a spawned server. src/tools/results.tsis nowsrc/tools/wording.ts, which is what its own
module docblock already called it, and no longer collides with the panel's
results.ts.- The commit tool declares its annotations. Under
--terminal-approvalthe
client's own approve/deny prompt is the entire gate, and that prompt is
rendered from them. - A proposal dropped to make room is remembered as superseded, so a panel holding
its id is told what happened rather than that the server does not know it. - The unused
filesfield is gone frompackage.json, and the README no longer
documents a command-line binary that does not exist.
Added
- ESLint, type-aware, in
npm run verifyand CI, along withknipfor dead
exports andnpm audit --omit=devfor what actually ships. - Tests for the command line, the commit gate's host check, the proposal store,
the input bounds and the commit threshold. The threshold had five conditions
keeping the button shut and no test held any of them. - A finding for a change that alters only the newline at the end of a file, which
the diff cannot show.
Testing
- Removed assertions that could not fail: an editor
disabledproperty never
set, a truthiness check on an already-filtered list, a temp-file check keyed to
a string copied out of the implementation, and a declaration-count floor below
the true count. Each was replaced by one that fails when the behaviour breaks. - The review-gate tests get their own server. They shared a grace period pinned
short for the opposite reason and lost the race roughly one shuffled run in
three. - The claim test now opens two proposals. With one open it took the single-open
fallback and passed only because earlier tests had left proposals behind.
Install: download mcp-interactive-editor.mcpb below and open it, or add the plugin marketplace skurekjakub/mcp-interactive-editor in Claude Code.
sha256 cc888f25c80746b8dd4c1a91619592963a82d908e1f2496d476942a15dca9aa6
v0.5.2 — say what the host actually said
The panel was being told why, and threw it away
A refused MCP call comes back as isError: true with the reason in its text blocks — not a thrown rejection. Both the claim loop and the attach path read only structuredContent, found none, and carried on. The result was thirty seconds of retries ending in "asking for one kept coming back empty", while the host had been answering with the actual reason ten times a second.
It is the same mistake as the loading screen that swallowed its own error, one layer further down: that fix displayed failure and left the code below still discarding it.
editor_pending gave one answer to two questions
"No proposal is open" means the panel asked before the server finished creating it — or it means several are open and none matched the path the host handed back. Opposite responses. It now reports how many are open and which paths it holds, and the panel repeats the server's own words instead of inventing a summary.
207 tests. Reinstall the .mcpb below.
v0.5.1 — the panel gets tests
Five things that were owed.
The panel had no tests
Nothing in ui/ was reachable from the suite, and three regressions shipped through that gap while 187 tests stayed green:
- a highlight that could not be commented on
- a loading screen that swallowed its own error
- a view that removed the editor entirely
Each is now a named test in test/panel, rendered under jsdom. Reintroducing all three turns five of the nine red — which is the only evidence a regression test is worth keeping.
Fixed
-
A renamed path could strand the panel forever.
editor_pendingcomparedtarget.requestedby string equality against whatever the host handed back. Any normalisation — slashes, case, relative to absolute — missed, and the panel then retried a string it could never match. It compares resolved paths now, and falls back to the single open proposal when there is exactly one.The first cut of that fallback returned the newest open proposal on any miss, and the e2e suite caught it straight away: a panel asking before its own proposal existed got handed somebody else's. With several open it now guesses nothing.
-
Tests no longer leak server processes when an assertion fails.
Added
- The panel says when it is a different build from the server. The
.mcpband the Claude Code plugin update independently, and a stale half looks exactly like a bug in the other one — it cost two debugging sessions before anything said so.
Changed
- Version lives in one module per half rather than as a literal inside unrelated code.
AGENTS.mdrecords why a tool call must not wait for the panel — the measurements, the line of spec that makes it the host's choice, and why elicitation is not the way out.
196 tests.
v0.5.0 — the panel comes first
Blocking is opt-in now, and off by default
0.4.0 held every opening call open until you decided. That logic works, and I measured it: a plain MCP client gets an answer from editor_pending in 4ms while propose_write is still open, and the whole accept/comment/discard round-trip completes in 138ms.
What it needs is the host to keep dispatching the panel's own calls during the call that created the panel. The MCP Apps spec requires no such thing:
The Host MAY forward any message from the View … it MAY decide to block some messages or subject them to further user approval.
At least one host doesn't forward them in time, so the panel never claims its proposal and sits on "Opening…" — a much worse trade than a review that doesn't block. --block-on-review turns the gate back on where the host allows it.
Why not elicitation? MCP's own primitive for pausing to ask is server.elicitInput, and its result is exactly accept | decline | cancel — a perfect fit on paper. It renders the client's form from a JSON schema, so adopting it would mean giving up the editor, which is the entire product. The Apps spec and the elicitation spec don't mention each other.
Added
- Fullscreen. The panel declares
availableDisplayModesand asks viaui/request-display-mode, believing the host's answer rather than its own request. Hidden where the host never offered the mode. - Send always delivers — resolves a waiting call when there is one, posts the comments as a message when there isn't.
187 tests. Reinstall the .mcpb below.
v0.4.2 — the panel lost the race it had to win
Two symptoms, one cause: the review did not block, and the panel sat on "Opening…" forever.
The server waited 4 seconds for a panel to attach. The panel gave itself 3 to find its proposal. A first mount has to fetch a half-megabyte UI resource, boot an iframe, run React, finish the ui/initialize handshake, and only then call a tool — it lost that race every time. The grace expired, the opening call returned "nobody answered", and the agent carried on as though there had been no review.
Both budgets are 30 seconds now.
Why it was invisible
The loading branch rendered a bare "Opening …" and never read failure, so a failed claim looked exactly like a slow one — a spinner forever, with the real error sitting in state nothing displayed. It now names the step it is on (waiting for the host / claiming the proposal / attaching) and shows the error when there is one.
It also stopped retrying as soon as it had a proposal id rather than the proposal itself, so an id from the handle with a failed attach behind it left nothing retrying.
187 tests. Reinstall the .mcpb below.
v0.4.1 — a highlight could become uncommentable
The bug
0.4.0 moved the comment box into a popover anchored to the selection, and in the same change removed the tray's own + Add row — making the popover the only way to attach a comment.
The popover needs a rectangle to position against, and not every selection yields one. Wherever the anchor came back empty, the highlight was dropped silently with nothing left to click. Preview never showed it, because a clean mouse drag always produces a rectangle.
The fix
The tray takes the pending selection again. The popover is the quick path; the tray row is the one that is always there. Both are live at once and either pins the passage, so the feature no longer depends on a measurement succeeding.
Also fixed:
- The diff pane listens for selections in its empty branch too — that is what an unchanged file renders through.
npm run bumpleaves the tree formatted. Its prettier step had never run: a deprecated shell invocation, thennpx.cmd, which fails on Windows with EINVAL after writing — so a bump produced a correct version and a failingformat:check.
187 tests. Reinstall the .mcpb below.
v0.4.0 — the editor decides what the call returns
The agent proposes and the call waits.
- Accept with no comment → it commits, and the call returns the receipt.
- Comment on it → that is the rejection. Nothing is written, and the call returns your words quoted against the lines they are about, for the agent to redraft from.
Commenting and committing are exclusive on purpose: an agent told "written, and here are some notes" treats the work as finished, which is the opposite of what you meant by taking the time to say what was wrong.
Also
- The comment box opens above the selection, in the pane you are reading, instead of at the foot of the window. Placement is unit tested — above by preference, flipped below only when there is no room, never off-screen.
- Send finishes the call. It used to go through
ui/message, which only drafts into the composer, so every send needed a second click somewhere else. - The tray at the foot keeps the note that applies to everything.
--review-timeout-msand--review-grace-ms.
No new way to hang
A host with no MCP Apps support has nobody to wait for and gets the diff as text, exactly as before. A host that advertises support but never mounts the panel runs out a short grace period and gets the same.
187 tests. Reinstall the .mcpb below in Claude Desktop → Extensions.
v0.3.0 — a comment per highlight
All of this came from watching the panel get used.
Added
- Every highlight carries its own comment. One note box served the whole batch, so a message arrived as a stack of quotes with a single paragraph underneath and no way to tell which remark went with which region. Each highlight is now a row with its own field, rendered as a blockquote directly beneath its passage.
- The tray docks to the foot of the panel and stays put instead of vanishing with the selection.
- Sending waits until every highlight has a comment, and names the ones outstanding.
Changed
- Reading order, not clicking order. Highlighting line 7 then line 3 produced a message that opened at line 7.
- The editor is never taken away.
diffused to remove it outright, leaving a change you could not type into — the one thing the panel exists to allow. It shrinks now and stays editable.
171 tests. Reinstall the .mcpb below in Claude Desktop → Extensions.