A NeoVim plugin that exposes Lua functions as MCP (Model Context Protocol) tools for AI coding assistants like OpenCode, Claude Code, and Cursor.
- DAP Integration: Inspect debug sessions, call stacks, variables, and evaluate expressions
- LSP Tools: Query hover info, document symbols, and diagnostics
- Undo Tree: Inspect undo history
- OpenCode Auto-Integration: Automatically registers with OpenCode when it starts
- Custom Tools: Register your own Lua functions as MCP tools
- NeoVim 0.9+
- One of: bun or Node.js 18+ with npx
- Optional: nvim-dap for debug tools
- Optional: opencode.nvim for auto-integration
{
"guill/mcp-tools.nvim",
build = "cd bridge && npm install",
config = function()
require("mcp-tools").setup()
end,
}use {
"guill/mcp-tools.nvim",
run = "cd bridge && npm install",
config = function()
require("mcp-tools").setup()
end,
}All tools and integrations are disabled by default. Enable what you need:
require("mcp-tools").setup({
-- Enable/disable built-in tools (all default to false)
tools = {
dap = true, -- Debug Adapter Protocol tools
diagnostics = true, -- LSP diagnostics
lsp = true, -- LSP hover, symbols
undo = true, -- Undo tree
test = true, -- Test tools (for development)
},
-- Enable/disable integrations (all default to false)
integrations = {
opencode = true, -- Auto-register with OpenCode
},
-- Bridge configuration
bridge = {
port = 0, -- 0 = OS assigns port
log_level = "info", -- debug, info, warn, error
},
-- Callbacks
on_ready = function(port)
print("MCP bridge ready on port " .. port)
end,
on_stop = function()
print("MCP bridge stopped")
end,
})Session Management:
| Tool | Description |
|---|---|
nvim_dap_status |
Get debug session status |
nvim_dap_run |
Start a new debug session with configuration |
nvim_dap_terminate |
Terminate the current debug session |
nvim_dap_disconnect |
Disconnect from the debug adapter |
Execution Control:
| Tool | Description |
|---|---|
nvim_dap_continue |
Continue execution (with optional wait_until_paused) |
nvim_dap_step_over |
Step over (with optional wait_until_paused) |
nvim_dap_step_into |
Step into (with optional wait_until_paused) |
nvim_dap_step_out |
Step out (with optional wait_until_paused) |
nvim_dap_run_to |
Run to a specific file and line |
nvim_dap_wait_until_paused |
Wait until debugger pauses |
Breakpoints:
| Tool | Description |
|---|---|
nvim_dap_set_breakpoint |
Set breakpoint at file:line with optional condition |
nvim_dap_remove_breakpoint |
Remove breakpoint at file:line |
nvim_dap_clear_breakpoints |
Clear all breakpoints |
nvim_dap_breakpoints |
List all breakpoints |
Inspection:
| Tool | Description |
|---|---|
nvim_dap_stacktrace |
Get current call stack |
nvim_dap_scopes |
Get scopes for a stack frame |
nvim_dap_variables |
Get variables in a scope |
nvim_dap_evaluate |
Evaluate an expression |
nvim_dap_threads |
List all threads |
nvim_dap_current_location |
Get current location with code context |
nvim_dap_program_output |
Get program stdout/stderr/console output |
| Tool | Description |
|---|---|
nvim_lsp_hover |
Get hover information |
nvim_lsp_symbols |
Get document symbols |
nvim_diagnostics_list |
Get diagnostics |
| Tool | Description |
|---|---|
nvim_undo_tree |
Get undo tree structure |
These tools verify MCP bridge async/sync execution patterns:
| Tool | Description |
|---|---|
nvim_test_async_prompt |
Tests async execution via user prompt |
nvim_test_sync_buffers |
Tests sync execution via NeoVim API |
Tools use a callback-based API. Call cb(result) to return success or cb(nil, "error message") to return an error.
Synchronous tool (most common):
local mcp = require("mcp-tools")
mcp.register({
name = "my_tool",
description = "Does something useful",
args = {
bufnr = {
type = "number",
description = "Buffer number",
required = false,
default = 0,
},
},
execute = function(cb, args)
local buf = args.bufnr == 0 and vim.api.nvim_get_current_buf() or args.bufnr
cb({ buffer = buf, lines = vim.api.nvim_buf_line_count(buf) })
end,
})Asynchronous tool (for long operations or user interaction):
mcp.register({
name = "delayed_response",
description = "Returns after a delay",
args = {
delay_ms = { type = "number", required = false, default = 1000 },
},
execute = function(cb, args)
vim.defer_fn(function()
cb({ message = "Done after delay" })
end, args.delay_ms)
end,
})Interactive tool (waits for user input):
mcp.register({
name = "confirm_action",
description = "Ask user for confirmation",
args = {
prompt = { type = "string", required = true },
},
execute = function(cb, args)
vim.ui.select({"Yes", "No"}, { prompt = args.prompt }, function(choice)
cb({ confirmed = choice == "Yes" })
end)
end,
})local mcp = require("mcp-tools")
-- Start bridge manually
mcp.start({
nvim_socket = vim.v.servername,
port = 0,
})
-- Stop bridge
mcp.stop()
-- Check status
if mcp.is_running() then
print("Bridge on port " .. mcp.get_port())
end
-- List registered tools
for name, def in pairs(mcp.list_tools()) do
print(name .. ": " .. def.description)
endRun :checkhealth mcp-tools to verify your installation.
- When OpenCode starts, this plugin detects it via
opencode.state.subscribe - Plugin spawns a TypeScript MCP bridge that connects to NeoVim via RPC
- Plugin registers the MCP server with OpenCode via
POST /mcp - OpenCode discovers tools via MCP
tools/list - When OpenCode calls a tool, the bridge invokes Lua via
nvim.call('luaeval', ...)
NeoVim Instance
├── mcp-tools.nvim (this plugin)
│ ├── Tool Registry (Lua)
│ └── MCP Bridge (TypeScript, child process)
│ ├── Connects to NeoVim via socket
│ ├── Exposes tools via MCP protocol
│ └── Routes tool calls back to Lua
└── opencode.nvim
└── Discovers nvim-tools MCP server
MIT