Skip to content

Releases: skurekjakub/mcp-interactive-editor

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 18:27

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 that mkdir creates 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

Choose a tag to compare

@github-actions github-actions released this 31 Aug 16:49

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 .mcpb and reinstall it on every change. It listens on
    127.0.0.1, and npm run dev:http starts 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

Choose a tag to compare

@github-actions github-actions released this 31 Aug 16:14

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-run took --dry-run as 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=value for 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 .mcpb configuration — 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 first await.
  • Nothing capped what a tool call carried. path and content had no
    bounds, so a proposal could be larger than any file this editor would agree to
    open, and an over-long path failed at rename with 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. .nvmrc pinned 20.10.0
    while jsdom, undici and whatwg-url all require newer. Pinned to 22 with
    engine-strict so the mismatch cannot come back quietly.

Changed

  • Assembling the editor state, explaining a refusal and describing a commit each
    live in one place in shared/. 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.ts is now src/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-approval the
    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 files field is gone from package.json, and the README no longer
    documents a command-line binary that does not exist.

Added

  • ESLint, type-aware, in npm run verify and CI, along with knip for dead
    exports and npm audit --omit=dev for 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 disabled property 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

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 13:34

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

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 13:26

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_pending compared target.requested by 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 .mcpb and 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.md records 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

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 13:14

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 availableDisplayModes and asks via ui/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

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 13:02

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

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 12:44

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 bump leaves the tree formatted. Its prettier step had never run: a deprecated shell invocation, then npx.cmd, which fails on Windows with EINVAL after writing — so a bump produced a correct version and a failing format:check.

187 tests. Reinstall the .mcpb below.

v0.4.0 — the editor decides what the call returns

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 12:18

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-ms and --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

Choose a tag to compare

@skurekjakub skurekjakub released this 31 Aug 11:35

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. diff used 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.