Model Context Protocol (MCP)
MCP server integration for coc.nvim to allow AI agents like OpenAI Codex to interact with the editor.
coc-mcp.txt *coc-mcp* MCP server integration for coc.nvim
MCP (Model Context Protocol) integration lets agents like OpenAI Codex connect to the running coc.nvim instance, read editor buffers (including unsaved changes), explore code through the language servers and apply edits that stay in sync with Vim/Neovim.
TABLE OF CONTENTS
Overview coc-mcp-overview
Enable coc-mcp-enable
Codex setup coc-mcp-codex
Commands coc-mcp-commands
Configuration coc-mcp-configuration
Result limits coc-mcp-result-limits
Security coc-mcp-security
Troubleshooting coc-mcp-troubleshooting
Remote over SSH coc-mcp-ssh
Resources and notifications coc-mcp-resources
1. OVERVIEW
coc.nvim starts a local MCP server over a loopback socket (TCP or Unix
socket). Codex spawns a tiny stdio bridge (coc-mcp) which forwards MCP
messages between Codex and the socket.
Protocol versions: the server accepts initialize requests for MCP
2024-11-05, 2025-06-18 and 2025-11-25 and echoes the requested
version back when supported. Sessions on 2024-11-05 (which predates
structured tool output and tool metadata) receive tools/list entries
without title, annotations or outputSchema and tools/call results
without structuredContent, so older clients only see the fields their
schema defines; newer versions get the full metadata.
2. ENABLE
The server is not started by default. Enable auto start in coc-settings.json:
| 1 | { |
| 2 | "mcp": { |
| 3 | "autoStart": true |
| 4 | } |
| 5 | } |
When it is disabled, you can still start it manually for the current
session with :CocCommand mcp.start (which ignores mcp.autoStart), and
stop it again with :CocCommand mcp.stop.
By default (mcp.transport: "auto") the server listens on a per-process
Unix socket on macOS/Linux (no local TCP port is exposed) and falls back
to loopback TCP with a random port on Windows. The connection info
(address + per-start token) is written to ~/.coc/mcp/coc-<pid>.json,
keyed by the vim pid; the file (and the unix socket, when used) is removed
when coc.nvim exits, and stale files from crashed instances are cleaned up
by the bridge whenever it scans the directory.
Use :CocCommand mcp.status to show the current address, working
directory and connected clients (each with its pid and connect/last-activity
time) and the full list of available MCP tools. :CocInfo includes the same
MCP status section.
3. CODEX SETUP
Add this stdio server to ~/.codex/config.toml:
| 1 | [mcp_servers.coc] |
| 2 | command = "node" |
| 3 | args = ["/path/to/coc.nvim/bin/coc-mcp.js"] |
| 4 | enabled = true |
Before starting Codex, start the coc.nvim MCP server with
"mcp.autoStart": true or :CocCommand mcp.start. Run codex mcp list to
verify the configuration. See
doc/coc-mcp-example.toml for timeout and tool-filter examples.
The bridge discovers coc.nvim instances through ~/.coc/mcp and needs no
environment variables. COC_MCP_DIR overrides the discovery directory for
tests or remote mounts. During initialization, the bridge waits up to five
seconds for a connection before returning an error and exiting. Set
COC_MCP_STARTUP_TIMEOUT_MS to change this timeout, and keep Codex's
startup_timeout_sec at least as large.
Multiple editor instances
Each instance publishes ~/.coc/mcp/coc-<pid>.json. The bridge selects one
as follows:
- default (
--match-cwd): first instance whose workspace contains the bridge working directory, with symlinks normalized; --match-first: first available instance, ignoring the working directory.
The bridge reconnects automatically after :CocRestart.
4. COMMANDS
:CocCommand mcp.start Start the MCP server
:CocCommand mcp.stop Stop the MCP server
:CocCommand mcp.status Show address, cwd, connected clients (pid and
connect/last-activity time) and the full
tool list
5. CONFIGURATION
All options live under the mcp section. Open all MCP settings in the Config explorer. Highlights:
mcp.autoStart— Automatically start the MCP socket server.mcp.allowedPaths— Add glob patterns for paths that MCP tools may access.mcp.deniedPaths— Add glob patterns for paths that MCP tools must never access.mcp.maxRequestsPerSecond— Limit requests per second for each MCP session.mcp.authClientPublicKey— Require clients to authenticate by signing a challenge with the matching private key.mcp.timeout— Set the timeout for mutating tool calls in milliseconds.mcp.readTimeout— Set the timeout for read-only tools such as LSP queries.mcp.allowedTools— Choose which tool names are exposed to agents; the default list is empty.
6. RESULT LIMITS
LSP list tools cap the number of returned items so large results do not
flood the agent or cost unnecessary memory/time (the server still counts
all results). Each affected tool accepts an optional maxResults
argument (minimum 1, hard maximum 1000):
| 1 | lsp/references, lsp/definition, lsp/declaration, |
| 2 | lsp/type_definition, lsp/implementation default 200 |
| 3 | lsp/document_symbols, lsp/workspace_symbols default 500 |
| 4 | lsp/diagnostics, lsp/code_actions default 100 |
| 5 | lsp/batch default 200 per method |
When the result was truncated, the structured output adds
returned (number actually returned) and truncated (true) next to
count (total found), and the text content notes how many results were
shown. The truncation happens before the results are serialized, so
asking for fewer results also makes the call faster.
lsp/batch applies maxResults to every listed method (the default is
200 per method).
7. SECURITY
- The server binds loopback only and requires a per-start random token.
- Tools are hidden behind a whitelist:
tools/listonly returns the names inmcp.allowedTools, and calls to other tools are rejected. The default is empty, so enable the tools you want explicitly incoc-settings.json(seecoc-config-mcp-allowedToolsfor the full list). - Public-key client auth: set
mcp.authClientPublicKey(PEM) and give the bridge the matching private key viaCOC_MCP_AUTH_KEY_FILE(a path to a PEM file). When configured, the client authenticates by signing a server-issued nonce (coc/challenge) and the per-start token is no longer required. This is mandatory for--connect(seecoc-mcp-ssh) so the token-bearing discovery file never has to leave the remote host. Generate a keypair withnode bin/coc-mcp.js --generate-key. Off by default (token-only). - Path access is restricted to workspace roots and opened documents
unless
mcp.allowedPathsextends it;mcp.deniedPathsalways wins. - Mutating tool calls are serialized across clients by a global write lock; read-only tools run in parallel.
- Destructive tools carry MCP
destructiveHintannotations so Codex prompts for confirmation before invoking them.
8. TROUBLESHOOTING
codex mcp list shows the server but tools do not appear:
- Start coc.nvim with
"mcp.autoStart": true, or run:CocCommand mcp.start, before starting Codex. The bridge exits if it cannot connect within five seconds. - Check that
~/.coc/mcp/coc-<pid>.jsonexists and its workspace contains the bridge working directory. Use--match-firstto ignore the workspace. - If
mcp.authClientPublicKeyis configured but the bridge has noCOC_MCP_AUTH_KEY_FILE, authentication fails: set the private key file or clear the config.
Edits are not visible on disk:
workspace/apply_editsaves all modified buffers with:waafter applying, so edited files are on disk when the tool returns (savedin the result). IfsaveErroris present, save manually withdocument/writeor:wa.
9. REMOTE OVER SSH
The server binds loopback only, so it cannot be reached over the network
directly. To use it on a remote host, forward the MCP port over SSH and
run the bridge locally. Public-key auth is required for this scenario:
never copy the per-instance discovery file (coc-<pid>.json) to the local
machine - it contains the per-start token, which also changes every time
coc.nvim restarts.
Setup on the remote host:
| 1 | # generate a keypair once (do this locally or remotely, keep the |
| 2 | # private key on the local machine): |
| 3 | node /path/to/coc.nvim/bin/coc-mcp.js --generate-key |
| 4 | |
| 5 | # set mcp.authClientPublicKey in the remote coc-settings.json to the |
| 6 | # generated public key, then restart coc.nvim. Read the port from |
| 7 | # ~/.coc/mcp/coc-<pid>.json. |
On the local machine, forward the port and configure the bridge to connect
to it with --connect, authenticated by the private key (no discovery
file, no token):
| 1 | ssh -N -L 127.0.0.1:PORT:127.0.0.1:PORT user@remote-host |
| 1 | [mcp_servers.coc] |
| 2 | command = "node" |
| 3 | args = ["/path/to/coc.nvim/bin/coc-mcp.js", "--connect=127.0.0.1:PORT"] |
| 4 | enabled = true |
| 5 | startup_timeout_sec = 30 |
| 6 | |
| 7 | [mcp_servers.coc.env] |
| 8 | COC_MCP_AUTH_KEY_FILE = "/home/user/.config/coc-mcp/key.pem" |
--connect accepts host:port (or a bare port, defaulting to
127.0.0.1; IPv6 like [::1]:port works too). The bridge sends
coc/challenge, signs the nonce with the private key from
COC_MCP_AUTH_KEY_FILE (path to a PEM file) and authenticates; the remote
server must have
mcp.authClientPublicKey set to the matching public key. Keep the private
key private; never forward it to untrusted hosts.
10. RESOURCES AND NOTIFICATIONS
The server exposes read-only coc:// resources and push notifications for
editor events. Both are opt-in: resources use the standard MCP requests,
and notifications are only sent to sessions that subscribed.
RESOURCES
Use the standard MCP requests resources/list, resources/read and
resources/templates/list:
| 1 | coc://documents/{uri} Text content of an editor document (buffer |
| 2 | first, unsaved changes included); `{uri}` is |
| 3 | the URL-encoded file URI. |
| 4 | coc://diagnostics Workspace diagnostics as JSON. |
| 5 | coc://services Language server state and initialize |
| 6 | capabilities as JSON. |
| 7 | coc://workspace Workspace root, folders and cwd as JSON. |
Reading an unknown resource returns MCP error -32002.
NOTIFICATIONS
Subscribe per session with the coc/subscribe request:
| 1 | { "jsonrpc": "2.0", "id": 1, "method": "coc/subscribe", |
| 2 | "params": { "events": ["coc/document_saved", "coc/diagnostics_changed"] } } |
coc/unsubscribe removes subscriptions. Only subscribed sessions receive
the events, and event names that do not start with coc/ are ignored:
| 1 | coc/diagnostics_changed { uri, bufnr, version, diagnostics } |
| 2 | coc/document_changed { uri, version, changes } |
| 3 | coc/document_saved { uri, version, languageId } |
| 4 | coc/workspace_folders_changed { added, removed } |
| 5 | coc/editor_state_changed { uri, bufnr, languageId } |
| 6 | coc/service_state_changed { id, state, languageIds } |
The server also broadcasts notifications/tools/list_changed to all
initialized sessions when the registered tool set changes (for example an
extension registers a tool). Query the current server status at any time
with the coc/status request (address, clients, tool list and protocol
version).
See also: coc-config-mcp.
vim:tw=78:nosta:noet:ts=8:sts=0:ft=help:noet:fen: