207 lines
6.5 KiB
Rust
207 lines
6.5 KiB
Rust
use serde_json::{Value, json};
|
|
use std::io::{BufRead, BufReader, Write, Read};
|
|
use std::process::{Command, Stdio};
|
|
|
|
fn send_message(stdin: &mut std::process::ChildStdin, msg: Value) {
|
|
let s = serde_json::to_string(&msg).unwrap();
|
|
stdin.write_all(format!("{s}\n").as_bytes()).unwrap();
|
|
stdin.flush().unwrap();
|
|
}
|
|
|
|
fn read_message(stdout: &mut std::process::ChildStdout) -> Option<Value> {
|
|
let mut reader = BufReader::new(stdout);
|
|
let mut line = String::new();
|
|
if reader.read_line(&mut line).unwrap_or(0) == 0 {
|
|
return None;
|
|
}
|
|
serde_json::from_str(&line).ok()
|
|
}
|
|
|
|
#[test]
|
|
fn test_mcp_initialization_and_tools_list() {
|
|
let mut nvim_exe = std::env::current_exe().unwrap();
|
|
nvim_exe.pop();
|
|
nvim_exe.pop();
|
|
nvim_exe.push("mcp-memory-win-nvim.exe");
|
|
|
|
let temp_dir = std::env::temp_dir().join("win-nvim-test");
|
|
std::fs::create_dir_all(&temp_dir).unwrap();
|
|
let temp_gemini = temp_dir.join(".gemini");
|
|
std::fs::create_dir_all(&temp_gemini).unwrap();
|
|
|
|
let mut child = Command::new(&nvim_exe)
|
|
.env("USERPROFILE", temp_dir.to_str().unwrap())
|
|
.env("HOME", temp_dir.to_str().unwrap())
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.expect("Failed to start mcp-memory-win-nvim");
|
|
|
|
let mut stdin = child.stdin.take().expect("Failed to open stdin");
|
|
let mut stdout = child.stdout.take().expect("Failed to open stdout");
|
|
|
|
// 0. Test server/discover (probe)
|
|
let discover_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "server/discover",
|
|
"params": {},
|
|
"id": 0
|
|
});
|
|
send_message(&mut stdin, discover_req);
|
|
let discover_resp = read_message(&mut stdout).expect("Failed to read server/discover response");
|
|
assert_eq!(discover_resp["error"]["code"], -32601);
|
|
|
|
// 1. Test Initialize
|
|
let init_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "initialize",
|
|
"params": {
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {},
|
|
"clientInfo": {
|
|
"name": "test-client",
|
|
"version": "1.0"
|
|
}
|
|
},
|
|
"id": 1
|
|
});
|
|
|
|
send_message(&mut stdin, init_req);
|
|
|
|
let init_resp = read_message(&mut stdout).expect("Failed to read initialize response");
|
|
|
|
assert_eq!(init_resp["jsonrpc"], "2.0");
|
|
assert_eq!(init_resp["id"], 1);
|
|
|
|
// Verify capabilities
|
|
let capabilities = &init_resp["result"]["capabilities"];
|
|
assert_eq!(capabilities["tools"], serde_json::json!({}));
|
|
|
|
// 2. Test tools/list
|
|
let tools_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/list",
|
|
"params": {},
|
|
"id": 2
|
|
});
|
|
|
|
send_message(&mut stdin, tools_req);
|
|
|
|
let tools_resp = read_message(&mut stdout).expect("Failed to read tools/list response");
|
|
|
|
assert_eq!(tools_resp["jsonrpc"], "2.0");
|
|
assert_eq!(tools_resp["id"], 2);
|
|
|
|
let tools = tools_resp["result"]["tools"]
|
|
.as_array()
|
|
.expect("result.tools must be an array");
|
|
assert!(!tools.is_empty(), "Server must expose at least one tool");
|
|
|
|
let has_get_active_buffer = tools.iter().any(|t| t["name"] == "nvim_get_active_buffer");
|
|
assert!(has_get_active_buffer, "Missing nvim_get_active_buffer tool");
|
|
|
|
let tool_names = vec![
|
|
"nvim_goto_line",
|
|
"nvim_get_active_buffer",
|
|
"nvim_get_cursor",
|
|
"nvim_get_visual_selection",
|
|
"nvim_set_diagnostics",
|
|
"nvim_set_extmark",
|
|
"nvim_list_buffers",
|
|
"nvim_list_windows",
|
|
"nvim_get_active_window",
|
|
"nvim_set_active_window",
|
|
"nvim_get_diagnostics",
|
|
"nvim_open_file",
|
|
"nvim_open_buffer",
|
|
"nvim_close_buffer",
|
|
"nvim_close_window",
|
|
"nvim_split_window",
|
|
"nvim_reload_buffer",
|
|
"nvim_save_buffer",
|
|
"nvim_set_quickfix",
|
|
"nvim_highlight_lines",
|
|
"nvim_get_messages",
|
|
"nvim_get_viewport",
|
|
"nvim_read_file",
|
|
"nvim_search_file",
|
|
"nvim_execute_lua",
|
|
"nvim_get_server_info",
|
|
"unknown_tool",
|
|
];
|
|
|
|
for (i, tool_name) in tool_names.iter().enumerate() {
|
|
let req_id = i + 100;
|
|
let call_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/call",
|
|
"params": {
|
|
"name": tool_name,
|
|
"arguments": {
|
|
"file": "test.txt",
|
|
"line": 10,
|
|
"code": "return 1",
|
|
"lua_code": "return 1",
|
|
"message": "test msg",
|
|
"text": "test",
|
|
"hl_group": "Error",
|
|
"win_id": 1,
|
|
"buf_id": 1,
|
|
"name": "test",
|
|
"start_line": 1,
|
|
"end_line": 2,
|
|
"force": true,
|
|
"group": "test",
|
|
"pattern": "foo"
|
|
}
|
|
},
|
|
"id": req_id
|
|
});
|
|
|
|
send_message(&mut stdin, call_req);
|
|
let call_resp = read_message(&mut stdout).expect("Failed to read tools/call response");
|
|
println!("Response for {}: {}", tool_name, serde_json::to_string(&call_resp).unwrap());
|
|
assert_eq!(call_resp["jsonrpc"], "2.0");
|
|
assert_eq!(call_resp["id"], req_id);
|
|
}
|
|
|
|
for (i, tool_name) in tool_names.iter().enumerate() {
|
|
let req_id = i + 200;
|
|
let call_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/call",
|
|
"params": {
|
|
"name": tool_name,
|
|
"arguments": {}
|
|
},
|
|
"id": req_id
|
|
});
|
|
|
|
send_message(&mut stdin, call_req);
|
|
let call_resp = read_message(&mut stdout).expect("Failed to read tools/call response");
|
|
assert_eq!(call_resp["jsonrpc"], "2.0");
|
|
assert_eq!(call_resp["id"], req_id);
|
|
}
|
|
|
|
let call_req_no_id = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/call",
|
|
"params": {
|
|
"name": "nvim_goto_line",
|
|
"arguments": {}
|
|
}
|
|
});
|
|
send_message(&mut stdin, call_req_no_id);
|
|
|
|
drop(stdin);
|
|
let mut stderr_output = String::new();
|
|
if let Some(mut stderr) = child.stderr.take() {
|
|
let _ = stderr.read_to_string(&mut stderr_output);
|
|
println!("Child STDERR: {}", stderr_output);
|
|
}
|
|
|
|
let status = child.wait().expect("Failed to wait on child");
|
|
assert!(status.success(), "Child process did not exit successfully. Stderr: {}", stderr_output);
|
|
}
|