mcp-interactive-editor
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-interactive-editorPropose a rewrite of config.json in the diff editor so I can approve before anything touches disk."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
███████╗██████╗ ██╗████████╗ ██████╗ ██████╗
██╔════╝██╔══██╗██║╚══██╔══╝██╔═══██╗██╔══██╗
█████╗ ██║ ██║██║ ██║ ██║ ██║██████╔╝
██╔══╝ ██║ ██║██║ ██║ ██║ ██║██╔══██╗
███████╗██████╔╝██║ ██║ ╚██████╔╝██║ ██║
╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝An interactive editor in front of every file write
mcp-interactive-editor · the model proposes, you edit the proposal by hand, and nothing reaches disk until you say so
MCP App · live diff against disk · per-line comments · human-in-the-loop · local-only
Install · How it works · Host support · Guarantees · Options · Development
Claude proposes a write. An editor opens with the proposed content on the left and a live diff against what is actually on disk on the right. You edit the proposal by hand — it is your text now — and nothing touches the filesystem until you press the button.
The point is not the confirmation prompt. The point is the editing. When a generated file is 95% right, fixing the last 5% by typing takes a second and changes nothing else. Asking for it in prose costs a round trip and usually rewrites two other things you liked.
┌─────────────────────────────────────────────────────────────────────┐
│▐ HELD .github/workflows/deploy.yml · overwrite +1 −25 │
│ Collapse the three jobs into one. │
├─────────────────────────────────────────────────────────────────────┤
│ BLOCKS This removes 25 of 39 lines (64%). │
│ NOTE No trailing newline. [ Add one ] │
├──────────────────────────────┬──────────────────────────────────────┤
│ PROPOSED — EDIT FREELY │ AGAINST DISK │
│ 1 # Deploy pipeline │ 7 - workflow_dispatch: │
│ 2 name: deploy │ 10 - test: │
│ 3 │ 9 + deploy: │
├──────────────────────────────┴──────────────────────────────────────┤
│ LINES 4–9 · 6 lines what is this job for? ▸ [ Send to Claude ] │
├─────────────────────────────────────────────────────────────────────┤
│ ☑ I have read the diff and I mean to remove those lines. │
│ [ Discard ] [▨ Write 15 lines to deploy ] │
└─────────────────────────────────────────────────────────────────────┘What you get
Editable proposals | The draft is a textarea, not a preview. Fix it in place and commit what you fixed. |
Live diff against disk | Recomputed on every keystroke, by the same module the server checks with before writing. |
Per-line comments | Highlight a region, attach a question to it. Comments decline the draft and send the agent back to redraft. |
A gate the agent cannot open | The tool that writes is not in the model's tool list, and the server refuses to commit for a host that renders nothing. |
Checks before the button | Destructive-deletion ratio, stale-file detection, line endings, trailing whitespace, indentation — each with a one-click fix. |
Local only | The panel is one inlined HTML file served into a sandbox with every CSP domain list empty. No CDN, no fonts, no network. |
Related MCP server: AI Diff Review MCP
How it works
The editor is not something you interact with "inside a tool call". That confusion is worth clearing up, because the whole design follows from it.
YOU CLAUDE HOST MCP SERVER
│ │ │ │
│─ "write ci.yml" ──▶│ │ │
│ │─ propose_write ────▶│────────────────── ▶│
│ │◀──── diff + ui:// ──│◀──── result ────────│
│ │ │ │
│ [Claude's turn ENDS here — the tool call is over] │
│ │ │ │
│ │ renders ui:// in an iframe │
│◀═══════ the editor, a live web page in the transcript ════════▶│
│ │ │ │
│─ type, edit, fix ─────────────────────── │ ─ editor_update ───▶│
│─ press the button ────────────────────── │ ─ editor_commit ───▶│ writes
│ │ │ │
│ │◀─ updateModelContext ─ "here's what landed"│Three things fall out of that:
The tool call returns immediately. It returns text for Claude plus a pointer to a
ui://resource. Claude's turn ends. Nothing is blocked or waiting. (--block-on-reviewchanges this — see Options.)The editor is a second, independent MCP client. It talks to the same server over postMessage, proxied by the host. When you type and it saves, that is the page calling the server — Claude is not in the loop, not consuming tokens, not aware. You can sit in that editor for ten minutes.
Two clients, two different tool lists. That is what makes
visibility: ["app"]mean something. The agent and the editor are separate callers, so the server can hand them different capabilities. The agent gets tools that open an editor. The editor gets the tool that writes.
You rejoin the conversation only when you choose to: by pressing the button (which reports back what landed), by discarding, or by selecting a passage and sending it to the chat.
Install
Claude Desktop — one click
Grab mcp-interactive-editor.mcpb from
Releases and
double-click it, or drag it onto Claude Desktop's Extensions screen. It will ask
you to pick the folders it is allowed to write in.
Build it yourself with:
npm install && npm run packSettings → Developer → Edit Config, which opens
%APPDATA%\Claude\claude_desktop_config.json on Windows or
~/Library/Application Support/Claude/claude_desktop_config.json on macOS:
{
"mcpServers": {
"interactive-editor": {
"command": "node",
"args": [
"C:\\path\\to\\mcp-interactive-editor\\bundle\\server\\index.js",
"--root",
"C:\\path\\to\\your-project"
]
}
}
}Backslashes doubled — it is JSON. Then quit Claude Desktop completely and reopen; closing the window is not quitting.
VS Code (GitHub Copilot)
Clone it anywhere. bundle/ is committed, so there is nothing to install or
build:
git clone https://github.com/skurekjakub/mcp-interactive-editor.gitThen add .vscode/mcp.json to your workspace, pointing at the clone:
{
"servers": {
"interactive-editor": {
"type": "stdio",
"command": "node",
"args": [
"/absolute/path/to/mcp-interactive-editor/bundle/server/index.js",
"--root",
"${workspaceFolder}"
]
}
}
}${workspaceFolder} is expanded by VS Code, so the root follows whatever project
you have open. The path to the clone has to be absolute.
This repository ships its own .vscode/mcp.json for dogfooding: open this
clone in VS Code and the editor guards this project, running the bundle/ that
is already in the workspace — no absolute path needed.
Claude Code — plugin marketplace
/plugin marketplace add skurekjakub/mcp-interactive-editor/plugin install interactive-editor@interactive-editorThe plugin roots the editor at your current project directory. Read the host support note below before you rely on it — Claude Code cannot render the editor.
Host support, honestly
MCP Apps need a rendering surface, and this is the part where a table is easy to
write and hard to keep true. Ask the running server instead: list_roots
reports its version, the writable roots, and whether the connected host declared
that it renders MCP Apps at all.
Host | Renders the editor | Committing |
Claude Desktop | ✅ | ✅ |
VS Code (Copilot) | ✅ | ✅ |
Claude Code / any terminal agent | ❌ | ❌ by default |
Other MCP clients vary with their MCP Apps support; list_roots is the answer
for the client in front of you.
In a terminal host, propose_write still works and still returns the diff as
text, so the agent sees what it proposed, and nothing can be committed — but
not for the reason it would be comfortable to assume.
visibility: ["app"] is a request to the host, not a guarantee. A host with no
MCP Apps support hands the agent every tool, editor_attach among them, so the
agent can mark its own proposal as attached and attached alone secures
nothing. What an agent cannot author is the capabilities its own client
declared at initialize, so that is what the commit path asks: if the host never
advertised that it renders text/html;profile=mcp-app, no panel ever appeared,
nobody saw the diff, and the write is refused. It degrades to a hard stop, not
an open gate — and there is an end-to-end test that walks the whole attack
(propose, self-attach, commit) to prove it.
If you want the server usable in a terminal anyway, --terminal-approval
exposes the commit tool to the agent and leans on your client's own approve/deny
prompt instead. It is a real gate and a much weaker one: your client prompts for
a tool call rather than showing you a diff, and it evaporates entirely if you
allowlist the tool. The plugin does not turn it on for you.
Using it
Talk normally. This is a tool, not a mode.
Propose a write — the model writes, you correct it:
Write a GitHub Actions workflow that runs the tests on push to main, at
.github/workflows/ci.yml
Open a file — you edit it yourself, the model stays out of it:
Open
src/config.tsso I can fix it
open_file loads the file into the editor and deliberately does not put the
body in the model's result. It goes to the editor, not into the context window.
The commit button stays dead until you actually change something.
Comment on a passage — highlight lines in either pane and a box opens beside the selection. Every highlight carries its own comment, and the tray at the bottom will not send until each one has been answered. Sending declines the draft: nothing is written, and the agent is handed your words to redraft from.
From the draft open in the interactive editor —
deploy.yml:lines 4–9:
jobs: deploy: runs-on: ubuntu-latestwhy is this not pinned to a SHA?
After you edit, the model is told what actually landed rather than what it proposed — otherwise the rest of the conversation is built on a file that does not exist.
Making Claude reach for it
The server sends usage instructions at connect time and every tool description says it never writes. Usually enough. If Claude has other filesystem tools and picks the wrong one, add to your Project instructions:
All file writes go through interactive-editor's propose_write. Never use another
filesystem tool to write, create, or delete files. After proposing, stop and
wait — do not re-propose the same write.Why the model cannot approve its own write
Tool | Callable by | What it does |
| model, editor | Opens an editor. Writes nothing. |
| model, editor | Opens an editor. Deletes nothing. |
| model, editor | Loads a file into the editor. |
| model, editor | Reads inside the roots. |
| model, editor | Roots, version, host capability. |
| editor only | Binds the editor to a proposal. |
| editor only | Saves your edits. |
| editor only | Writes to disk. |
| editor only | Drops the proposal. |
| editor only | Sends your comments, declines it. |
| editor only | Claims the proposal on mount. |
Editor-only tools carry _meta.ui.visibility: ["app"]. Under the
MCP Apps spec a host MUST
NOT put them in the agent's tool list. That is the whole of what the spec
requires, and it is worth being precise about: a host with no MCP Apps support
hands them over regardless, which is exactly why the commit path also checks the
capability the client declared at initialize — the one input an agent cannot
author for itself.
Belt and braces, the server also refuses to commit a proposal that no editor ever attached to.
The rest of the guarantees
Roots are absolute. A path is writable only if, after full resolution including symlinks, it sits inside a
--root. A symlink planted inside a root pointing outside it does not work.A deny list on top:
.git/,node_modules/,.env,.ssh/,id_rsa,.pem,.key,.p12,.pfx,credentials,.aws/,.npmrc. Patterns are anchored to whole filenames and extensions, soshortcuts.keymap.tsis not caught by.key. A refusal names the pattern that matched.The proposal's target is immutable. Nothing can re-point a proposal at another file after the diff was shown, so the file you reviewed is the file that gets written.
Stale writes are refused. If the file changes on disk while the editor is open, the commit fails and the proposal is closed: the diff you approved is not the diff that would apply, and a second press must not walk through it.
Deleting most of a file needs a tick — over half the lines, or emptying it, or deleting it. Small files are exempt; a checkbox in front of every one-line edit trains you to tick without reading.
Writes are atomic and keep their permissions — temp file,
chmodto match the file being replaced, then rename. A crash leaves the original intact rather than half a file, and a0755script does not come back0644.Everything is rechecked server-side at commit. The editor's checks are for your eyes; the server's are the ones with authority.
Options
There is no installed binary — this is not published to npm. The server is a script, launched by whatever config points your host at it:
node bundle/server/index.js --root <dir> [--root <dir> ...] [options]
--root <dir> A directory the editor may write inside. Required, repeatable.
--root-from-cwd Add the working directory as a root.
--deny <pattern> Extra filename or extension to refuse. Repeatable.
--allow-everything-in-roots Drop the built-in deny list.
--dry-run Run the whole flow but never touch disk.
--terminal-approval Expose the commit tool to the agent, for hosts with no UI.
--block-on-review Hold the opening call open until the human decides.
--review-timeout-ms <ms> How long that call waits for a human. Default 600000.
--review-grace-ms <ms> How long to wait for the panel to attach. Default 30000.
--http Serve over Streamable HTTP on 127.0.0.1 instead of stdio.
--http-port <n> Port for --http. Default 3001.
--allow-origin <origin> Extra browser origin allowed to call the HTTP endpoint. Repeatable.INTERACTIVE_EDITOR_DRY_RUN=true does the same as --dry-run, for launchers
that cannot add a flag conditionally. --dry-run is the honest way to try this
on a real project: everything works, the receipt says what would have happened,
nothing changes.
With the flag, propose_write does not return until you accept or comment, so
the agent learns the verdict in the result of the call it already made rather
than in a later message.
It is off by default because it requires the host to keep dispatching tool calls while one is still outstanding: the panel has to claim its proposal and attach during the call that created it. Not every host does, and where one does not the panel never loads and the editor is unusable. A non-blocking editor is a smaller thing than a blocking one and it works everywhere.
Turn it on where your host allows it. The tool descriptions the model sees are generated from this setting, so they never promise a wait that will not happen.
Development
npm install
npm run preview # the editor at localhost:5178, fixture data, no host needed
npm test # unit, panel and end-to-end over a real stdio server
npm run verify # typecheck + format + comment policy + tests, same as CI
npm run lint:comments # docs/comment-policy.md, enforced
npm run pack # build the .mcpb extension
npm run bump -- 1.2.3 # move every declared version at oncenpm run preview serves the editor in a plain browser tab with an in-memory
server, running the same diff and lint modules as the real thing. No host, no
risk to any file.
Layout
shared/ types, line diff, lint rules, passages — imported by BOTH the
server and the editor, so what you see is computed the way the
server checks it
src/ fsGuard (roots, deny list, atomic write), proposals, review,
and one module per tool under src/tools/
ui/ the editor: React, built to one self-contained HTML file
bundle/ committed distribution artifact — esbuild'd server + the editor.
Claude Code installs plugins by git clone with no build step.
test/unit/ shared modules, the filesystem guard, the release manifests
test/panel/ the React panel in jsdom
test/e2e/ a real server over stdio, driven by an MCP client, run twice:
against dist/ and against a copy of bundle/ outside the repo
docs/ the comment policy the build enforces
scripts/ bundle, version bump, comment-policy checker
.claude-plugin/ Claude Code plugin marketplace manifestsshared/ exists so the editor can recompute the diff and findings on every
keystroke without a round trip, while the server recomputes the identical thing
before committing. Same code, two audiences, one of them authoritative.
The editor ships as a single inlined HTML file because the host serves it into a
sandbox with connectDomains, resourceDomains and frameDomains all empty. No
CDN, no fonts, no network. An editor that can phone home would be a worse problem
than the writes it is guarding.
House rules
Comments follow docs/comment-policy.md: a docblock on
every top-level declaration, inline comments only at gotchas, and no narration —
no incident retellings, no version numbers attached to behaviour, no counts.
npm run lint:comments walks the AST and fails the build on a violation, so the
policy stays true rather than aspirational.
Publishing
bundle/ is committed and CI fails if it drifts from src/, because Claude Code
plugin users install straight from the repo tree. Run npm run bundle and commit
the result with any change to the server or editor.
The declared version is load-bearing: Claude Code caches an installed plugin
under it and rebuilds only when it changes, so a change shipped without a bump
reaches nobody. npm run bump -- <version> moves every declaration at once,
test/unit/release.test.ts fails if they ever disagree, and CI fails a pull
request that touches src/, shared/ or ui/ without moving the version.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Preventive human-approval write-gate for AI agents: writes commit only after a human approves.
Security reviews for coding agents: diffs checked against your org policy and live infrastructure.
Guardian agent for AI coding: four frontier models review risky diffs and commits before they ship.
AI agents propose database changes as reviewable requests — no direct write access.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables AI assistants to perform code reviews by providing access to staged files, git diffs, and repository file content. It allows users to evaluate changes and context within any local git repository before committing or pushing.34 npmISC
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to edit files in VS Code with an interactive diff review panel, allowing users to accept or reject changes.1MIT

CodePeel MCP Serverofficial
FlicenseAqualityDmaintenanceEnables AI agents to review code diffs for bugs, security issues, and bad patterns, and generate fixes.4-- AlicenseAqualityFmaintenanceEnables AI assistants to draft content such as PR descriptions, tickets, or messages, with a review window for live preview, editing, and approval before submission.15 npm1MIT