Implement nvim tool mitigations

This commit is contained in:
Riz Ashraf committed 2026-09-26 20:22:59 +01:00
1 parent 24aacdfae1
commit d60d6ca70b
1 file changed
+35 -10
+35 -10
View File
@@ -753,7 +753,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
"tools": [ "tools": [
{ {
"name": "nvim_goto_line", "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": { "inputSchema": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -773,7 +773,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
}, },
{ {
"name": "nvim_get_cursor", "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": { "inputSchema": {
"type": "object", "type": "object",
"properties": {} "properties": {}
@@ -781,7 +781,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
}, },
{ {
"name": "nvim_get_visual_selection", "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": { "inputSchema": {
"type": "object", "type": "object",
"properties": {} "properties": {}
@@ -793,7 +793,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
"inputSchema": { "inputSchema": {
"type": "object", "type": "object",
"properties": { "properties": {
"line": { "type": "integer" }, "line": { "type": "integer", "description": "Line number (1-indexed)" },
"message": { "type": "string" } "message": { "type": "string" }
}, },
"required": ["line", "message"] "required": ["line", "message"]
@@ -805,7 +805,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
"inputSchema": { "inputSchema": {
"type": "object", "type": "object",
"properties": { "properties": {
"line": { "type": "integer" }, "line": { "type": "integer", "description": "Line number (1-indexed)" },
"text": { "type": "string" }, "text": { "type": "string" },
"highlight_group": { "type": "string", "description": "Optional. E.g., 'DiagnosticVirtualTextInfo', 'Comment'" } "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", "type": "object",
"properties": { "properties": {
"buf_id": { "type": "integer" }, "buf_id": { "type": "integer" },
"start_line": { "type": "integer" }, "start_line": { "type": "integer", "description": "1-indexed start line" },
"end_line": { "type": "integer" }, "end_line": { "type": "integer", "description": "1-indexed end line" },
"group": { "type": "string" }, "group": { "type": "string" },
"duration_ms": { "type": "integer", "description": "Duration to show highlight in milliseconds. Set to 0 for permanent (until cleared manually)." }, "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." } "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 = " let code = "
local first = vim.fn.line('w0') local first = vim.fn.line('w0')
local last = vim.fn.line('w$') 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 lines = vim.api.nvim_buf_get_lines(0, first - 1, last, false)
local res = {} local res = {}
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
table.insert(res, tostring(first + i - 1) .. ': ' .. line) table.insert(res, tostring(first + i - 1) .. ': ' .. line)
end 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 { match execute_nvim_lua(code).await {
Ok(res) => send_text_result!(id.clone(), res), Ok(res) => send_text_result!(id.clone(), res),
@@ -1402,14 +1413,28 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
end end
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local matches = {{}} local matches = {{}}
local pcall_ok, _ = pcall(function()
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
if string.match(line, pattern) then if vim.fn.match(line, '\\\\v' .. pattern) >= 0 then
table.insert(matches, tostring(i) .. ': ' .. line) table.insert(matches, tostring(i) .. ': ' .. line)
end end
end end
if #matches == 0 then 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.' return 'No matches found.'
end end
end
return table.concat(matches, '\\n') return table.concat(matches, '\\n')
"); ");
match execute_nvim_lua(&code).await { match execute_nvim_lua(&code).await {