MCP server for AI-assisted control of Neovim via msgpack-RPC
nvim-mcp demonstrates strong definition quality across 16 tools with consistent naming conventions, comprehensive descriptions, and well-defined input schemas. All tools follow verb-noun naming patterns (connect, send_command, get_state, etc.). Descriptions are detailed and contextual, ranging from 150-400 characters with clear guidance on when to use each tool. Parameter schemas are consistently typed with string and integer constraints. However, output schemas are not explicitly documented in the tool definitions, and several tools lack explicit enum constraints for categorical parameters. Error handling descriptions are present but could be more granular regarding error recovery strategies.
Add virtual text to a line in a Neovim buffer using MCP extmarks. Virtual text is non-editable UI-only text that can appear at the end of a line (eol), above it (above), or below it (below). This is a mutation tool — it modifies Neovim's display state but not buffer content.
Clear all MCP highlights from a buffer. Read/write.
Clear all MCP virtual text from a buffer.
Connect to a running Neovim instance over its Unix socket or TCP address. Call this before any other tool if the agent is not yet connected. Connection is persistent for the session; you only need to call it once unless you want to switch instances. Called with no arguments: auto-connects when exactly one instance is running; returns a list of instances when multiple are found. Optional selection (provide at most one): - index: pick from the listed instances (1-based). - socket_path: connect directly to a known Unix socket or host:port. - terminal_pid: find the Neovim instance whose process tree contains this PID (useful when Neovim runs inside a specific terminal). Returns {connected, cwd, file} on success, or {error} with details on failure (e.g. no instances found, connection timeout, bad index).
Find and replace text in a Neovim buffer. The edit happens in-memory and is fully undoable — nothing is written to disk until the user saves. file: path relative to Neovim's cwd (as shown in `get_state` buffers). old_string: the exact text to find. Must match exactly once in the buffer; returns an error if not found or if it matches multiple locations. Include surrounding lines to disambiguate. new_string: the replacement text. Creates the buffer if it doesn't already exist. Use this for targeted edits. Use `write_full_buf` instead when replacing the entire buffer content. Use `read_full_buf` or `read_buf_range` first if you need to see the current content before editing. Returns {start_line, lines_removed, lines_added, total_lines} on success, or {error} with a message on failure.
Output schemas not explicitly documented. Tools return dicts with implicit fields (e.g. 'connected', 'cwd', 'file' for connect; 'output' for send_command) but these are not formally declared in tool definitions. LLMs cannot plan downstream chaining without explicit return type documentation.
State retrieval tools (get_state, get_state_brief, clear_highlights, clear_virtual_texts) have truncated or minimal descriptions (<65 chars). get_state description is only 'Read Neovim's full state: open buffers, windows, cursor positions, folds, diagnostics, marks, terminals, highlights, and virtual text. Read-only.', lacks guidance on when to call, what it returns structurally, or how to filter results. clear_highlights and clear_virtual_texts are one-liners.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | 2026-07-28+ | v2 |
Get LSP diagnostics from all open buffers in Neovim. Read-only. Use this for a project-wide overview of errors and warnings. Use `get_buf_diagnostics` instead when you only need diagnostics for a specific file — it is more focused and returns less data. Returns a list of {file, line, col, severity, message, source}. severity is one of "error", "warning", "info", "hint". Returns an empty list when there are no diagnostics. Results depend on which LSP servers are attached and which buffers are loaded in Neovim.
Get LSP diagnostics for a single Neovim buffer. Read-only. file: path relative to Neovim's cwd (as shown in `get_state` buffers). The buffer must already be open in Neovim; returns an error otherwise. Use this when you need diagnostics for one specific file. Use `get_all_diagnostics` instead for a project-wide overview. Returns a list of {file, line, col, severity, message, source}. severity is one of "error", "warning", "info", "hint". Returns an empty list when the buffer has no diagnostics.
Read Neovim's full state: open buffers, windows, cursor positions, folds, diagnostics, marks, terminals, highlights, and virtual text. Read-only.
Read Neovim's state in brief form: only active and alternate windows with minimal context. Much smaller than `get_state`, faster to call, and better for frequent status checks. Read-only.
Highlight a line range in a Neovim buffer using an MCP extmark. The highlight is stored as metadata and can be queried via `get_state`. This is a mutation tool — it modifies Neovim's display state but not buffer content.
Read a specific line range from a Neovim buffer. Read-only; reads from Neovim's in-memory buffer, which may differ from the file on disk if there are unsaved changes. file: path relative to Neovim's cwd (as shown in `get_state` buffers). The buffer must already be open in Neovim; returns an error otherwise. start_line: first line to read (1-indexed, inclusive). end_line: last line to read (1-indexed, inclusive). Out-of-range values are clamped to the buffer bounds. If start_line > end_line they are swapped.
Read the full content of a Neovim buffer. Read-only; reads from Neovim's in-memory buffer, which may differ from the file on disk if there are unsaved changes. file: path relative to Neovim's cwd (as shown in `get_state` buffers). The buffer must already be open in Neovim; returns an error otherwise. Use this when you need to see the entire file. Use `read_buf_range` instead when you only need a specific section — it returns less data. Returns {lines, total_lines}. lines is a list of strings, each prefixed with its 1-based line number (e.g. "1: first line").
Run one or more Vim ex commands in Neovim. This is a mutation tool — commands can modify buffers, files on disk, windows, and editor state. command: a single command string or a list of strings, without the leading ':'. E.g. "w", "e src/main.py", "42", "wincmd v", "lua vim.print(...)", or ["wincmd p", "e file.py", "wincmd p"]. Use this for editor operations that don't have a dedicated tool (e.g. saving, opening files, splitting windows, setting options). Use `send_keys` instead when you need normal-mode motions or operator sequences. Use `find_and_replace_buf` or `write_full_buf` for buffer text edits — they are safer and provide undo. Returns {output} with the command's captured output, or {error} if the command failed. When given a list, returns a list of results in the same order; execution stops on the first error.
Send raw keystrokes to Neovim as if typed by the user. This is a mutation tool — keystrokes can modify buffers, change mode, and trigger editor actions. keys: a string of Vim keystrokes. Esc is prepended automatically, so input always begins in normal mode. Multi-mode sequences must be sent in a single call (e.g. "17GVG", not "17GV" then "G"). Use Vim notation for special keys (e.g. "<CR>", "<C-w>v", "<Tab>"). Use this for normal-mode motions, visual selections, or operator sequences. Use `send_command` for ex commands, and `find_and_replace_buf` or `write_full_buf` for text edits — they are safer and provide structured results. Returns {sent} confirming the keys that were dispatched. Keystrokes are fire-and-forget; errors from the resulting Vim actions are not captured in the return value.
Type text into a terminal buffer's running program (usually a shell) by writing to its job channel. This is a mutation tool — the text reaches the program's stdin as if typed, but is not executed unless submit is true. text: the text to send, raw. In most shells an embedded newline acts like pressing Enter, so multi-line text may execute line by line. When submit is false, trailing newlines are stripped so nothing runs by accident. terminal: which terminal to target — a buffer number or buffer name, as listed under `terminals` in `get_state` / `get_state_brief`. Names match exactly first, then by unique substring. Omit it when exactly one terminal exists; with several open, omitting it returns an error listing them. submit: false (default) leaves the text at the prompt for the user to review and press Enter. true appends a carriage return so the program executes it immediately. NEVER pass submit=true unless the user has explicitly asked for the command to be run — "put", "paste", "type", or "prepare" a command always means submit=false. Suggesting a command yourself is not permission to run it. When in doubt, use submit=false and let the user press Enter. Use this whenever text needs to go into a terminal. It works regardless of focus, mode, or visibility and never moves the user's cursor — unlike `send_keys`, which requires focusing the terminal and juggling modes. Terminal buffers cannot be edited with the buffer tools. Returns {sent, terminal, buf, submitted} on success — sent is the byte count actually written. On failure returns {error}, including a `terminals` list when the target was missing or ambiguous.
Replace the entire content of a Neovim buffer. The edit happens in-memory and is fully undoable — nothing is written to disk until the user saves. file: path relative to Neovim's cwd (as shown in `get_state` buffers). content: the full new text for the buffer. Creates the buffer if it doesn't already exist. Use this when you need to rewrite the whole file. Use `find_and_replace_buf` instead for targeted edits that preserve surrounding content. Returns {total_lines} with the new line count.
Enum constraints missing for categorical parameters. 'position' parameter in add_virtual_text accepts 'eol', 'above', 'below' but is typed as string without enum; 'color' parameter in highlight_buffer is a free-form string (Vim highlight group name) without a constrained list or validation hint. LLMs may hallucinate invalid values.
Error recovery guidance inconsistent. Most tool descriptions include error return examples ('returns {error}') but do not provide actionable recovery hints (e.g. 'Try connect() first if not yet connected'; 'Use search_users() to find the user_id'). Agents lack guidance on what to do when a tool fails.
Result pagination/limits not declared for list-returning tools. get_all_diagnostics returns a list with no stated limit or pagination support. If diagnostics grow large, agents face context explosion. Tool should document limits and offer pagination (offset/limit or cursor-based).