From d60d6ca70b4c18b29d140aad481952e62b54b944 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Sat, 26 Sep 2026 20:22:59 +0100 Subject: [PATCH] Implement nvim tool mitigations --- nvim-core/src/lib.rs | 53 ++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/nvim-core/src/lib.rs b/nvim-core/src/lib.rs index 5530f3f..1b6982a 100644 --- a/nvim-core/src/lib.rs +++ b/nvim-core/src/lib.rs @@ -753,7 +753,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { "tools": [ { "name": "nvim_goto_line", - "description": "Open a file and jump to a specific line", + "description": "Open a file and jump to a specific line. WARNING: Jumps the cursor in the active window. Do NOT use if you might disrupt the user's current workflow. Prefer nvim_split_window if investigating.", "inputSchema": { "type": "object", "properties": { @@ -773,7 +773,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { }, { "name": "nvim_get_cursor", - "description": "Get the current cursor position (line and column) in the active Neovim buffer", + "description": "Get the current cursor position. WARNING: Transient state. Do NOT base code edits strictly on cursor position. Use absolute line numbers.", "inputSchema": { "type": "object", "properties": {} @@ -781,7 +781,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { }, { "name": "nvim_get_visual_selection", - "description": "Get the text that is currently highlighted or was last highlighted in Visual mode", + "description": "Get visually selected text. WARNING: Transient state. The user might have moved the cursor. Verify contents before editing.", "inputSchema": { "type": "object", "properties": {} @@ -793,7 +793,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { "inputSchema": { "type": "object", "properties": { - "line": { "type": "integer" }, + "line": { "type": "integer", "description": "Line number (1-indexed)" }, "message": { "type": "string" } }, "required": ["line", "message"] @@ -805,7 +805,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { "inputSchema": { "type": "object", "properties": { - "line": { "type": "integer" }, + "line": { "type": "integer", "description": "Line number (1-indexed)" }, "text": { "type": "string" }, "highlight_group": { "type": "string", "description": "Optional. E.g., 'DiagnosticVirtualTextInfo', 'Comment'" } }, @@ -973,8 +973,8 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { "type": "object", "properties": { "buf_id": { "type": "integer" }, - "start_line": { "type": "integer" }, - "end_line": { "type": "integer" }, + "start_line": { "type": "integer", "description": "1-indexed start line" }, + "end_line": { "type": "integer", "description": "1-indexed end line" }, "group": { "type": "string" }, "duration_ms": { "type": "integer", "description": "Duration to show highlight in milliseconds. Set to 0 for permanent (until cleared manually)." }, "clear_only": { "type": "boolean", "description": "If true, will only clear existing highlights and ignore start/end lines." } @@ -1342,12 +1342,23 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { let code = " local first = vim.fn.line('w0') local last = vim.fn.line('w$') + local total = vim.api.nvim_buf_line_count(0) local lines = vim.api.nvim_buf_get_lines(0, first - 1, last, false) local res = {} for i, line in ipairs(lines) do table.insert(res, tostring(first + i - 1) .. ': ' .. line) end - return table.concat(res, '\\n') + local content = table.concat(res, '\\n') + + local pct = math.floor(((last - first + 1) / total) * 100) + local result = { + viewport_start = first, + viewport_end = last, + total_buffer_lines = total, + warning = string.format('You are only seeing %d%% of this file. Do not assume variables are undefined just because they are not in the viewport.', pct), + content = content + } + return result "; match execute_nvim_lua(code).await { Ok(res) => send_text_result!(id.clone(), res), @@ -1402,13 +1413,27 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { end local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local matches = {{}} - for i, line in ipairs(lines) do - if string.match(line, pattern) then - table.insert(matches, tostring(i) .. ': ' .. line) + + local pcall_ok, _ = pcall(function() + for i, line in ipairs(lines) do + if vim.fn.match(line, '\\\\v' .. pattern) >= 0 then + table.insert(matches, tostring(i) .. ': ' .. line) + end + end + end) + + if not pcall_ok or #matches == 0 then + matches = {{}} + for i, line in ipairs(lines) do + if string.find(line, pattern, 1, true) then + table.insert(matches, tostring(i) .. ': ' .. line) + end + end + if #matches > 0 then + table.insert(matches, 1, 'Regex failed or no results, but found these literal matches:') + else + return 'No matches found.' end - end - if #matches == 0 then - return 'No matches found.' end return table.concat(matches, '\\n') ");