From bce9b82b66d2340909f58e9176645412b9b33d36 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Fri, 9 Oct 2026 06:08:08 +0100 Subject: [PATCH] feat(nvim-core): consolidate nvim tools into 5 mega-tools --- nvim-core/src/lib.rs | 380 ++++++++++++++++++++----------------------- 1 file changed, 176 insertions(+), 204 deletions(-) diff --git a/nvim-core/src/lib.rs b/nvim-core/src/lib.rs index 8d879de..4e0ab13 100644 --- a/nvim-core/src/lib.rs +++ b/nvim-core/src/lib.rs @@ -1083,208 +1083,125 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { jsonrpc: "2.0".to_string(), id, result: Some(json!({ - "tools": [ - { - "name": "nvim_buffer", - "description": "Unified buffer management: inspect, open, read, search, save, reload, or close Neovim buffers.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["get_active", "read", "open", "create_scratch", "save", "reload", "close", "list", "search", "edit", "undo", "redo"], - "description": "Action to perform on the buffer" - }, - "file": { "type": "string", "description": "Target file path (for open, read, search)" }, - "buf_id": { "type": "integer", "description": "Buffer ID (for close, reload, or split)" }, - "steps": { "type": "integer", "description": "Number of undo/redo steps to apply (default: 1)" }, - "content": { "type": "string", "description": "Initial text content (for create_scratch)" }, - "name": { "type": "string", "description": "Buffer display name (for create_scratch)" }, - "filetype": { "type": "string", "description": "Filetype syntax (for open, create_scratch)" }, - "start_line": { "type": "integer", "description": "1-indexed start line (for read)" }, - "end_line": { "type": "integer", "description": "1-indexed end line (for read)" }, - "pattern": { "type": "string", "description": "Regex or substring pattern to search for (for search)" }, - "force": { "type": "boolean", "description": "Force reload or close (for reload, close)" }, - "edits": { - "type": "array", - "description": "Array of edits to apply sequentially (for edit). Grouped by file, applied in descending order.", - "items": { - "type": "object", - "properties": { - "file": { "type": "string", "description": "Target file path" }, - "start_line": { "type": "integer", "description": "1-indexed start line" }, - "end_line": { "type": "integer", "description": "1-indexed end line" }, - "replacement_content": { "type": "string", "description": "New content" }, - "expected_content": { "type": "string", "description": "Optional: exact content expected in the replacement range to prevent line drift corruption" } - }, - "required": ["file", "start_line", "end_line", "replacement_content"] - } - } - }, - "required": ["action"] - } - }, - { - "name": "nvim_window", - "description": "Manage Neovim windows and splits: list open windows, query or focus active window, create splits, or close windows.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["list", "get_active", "focus", "split", "close"], - "description": "Window operation to perform" - }, - "win_id": { "type": "integer", "description": "Window ID to focus or close" }, - "file": { "type": "string", "description": "File to open in split" }, - "buf_id": { "type": "integer", "description": "Buffer ID to attach to split" }, - "direction": { "type": "string", "enum": ["vertical", "horizontal"], "description": "Split orientation (default: vertical)" }, - "force": { "type": "boolean", "description": "Force close window" } - }, - "required": ["action"] - } - }, - { - "name": "nvim_view", - "description": "Editor navigation and viewport introspection: jump to line, query cursor position, get visible viewport lines, or get visual selection.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["goto_line", "get_cursor", "get_viewport", "get_selection"], - "description": "Navigation/inspection action to perform" - }, - "file": { "type": "string", "description": "File path (for goto_line)" }, - "line": { "type": "integer", "description": "Target line number (1-indexed, for goto_line)" } - }, - "required": ["action"] - } - }, - { - "name": "nvim_diagnostics", - "description": "LSP diagnostics and quickfix integration: fetch current diagnostics, inject LSP diagnostic markers, or populate the quickfix list.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["get", "set", "set_quickfix"], - "description": "Diagnostic action to perform" - }, - "line": { "type": "integer", "description": "Line number (1-indexed, for set)" }, - "message": { "type": "string", "description": "Diagnostic warning/error message (for set)" }, - "items": { - "type": "array", - "description": "Quickfix entries (for set_quickfix)", - "items": { - "type": "object", - "properties": { - "filename": { "type": "string" }, - "lnum": { "type": "integer" }, - "text": { "type": "string" } - }, - "required": ["filename", "lnum", "text"] - } - }, - "qf_action": { "type": "string", "enum": ["replace", "append", "prepend"], "description": "Quickfix list modification action (for set_quickfix)" } - }, - "required": ["action"] - } - }, - { - "name": "nvim_visual", - "description": "Visual feedback, syntax highlighting, ghost text extmarks, and diff preview overlays.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["preview", "extmark", "highlight", "clear_highlight"], - "description": "Visual feedback action" - }, - "file_path": { "type": "string", "description": "File path (for preview)" }, - "start_line": { "type": "integer", "description": "Start line (1-indexed, for preview, highlight)" }, - "end_line": { "type": "integer", "description": "End line (1-indexed, for preview, highlight)" }, - "preview_content": { "type": "string", "description": "Proposed replacement code (for preview)" }, - "line": { "type": "integer", "description": "Line number (1-indexed, for extmark)" }, - "text": { "type": "string", "description": "Virtual ghost text to display (for extmark)" }, - "highlight_group": { "type": "string", "description": "Highlight group (for extmark, highlight, e.g. 'Comment', 'IncSearch')" }, - "buf_id": { "type": "integer", "description": "Buffer ID (for highlight, clear_highlight)" }, - "duration_ms": { "type": "integer", "description": "Auto-clear duration in ms (for highlight, default: 5000)" } - }, - "required": ["action"] - } - }, - { - "name": "nvim_execute_lua", - "description": "Execute arbitrary Lua code directly in the active Neovim session. Primary tool for editing files via vim.api.nvim_buf_set_lines, querying editor state, and triggering notifications.", - "inputSchema": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "The Lua code string to execute in Neovim" - } - }, - "required": ["code"] - } - }, - { - "name": "nvim_system", - "description": "System diagnostics, terminal interaction, and editor notification messages.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["get_info", "get_messages", "send_to_terminal"], - "description": "System action to perform" - }, - "command": { "type": "string", "description": "Shell command to send (for send_to_terminal)" }, - "tail": { "type": "integer", "description": "Number of message lines to return (for get_messages)" } - }, - "required": ["action"] - } - }, - { - "name": "nvim_lsp", - "description": "Language Server Protocol integration for semantic queries and safe workspace-wide refactoring.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["rename", "definition", "references", "hover", "code_action"], - "description": "LSP action to perform" - }, - "file": { "type": "string", "description": "Target file path" }, - "line": { "type": "integer", "description": "1-indexed line number" }, - "col": { "type": "integer", "description": "0-indexed column number" }, - "new_name": { "type": "string", "description": "New name for rename action" } - }, - "required": ["action"] - } - }, - { - "name": "nvim_ast", - "description": "Tree-sitter AST queries for semantic code exploration and outlining.", - "inputSchema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["query", "outline"], - "description": "AST action to perform" - }, - "file": { "type": "string", "description": "Target file path" }, - "query": { "type": "string", "description": "Tree-sitter query string" }, - "preset": { "type": "string", "description": "Query preset (e.g., 'functions', 'classes')" } - }, - "required": ["action"] - } - } - ] + "tools": +[ + { + "name": "nvim_buffer", + "description": "Core Text Editing: read, replace, save, and manipulate Neovim buffers in memory.", + "inputSchema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["read", "replace", "save", "undo", "redo", "create_scratch"], + "description": "Action to perform on the buffer" + }, + "file": { "type": "string", "description": "Target file path (for read, replace, save)" }, + "start_line": { "type": "integer", "description": "1-indexed start line (for read)" }, + "end_line": { "type": "integer", "description": "1-indexed end line (for read)" }, + "content": { "type": "string", "description": "Initial text content (for create_scratch)" }, + "name": { "type": "string", "description": "Buffer display name (for create_scratch)" }, + "steps": { "type": "integer", "description": "Number of undo/redo steps to apply (default: 1)" }, + "edits": { + "type": "array", + "description": "Array of edits to apply sequentially (for replace). Grouped by file, applied in descending order.", + "items": { + "type": "object", + "properties": { + "file": { "type": "string" }, + "start_line": { "type": "integer" }, + "end_line": { "type": "integer" }, + "replacement_content": { "type": "string" }, + "expected_content": { "type": "string" } + }, + "required": ["file", "start_line", "end_line", "replacement_content"] + } + } + }, + "required": ["action"] + } + }, + { + "name": "nvim_workspace", + "description": "Window & Editor State: list buffers, windows, focus splits, and manage cwd.", + "inputSchema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["list_buffers", "list_windows", "focus", "split", "cwd"], + "description": "Workspace operation to perform" + }, + "win_id": { "type": "integer", "description": "Window ID to focus" }, + "file": { "type": "string", "description": "File to open in split" }, + "direction": { "type": "string", "enum": ["vertical", "horizontal"], "description": "Split orientation (default: vertical)" }, + "path": { "type": "string", "description": "Target directory (for cwd action)" } + }, + "required": ["action"] + } + }, + { + "name": "nvim_intelligence", + "description": "Code Semantics: LSP queries (hover, definition, references, code_action, rename, diagnostics) and AST outlining/queries.", + "inputSchema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["hover", "definition", "references", "outline", "query", "diagnostics", "rename", "code_action"], + "description": "Intelligence action to perform" + }, + "file": { "type": "string", "description": "Target file path" }, + "line": { "type": "integer", "description": "1-indexed line number (for LSP)" }, + "col": { "type": "integer", "description": "0-indexed column number (for LSP)" }, + "new_name": { "type": "string", "description": "New name (for rename action)" }, + "query": { "type": "string", "description": "Tree-sitter query string (for AST query)" }, + "preset": { "type": "string", "description": "Query preset (e.g., 'functions', 'classes' for AST query)" } + }, + "required": ["action"] + } + }, + { + "name": "nvim_ui", + "description": "Visual Feedback: communicating visually with the human user via highlights and ghost text.", + "inputSchema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["highlight", "ghost_text", "clear"], + "description": "UI action" + }, + "buf_id": { "type": "integer", "description": "Buffer ID to apply to" }, + "start_line": { "type": "integer", "description": "1-indexed start line (for highlight)" }, + "end_line": { "type": "integer", "description": "1-indexed end line (for highlight)" }, + "line": { "type": "integer", "description": "1-indexed line number (for ghost_text)" }, + "text": { "type": "string", "description": "Virtual text to display (for ghost_text)" }, + "highlight_group": { "type": "string", "description": "Highlight group (e.g. 'Comment', 'IncSearch')" }, + "duration_ms": { "type": "integer", "description": "Auto-clear duration in ms (for highlight, default: 5000)" } + }, + "required": ["action"] + } + }, + { + "name": "nvim_exec", + "description": "The Escape Hatch: execute lua read-only queries, run vimscript commands, or send commands to the terminal.", + "inputSchema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["lua", "vimscript", "terminal"], + "description": "Execution action" + }, + "code": { "type": "string", "description": "Lua code or Vimscript command to execute" }, + "command": { "type": "string", "description": "Shell command to send (for terminal)" } + }, + "required": ["action"] + } + } +] + })), error: None, }).await; @@ -1297,8 +1214,42 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { let action = args.get("action").and_then(|v| v.as_str()).unwrap_or(""); - match name { - "nvim_buffer" => match action { + let (mapped_name, mapped_action) = match (name, action) { + ("nvim_buffer", "read") => ("nvim_buffer", "read"), + ("nvim_buffer", "replace") => ("nvim_buffer", "edit"), + ("nvim_buffer", "save") => ("nvim_buffer", "save"), + ("nvim_buffer", "undo") => ("nvim_buffer", "undo"), + ("nvim_buffer", "redo") => ("nvim_buffer", "redo"), + ("nvim_buffer", "create_scratch") => ("nvim_buffer", "create_scratch"), + + ("nvim_workspace", "list_buffers") => ("nvim_buffer", "list"), + ("nvim_workspace", "list_windows") => ("nvim_window", "list"), + ("nvim_workspace", "focus") => ("nvim_window", "focus"), + ("nvim_workspace", "split") => ("nvim_window", "split"), + ("nvim_workspace", "cwd") => ("nvim_system", "cwd"), + + ("nvim_intelligence", "hover") => ("nvim_lsp", "hover"), + ("nvim_intelligence", "definition") => ("nvim_lsp", "definition"), + ("nvim_intelligence", "references") => ("nvim_lsp", "references"), + ("nvim_intelligence", "rename") => ("nvim_lsp", "rename"), + ("nvim_intelligence", "code_action") => ("nvim_lsp", "code_action"), + ("nvim_intelligence", "outline") => ("nvim_ast", "outline"), + ("nvim_intelligence", "query") => ("nvim_ast", "query"), + ("nvim_intelligence", "diagnostics") => ("nvim_diagnostics", "get"), + + ("nvim_ui", "highlight") => ("nvim_visual", "highlight"), + ("nvim_ui", "ghost_text") => ("nvim_visual", "extmark"), + ("nvim_ui", "clear") => ("nvim_visual", "clear_highlight"), + + ("nvim_exec", "lua") => ("nvim_execute_lua", ""), + ("nvim_exec", "vimscript") => ("nvim_system", "vimscript"), + ("nvim_exec", "terminal") => ("nvim_system", "send_to_terminal"), + + (n, a) => (n, a), + }; + + match mapped_name { + "nvim_buffer" => match mapped_action { "get_active" => match get_nvim_active_buffer().await { Ok(content) => { send_text_result!(id.clone(), content); @@ -2147,6 +2098,27 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { send_error(id, -32602, "Missing 'command'").await; } } + "cwd" => { + let lua_code = + if let Some(path) = args.get("path").and_then(|v| v.as_str()) { + format!( + "vim.cmd('cd {}'); return vim.fn.getcwd()", + path.replace("\\", "\\\\").replace("'", "\\'") + ) + } else { + "return vim.fn.getcwd()".to_string() + }; + handle_lua_result!(id, execute_nvim_lua(&lua_code)); + } + "vimscript" => { + if let Some(code) = args.get("code").and_then(|v| v.as_str()) { + let lua_code = + format!("vim.cmd([=[{}]=]); return 'Success'", code); + handle_lua_result!(id, execute_nvim_lua(&lua_code)); + } else { + send_error(id, -32602, "Missing 'code'").await; + } + } _ => { send_error( id,