Compare commits

...
2 Commits
3 changed files with 101 additions and 10 deletions

No files matched your search

+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"]
}
} }
] ]
})), })),
@@ -1330,6 +1355,72 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
} }
} }
"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()) {
// BAKE IN: Block interactive prompts that cause server deadlocks // BAKE IN: Block interactive prompts that cause server deadlocks
+9 -9
View File
@@ -631,7 +631,7 @@ mod tests {
let create_handler = CreateEntitiesHandler; let create_handler = CreateEntitiesHandler;
let args = json!({ let args = json!({
"entities": [ "entities": [
{"name": "Alice", "entityType": "Person", "observations": ["Likes Bob"]} {"name": "Alice", "entity_type": "Person", "observations": ["Likes Bob"]}
] ]
}); });
@@ -677,7 +677,7 @@ mod tests {
let handler = CreateRelationsHandler; let handler = CreateRelationsHandler;
let args = json!({ let args = json!({
"relations": [ "relations": [
{"from": "A", "to": "B", "relationType": "knows"} {"from": "A", "to": "B", "relation_type": "knows"}
] ]
}); });
let res = handler.execute(args, state.clone()).await.unwrap(); let res = handler.execute(args, state.clone()).await.unwrap();
@@ -706,7 +706,7 @@ mod tests {
let add_obs = AddObservationsHandler; let add_obs = AddObservationsHandler;
let args_obs = json!({ let args_obs = json!({
"observations": [ "observations": [
{"entityName": "A", "contents": ["Obs 1", "Obs 2"]} {"entity_name": "A", "contents": ["Obs 1", "Obs 2"]}
] ]
}); });
let res1 = add_obs.execute(args_obs, state.clone()).await.unwrap(); let res1 = add_obs.execute(args_obs, state.clone()).await.unwrap();
@@ -722,7 +722,7 @@ mod tests {
let del_entity = DeleteEntitiesHandler; let del_entity = DeleteEntitiesHandler;
let res4 = del_entity let res4 = del_entity
.execute(json!({"entityNames": ["A"]}), state.clone()) .execute(json!({"entity_names": ["A"]}), state.clone())
.await .await
.unwrap(); .unwrap();
assert_eq!(res4, "Entities deleted"); assert_eq!(res4, "Entities deleted");
@@ -742,8 +742,8 @@ mod tests {
let create_handler = CreateEntitiesHandler; let create_handler = CreateEntitiesHandler;
let args_ent = json!({ let args_ent = json!({
"entities": [ "entities": [
{"name": "X", "entityType": "File", "observations": ["Obs X"], "namespace": "global"}, {"name": "X", "entity_type": "File", "observations": ["Obs X"], "namespace": "global"},
{"name": "Y", "entityType": "File", "observations": ["Obs Y"], "namespace": "global"} {"name": "Y", "entity_type": "File", "observations": ["Obs Y"], "namespace": "global"}
] ]
}); });
create_handler create_handler
@@ -754,7 +754,7 @@ mod tests {
let rel_handler = CreateRelationsHandler; let rel_handler = CreateRelationsHandler;
let args_rel = json!({ let args_rel = json!({
"relations": [ "relations": [
{"from": "X", "to": "Y", "relationType": "depends_on", "namespace": "global"} {"from": "X", "to": "Y", "relation_type": "depends_on", "namespace": "global"}
] ]
}); });
rel_handler.execute(args_rel, state.clone()).await.unwrap(); rel_handler.execute(args_rel, state.clone()).await.unwrap();
@@ -784,7 +784,7 @@ mod tests {
let condense = CondenseEntityHandler; let condense = CondenseEntityHandler;
let res_cond = condense let res_cond = condense
.execute( .execute(
json!({"entityName": "X", "summarized_observations": ["X condensed"]}), json!({"entity_name": "X", "summarized_observations": ["X condensed"]}),
state.clone(), state.clone(),
) )
.await .await
@@ -794,7 +794,7 @@ mod tests {
let merge = MergeEntitiesHandler; let merge = MergeEntitiesHandler;
let res_merge = merge let res_merge = merge
.execute( .execute(
json!({"sourceEntity": "X", "targetEntity": "Y"}), json!({"source_entity": "X", "target_entity": "Y"}),
state.clone(), state.clone(),
) )
.await .await
+1 -1
View File
@@ -609,7 +609,7 @@ mod tests {
let code_handler = LogCodeChangeHandler; let code_handler = LogCodeChangeHandler;
let args_code = json!({ let args_code = json!({
"filePath": "main.rs", "file_path": "main.rs",
"description": "refactor", "description": "refactor",
"git_commit": "def", "git_commit": "def",
"git_branch": "main" "git_branch": "main"