feat(nvim): add dedicated mcp tools for file reading and regex searching

This commit is contained in:
Riz Ashraf committed 2026-09-26 18:23:06 +01:00
1 parent b1fc381107
commit 24aacdfae1
1 file changed
+91
+91
View File
@@ -999,6 +999,31 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
"type": "object", "type": "object",
"properties": {} "properties": {}
} }
},
{
"name": "nvim_read_file",
"description": "Read specific lines from a file buffer (like cat or sed -n). Reads unsaved changes if open.",
"inputSchema": {
"type": "object",
"properties": {
"file": { "type": "string" },
"start_line": { "type": "integer", "description": "1-indexed start line (optional, defaults to 1)" },
"end_line": { "type": "integer", "description": "1-indexed end line (optional, defaults to end of file)" }
},
"required": ["file"]
}
},
{
"name": "nvim_search_file",
"description": "Search for a regex pattern within a file buffer (like grep). Searches unsaved changes if open.",
"inputSchema": {
"type": "object",
"properties": {
"file": { "type": "string" },
"pattern": { "type": "string", "description": "Lua-compatible regex pattern to search for" }
},
"required": ["file", "pattern"]
}
} }
] ]
})), })),
@@ -1329,6 +1354,72 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
Err(e) => send_error(id, -32603, &e).await, Err(e) => send_error(id, -32603, &e).await,
} }
} }
"nvim_read_file" => {
if let Some(file) = args.get("file").and_then(|v| v.as_str()) {
let start_line = args.get("start_line").and_then(|v| v.as_i64()).unwrap_or(1);
let end_line = args.get("end_line").and_then(|v| v.as_i64()).unwrap_or(-1);
let escaped_file = file.replace('\\', "\\\\").replace('"', "\\\"");
let code = format!("
local file = \"{escaped_file}\"
local start_idx = {start_line} - 1
local end_idx = {end_line}
local bufnr = vim.fn.bufnr(file, true)
if vim.fn.bufloaded(bufnr) == 0 then
vim.fn.bufload(bufnr)
end
local lines = vim.api.nvim_buf_get_lines(bufnr, start_idx, end_idx, false)
local res = {{}}
for i, line in ipairs(lines) do
local line_num = start_idx + i
table.insert(res, tostring(line_num) .. ': ' .. line)
end
return table.concat(res, '\\n')
");
match execute_nvim_lua(&code).await {
Ok(res) => send_text_result!(id.clone(), res),
Err(e) => send_error(id, -32603, &e).await,
}
} else {
send_error(id, -32602, "Missing 'file'").await;
}
}
"nvim_search_file" => {
if let (Some(file), Some(pattern)) = (
args.get("file").and_then(|v| v.as_str()),
args.get("pattern").and_then(|v| v.as_str()),
) {
let escaped_file = file.replace('\\', "\\\\").replace('"', "\\\"");
let escaped_pattern = pattern.replace('\\', "\\\\").replace('"', "\\\"");
let code = format!("
local file = \"{escaped_file}\"
local pattern = \"{escaped_pattern}\"
local bufnr = vim.fn.bufnr(file, true)
if vim.fn.bufloaded(bufnr) == 0 then
vim.fn.bufload(bufnr)
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)
end
end
if #matches == 0 then
return 'No matches found.'
end
return table.concat(matches, '\\n')
");
match execute_nvim_lua(&code).await {
Ok(res) => send_text_result!(id.clone(), res),
Err(e) => send_error(id, -32603, &e).await,
}
} else {
send_error(id, -32602, "Missing 'file' or 'pattern'").await;
}
}
"nvim_execute_lua" => { "nvim_execute_lua" => {
if let Some(code) = args.get("code").and_then(|v| v.as_str()) { if let Some(code) = args.get("code").and_then(|v| v.as_str()) {