Skip to main content
Neovim implements the MessagePack-RPC protocol for its RPC API. This page explains the protocol details and Nvim-specific constraints.

What is MessagePack-RPC?

MessagePack-RPC is a binary RPC protocol built on top of MessagePack serialization. It enables efficient, language-agnostic remote procedure calls.

Key Characteristics

  • Binary protocol: More compact and efficient than JSON-RPC
  • Language agnostic: Works with any language that has MessagePack support
  • Bidirectional: Both client and server can initiate calls
  • Asynchronous: Supports non-blocking requests and notifications

Protocol Specification

Nvim follows the MessagePack-RPC specification with some additional constraints:

Nvim-Specific Constraints

Nvim adds these extra constraints to the standard MessagePack-RPC protocol:
Important Protocol Constraints:
  1. Response ordering: Responses must be given in reverse order of requests (like “unwinding a stack”)
  2. Sequential processing: Nvim processes all messages (requests and notifications) in the order they are received
These constraints ensure predictable behavior and prevent race conditions.

Message Types

MessagePack-RPC defines four message types:

1. Request

A request expects a response from the server. Format: [0, msgid, method, params]
  • 0: Message type (request)
  • msgid: Unique integer ID for matching responses
  • method: String method name (e.g., “nvim_command”)
  • params: Array of method parameters
[0, 1, "nvim_command", ["echo 'hello'"]]

2. Response

A response to a previous request. Format: [1, msgid, error, result]
  • 1: Message type (response)
  • msgid: ID of the request being responded to
  • error: Error object (null if successful)
  • result: Return value (null if error)
[1, 1, null, ["line 1", "line 2", "line 3"]]

3. Notification

A one-way message that does not expect a response. Format: [2, method, params]
  • 2: Message type (notification)
  • method: String method name
  • params: Array of method parameters
[2, "nvim_command", ["echo 'async message'"]]
Notifications are fire-and-forget. The sender does not wait for or receive a response.

4. Event (Nvim Extension)

Events are notifications sent from Nvim to the client (e.g., UI events, buffer updates). Format: [2, "event_name", [event_params]] Example UI redraw event:
[2, "redraw", [["put", [["H", "e", "l", "l", "o"]]]]]

Request/Response Flow

1

Client sends request

The client serializes a request with a unique message ID:
request = [0, 42, "nvim_get_current_line", []]
2

Nvim processes request

Nvim receives the request, validates parameters, and executes the API function.
3

Nvim sends response

Nvim returns a response with the same message ID:
response = [1, 42, null, "Current line contents"]
4

Client matches response

The client uses the message ID (42) to match the response to the original request.

Synchronous vs Asynchronous

Synchronous Requests (Request/Response)

Block until a response is received:
nvim = attach('socket', path='/tmp/nvim.sock')

# Blocks until response is received
result = nvim.request('nvim_eval', '2 + 2')
print(result)  # => 4

Asynchronous Notifications

Send fire-and-forget messages:
nvim = attach('socket', path='/tmp/nvim.sock')

# Returns immediately, does not wait for completion
nvim.notify('nvim_command', 'echo "async"')

Fast vs Deferred Functions

Most API functions are deferred: they are queued on the main loop and processed sequentially with normal input.
If the editor is waiting for user input in a modal fashion (e.g., hit-enter prompt), deferred requests will block.
Fast functions are served immediately without waiting in the input queue:
  • nvim_get_mode()
  • nvim_input()
  • nvim_input_mouse()
  • Other functions marked with api-fast in documentation
In Lua, use vim.in_fast_event() to detect if you’re in a fast context.

Error Handling

Errors are returned in the response message: Error Format: [error_type, error_message]
[1, 1, [1, "Invalid buffer id: 999"], null]
Common error types (from API metadata):
  • Exception: General exception
  • Validation: Invalid arguments
try:
    nvim.request('nvim_buf_get_lines', 999, 0, -1, False)
except nvim.NvimError as e:
    print(f"Error: {e}")

Extended Types (EXT)

Nvim uses MessagePack extension types for special objects:
TypeCodeDescription
Buffer0Buffer handle
Window1Window handle
Tabpage2Tabpage handle
These are encoded as MessagePack EXT types with the handle as the data:
# Buffer with handle 1
ext_type = 0
data = b'\x00\x00\x00\x01'  # 4-byte integer
buffer = msgpack.ExtType(ext_type, data)
Most client libraries handle these automatically. You typically work with opaque handle objects.

Connection Example

Here’s a low-level example showing the MessagePack-RPC protocol:
import msgpack
import socket

# Connect to Nvim socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect('/tmp/nvim.sock')

# Create unpacker for receiving messages
unpacker = msgpack.Unpacker(raw=False)

# Send a request: get API info
msgid = 1
request = [0, msgid, "nvim_get_api_info", []]
message = msgpack.packb(request)
sock.sendall(message)

# Receive response
while True:
    data = sock.recv(1024)
    if not data:
        break
    
    unpacker.feed(data)
    for msg in unpacker:
        msg_type, response_id, error, result = msg
        if response_id == msgid:
            if error:
                print(f"Error: {error}")
            else:
                print(f"API Info: {result}")
            break
    break

sock.close()

Message Batching

You can send multiple messages in sequence without waiting for responses:
import msgpack

# Send multiple requests
requests = [
    [0, 1, "nvim_command", ["echo 'first'"]],
    [0, 2, "nvim_command", ["echo 'second'"]],
    [0, 3, "nvim_eval", ["2 + 2"]],
]

for request in requests:
    message = msgpack.packb(request)
    sock.sendall(message)

# Process responses (will arrive in reverse order per Nvim constraint)
Remember: Nvim returns responses in reverse order of requests (LIFO/stack behavior).

Performance Considerations

Use Notifications for Fire-and-Forget

If you don’t need a response, use notifications:
# Faster - no response needed
nvim.notify('nvim_command', 'echo "hello"')

# Slower - waits for response
nvim.request('nvim_command', 'echo "hello"')

Batch API Calls

Use nvim_call_atomic() to batch multiple API calls into one request:
calls = [
    ['nvim_set_current_line', ['new line']],
    ['nvim_command', ['echo "done"']],
    ['nvim_eval', ['2 + 2']],
]

results = nvim.request('nvim_call_atomic', calls)
This reduces round-trip overhead.

Use Buffer Updates for Large Text

For inserting large blocks of text, use nvim_paste() instead of multiple API calls:
# Efficient for large text
nvim.request('nvim_paste', large_text, True, -1)

# Less efficient
for line in large_text.split('\n'):
    nvim.request('nvim_command', f'normal! o{line}')

Debugging RPC Communication

Enable RPC Logging

Set the $NVIM_LISTEN_ADDRESS environment variable to log RPC messages:
export NVIM_RPC_DEBUG=1
nvim --listen /tmp/nvim.sock

Inspect with tcpdump (TCP connections)

For TCP connections, you can capture packets:
tcpdump -i lo -X port 6666

Manual Protocol Testing

Use nvim_get_api_info() to explore the API:
from pynvim import attach

nvim = attach('socket', path='/tmp/nvim.sock')
api_info = nvim.request('nvim_get_api_info')

# Inspect available functions
for func in api_info[1]['functions']:
    print(f"{func['name']}: {func['parameters']}")

Security Considerations

RPC channels are implicitly trusted!The process at the other end of an RPC channel can invoke any API function, including:
  • Reading/writing files
  • Executing shell commands
  • Modifying system state

Best Practices

  1. Use Unix sockets over TCP: Named pipes are more secure than TCP sockets
  2. Bind to localhost only: Never expose TCP sockets on public interfaces
  3. Set socket permissions: Restrict access to the socket file
  4. Validate client identity: Use nvim_set_client_info() for authentication
# Set client info for identification
nvim.request('nvim_set_client_info', 
    'my-client',  # name
    {'major': 1, 'minor': 0},  # version
    'ui',  # type
    {},  # methods
    {}  # attributes
)

Client Libraries

Official and community client libraries handle MessagePack-RPC for you:
LanguageLibraryInstallation
Pythonpynvimpip install pynvim
Rubyneovim-rubygem install neovim
JavaScriptnode-clientnpm install neovim
LuaBuilt-invim.rpcrequest()
Gogo-clientgo get github.com/neovim/go-client
Rustnvim-rscargo add nvim-rs
Javaneovim-javaMaven/Gradle
Using a client library is strongly recommended over implementing the protocol manually.

Next Steps

API Reference

Browse all available API functions

Buffer API

Buffer operations and updates

Build docs developers (and LLMs) love