NAME
mcp-vim - MCP server for live Vim editors
SYNOPSIS
mcp-vim [--name NAME] [--bind ADDRESS[:PORT]] [--stdio] [--print-url]
DESCRIPTION
mcp-vim lets MCP clients read and edit buffers in running Vim editors, including changes that have not been saved to disk. An assistant can work with the same text you are editing, and its changes appear in Vim for you to review and save.
It runs as a broker daemon. Each Vim editor needs the supplied Vim plugin, which connects the editor to the broker. Your MCP client connects to the same broker, which forwards requests to the editor and returns its replies. The client can use MCP over HTTP or stdin/stdout; Vim always connects over TCP using the plugin's private protocol.
One broker can serve several Vim editors. Each editor connects with a target name, allowing the client to list the connected editors and choose which one to work with. Buffer IDs belong to the selected editor.
See "VIM PLUGIN INSTALLATION" for installing and connecting the Vim plugin, and AnyEvent::MCP::Intro for configuring an MCP client.
FIRST USE
Install the companion Vim plugin described below. Configure the MCP client to launch:
mcp-vim --stdio --name editor
In another terminal, under the same user and configuration directory, retrieve the listener's credential-bearing URL:
mcp-vim --name editor --print-url
Append a target name and enter the resulting command in Vim:
:Mcp http://HOST:PORT/TOKEN/my-editor
Then ask the assistant to call targets, state, and read-lines for a buffer in my-editor. targets shows whether Vim connected; state supplies buffer IDs and changedticks. Reads include unsaved text.
Through a multibroker, tools have a prefix such as vim.targets. Start its mcp-vim child with --eager so the listener exists before the first tool call. Starting the broker alone does not connect Vim: each editor must run :Mcp. If connecting fails, check the broker's --name, bind address, token and configuration environment. Edits require a current expected_changedtick; they do not save files until the separate save tool or a writing Ex command is used.
TOOLS
targets lists connected Vim sessions, and state reports their buffers and current cursor. read-lines and grep operate on live Vim buffers, not saved disk files. apply-diff and substitute edit buffers; set-cursor deliberately changes the visible editor position. Use targets first, then state to obtain buffer IDs and changedticks. A target identifies one connected editor; a buffer ID is local to that editor.
Editing operations require the buffer's expected_changedtick from a recent state or read. A changed tick rejects stale edits instead of silently applying them to a buffer that has moved on. Diff context must also match. Explicitly addressed unloaded buffers can be loaded on demand, so reads and edits are not necessarily free of editor side effects. A buffer edit does not save the file to disk; the user remains in control of that step.
Patch text and buffer lines
apply-diff accepts Unicode text. The private JSON protocol decodes it into a Vim String; the plugin writes buffer lines in Vim's internal &encoding and sends the patch String to GNU patch. &fileencoding and &fileformat govern saving the buffer, not these temporary files. The UTF-8 path has been checked with non-ASCII text.
Patching operates on complete logical lines, with a final newline, independently of &endofline. Nonempty output without that final newline is rejected. Vim represents an embedded NUL in a buffer line as NL inside the corresponding String; writefile/readfile preserve such bytes in untouched lines.
NUL-containing diffs are rejected by the broker before permission prompting or forwarding: Vim's json_decode would drop \u0000 from Strings. There is no raw-byte diff transport for Vim yet. This differs from the byte-oriented shared helper and the filesystem tool's raw-byte base64 form.
Undo and redo
undo and redo each move one step through the selected buffer's normal Vim undo tree. Both require target, buffer and expected_changedtick, and use the write permission. The tick is checked after loading and entering that buffer. A stale tick or an unmodifiable buffer rejects the operation. Neither tool saves the buffer.
The result includes buffer, the new changedtick, changed, seq_cur (the reached undo sequence), seq_last (the latest allocated sequence) and modified. At the oldest/newest reachable state, the operation succeeds with changed false. Redo follows Vim's selected branch; it does not switch to another branch or select an arbitrary historical sequence.
When another buffer is selected, the caller's original buffer and view are restored. In the current buffer, Vim's normal undo/redo cursor adjustment is retained. Undo acts on ordinary buffer history, including human edits; it does not identify or group changes made by an assistant.
Open a file
open accepts target and a nonempty path without NUL. It adds the pathname to Vim's buffer list and loads it without selecting it. Relative paths use Vim's current effective directory at execution time. The pathname is passed to bufadd, not an Ex command or shell; wildcard and command characters are literal. A missing file becomes an empty named buffer.
An existing buffer is reused. Loaded contents, including unsaved edits, are not reread from disk. The result reports buffer, filename, changedtick, line_count and modified, ready for subsequent tools. No file is saved. Use set-cursor separately to display the buffer.
This requires the separate open action. Loading can run normal Vim autocommands and modelines, which can change editor state or fail after side effects. An error does not undo buffer creation or those effects. Permission scopes still cover an editor, not an individual pathname.
Save modified buffers
save accepts target, buffer and expected_changedtick. It runs Vim's :update in that buffer: an unmodified buffer needs no write; a modified buffer is saved to its current filename. There is no filename argument, save-as or force option. A stale tick rejects the operation.
Saving requires the separate save permission. A write grant for editing does not authorize saving. The result reports buffer, changedtick, modified and filename after the command. Inspect modified: write autocommands can leave or make the buffer modified.
Normal Vim write checks and autocommands apply. They can change text, rename or remove buffers, write other files, or fail after side effects. The tick guard checks the buffer before the command; it does not constrain autocommands or detect external changes to the disk file. A removed or unloaded target after the command produces an error even if a write occurred. The caller's original buffer and view are restored when possible.
Unrestricted Ex commands
ex accepts target and command. Without buffer, it executes the command text in that editor's current buffer, window and directory. An optional buffer makes that buffer current first and leaves it current unless the command changes it. expected_changedtick requires buffer and is checked in Vim immediately before executing the command, after entering the buffer. A stale tick prevents execution. Buffer loading and entering can still have their normal Vim side effects.
For example, command => "update" with a buffer id and its latest changedtick saves that buffer only if its contents have not changed since you inspected it. The tick does not describe the disk file or prevent autocommands from running.
The command can change the buffer, window and directory, access files, invoke shell commands or configured functions, and exit Vim. It is not restricted to the current buffer. Prefer the dedicated tools when they express the requested operation.
The permission prompt includes the command text. Persistent choices cover this exact command in this editor, any Ex command in this editor, or any Ex command in all current and future editors. Exact means the complete command string, including whitespace and newlines. Such a rule applies to all buffers in that editor: approving update is not approval for only the buffer used in the original request. Commands can invoke mappings, functions and autocommands whose behaviour changes independently of the command text.
The result is {output: STRING}, containing captured Vim messages. An error is returned as a tool error; effects before that error remain. Use noninteractive commands. Message capture suppresses ordinary message display, but does not prevent commands from prompting or blocking Vim. Request cancellation stops waiting for the reply, not a command already running in Vim. If a command exits Vim, the connection can close before a reply is delivered.
VIM PLUGIN INSTALLATION
The Vim plugin is included in the AnyEvent::MCP source distribution as vim/plugin/mcp-vim.vim. Installing the Perl distribution does not install the Vim plugin. From the source distribution, install it yourself:
mkdir -p ~/.vim/plugin
cp vim/plugin/mcp-vim.vim ~/.vim/plugin/mcp-vim.vim
Alternatively, download the published plugin:
mkdir -p ~/.vim/plugin
wget -O ~/.vim/plugin/mcp-vim.vim \
https://dist.schmorp.de/mcp-vim/1/mcp-vim.vim
The /1/ is the major version of the private Vim/broker protocol, not the MCP protocol or the Perl distribution. Restart Vim after installing or updating the plugin.
Connect a Vim instance with :Mcp URL, appending the desired target name to the broker URL (which mcp-vim --print-url can show):
:Mcp http://HOST:PORT/TOKEN/TARGET
To avoid typing the URL prefix repeatedly, configure g:mcp_broker_url in your vimrc:
let g:mcp_broker_url = 'http://HOST:PORT/TOKEN/'
Then use :Mcp TARGET, with the target name alone.
Distinct Vim instances requesting the same target name receive suffixes such as #2. Reconnecting the same instance replaces its connection and keeps its assigned name. Use targets to obtain the actual names.
The Vim plugin uses a private protocol, not MCP. Its initial raw JSON line is ["mcp-vim", cookie, major, minor, target, instance_id]; subsequent messages use Vim's ordinary JSON channel envelope. The broker accepts clients with the same major version and a minor version no newer than its own. Keep the broker and plugin versions compatible when updating them.
The broker itself can connect to a multibroker using --connect http://host:port/token/alias. Its own TCP listener remains active for Vim and browser connections. The multibroker URL and the Vim broker's own listener URL have independent credentials.
CONNECTIONS AND AUTHENTICATION
By default, the broker derives a stable port in the 16384..32767 range from its service-instance name, defaulting to default. By default its token is derived from the private global AnyEvent::MCP master secret. Configured tokens or repeated --token options replace that default. Every accepted token authenticates MCP HTTP, browser WebSocket connections and the private Vim/broker protocol. Keep these tokens private.
By default the TCP listener binds IPv4 loopback on the name-derived port. bind in [instance NAME] or --bind overrides the address and port.
--stdio, or PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio, additionally serves MCP over stdin/stdout. The TCP listener remains active. In this mode the process exits after stdio closes and pending responses have drained.
--print-url prints the URL prefix (without a Vim target name) and exits, without opening the listener. Normal startup does not print the credential. Use the same HOME and XDG_CONFIG_HOME as the running broker, for example when checking the URL of a systemd service account. Changing the global master secret changes derived tokens and invalidates URLs using those tokens. Explicitly configured tokens are independent of the master secret.
PERMISSIONS
MCP tool calls require permission independently of connection authentication. read covers targets, state, read-lines and grep. write covers apply-diff, substitute, undo, redo and set-cursor, including changes to the visible editor position. save authorizes the save tool and is independent of read and write. open independently authorizes loading files by pathname. exec authorizes the unrestricted ex tool. Neither read nor write implies exec, or each other. Explicit all-action rules also match exec. Dedicated editing tools leave buffers unsaved; save saves explicitly, and Ex commands can save files or perform other arbitrary work.
Permission prompts offer the selected editor (all its buffers), or all editors including future connections. Listing target names has its own scope. Rules identify editors by their assigned target names, not connection IDs; a saved rule also applies to a later editor using that name. Buffer filenames do not restrict these grants.
apply-diff attaches display-only View patch details containing the editor name, buffer number and supplied diff. YAD offers a separate viewer; terminal and native MCP prompts show a bounded preview inline. These details do not narrow a saved editor-wide write grant.
Rules are stored in $XDG_CONFIG_HOME/anyevent-mcp/rules/vim-NAME.ini, or the corresponding $HOME/.config path. NAME is the service-instance name. For example, mcp-vim uses rules/vim-default.ini. Subjects are target:TARGET for read/write access to an editor and targets for discovery. Ex subjects are ex:JSON_TARGET:JSON_COMMAND, where both JSON values are strings. The helper offers exact and editor-wide matchers; there is no need to write their quoting by hand. An omitted subject still matches all subjects. An allow rule needs an empty sandbox-grants value:
[rule]
decision=allow
action=read
subject=target:my-editor
sandbox-grants=
By default, questions go to the local permission helper. --ask tty and --ask yad force the built-in terminal or YAD interface without fallback. Use --ask mcp to ask through a client with form elicitation, or --ask auto to use elicitation when advertised and the helper otherwise. A declined elicitation does not fall back to the helper. The environment variable PERL_ANYEVENT_MCP_PERMISSION_ASK supplies the default when --ask is omitted. See AnyEvent::MCP::Server::Permissions for rule and prompt details.
These checks apply to MCP tools over both HTTP and stdio. They do not change the authenticated private Vim connection or the browser session protocol. Reads can still load explicitly addressed buffers and trigger Vim's usual loading behaviour; these permissions do not sandbox Vim.
OPTIONS
Common options, including --name, --bind, --token, --stdio, --connect, --print-url, --auto-allow and --auto-deny, are documented in "mcp_getoptions" in AnyEvent::MCP::Server::Simple. For this server, --bind also controls the Vim and WebSocket listener; all configured tokens authenticate those connections as well as MCP. --stdio adds an MCP JSONL connection while keeping the TCP listener active, and --print-url reports its credential-bearing URL prefix. --tool-help prints registered tools and arguments without starting a listener. For example, --auto-allow '*,write' approves buffer-editing actions but not save, open or unrestricted exec.
- --ask MODE
-
Select permission prompting:
helper,tty,yad,mcporauto. See "PERMISSIONS".
SYSTEMD
A service can run /path/to/mcp-vim directly. Set its HOME and, if needed, XDG_CONFIG_HOME so it uses the intended global secret. Run mcp-vim --print-url as the service user to obtain the connection URL; there is no separate install-time cookie generation step.