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 { 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); }