use serde_json::Value;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
fn run_mcp(requests: &[&str], cwd: &Path) -> Vec<Value> {
let mut child = Command::new(env!("CARGO_BIN_EXE_repopilot"))
.arg("mcp")
.current_dir(cwd)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("spawn `repopilot mcp`");
{
let mut stdin = child.stdin.take().expect("child stdin");
for request in requests {
stdin.write_all(request.as_bytes()).expect("write request");
stdin.write_all(b"\n").expect("write newline");
}
}
let output = child.wait_with_output().expect("wait for `repopilot mcp`");
assert!(
output.status.success(),
"server exited with {:?}",
output.status.code()
);
String::from_utf8(output.stdout)
.expect("stdout is utf-8")
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| serde_json::from_str(line).expect("response line is json"))
.collect()
}
#[test]
fn mcp_server_initializes_lists_tools_and_runs_scan_locally() {
let temp = tempfile::tempdir().expect("temp dir");
fs::create_dir_all(temp.path().join("src")).expect("src dir");
fs::write(
temp.path().join("src/lib.rs"),
"pub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n",
)
.expect("source file");
let responses = run_mcp(
&[
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#,
r#"{"jsonrpc":"2.0","id":2,"method":"tools/list"}"#,
r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"repopilot_scan","arguments":{"path":"."}}}"#,
],
temp.path(),
);
assert_eq!(responses.len(), 3, "one response per request");
assert_eq!(responses[0]["result"]["serverInfo"]["name"], "repopilot");
let names: Vec<&str> = responses[1]["result"]["tools"]
.as_array()
.expect("tools array")
.iter()
.map(|tool| tool["name"].as_str().expect("tool name"))
.collect();
assert_eq!(
names,
[
"repopilot_review_change",
"repopilot_scan",
"repopilot_context",
"repopilot_explain_file",
]
);
let result = &responses[2]["result"];
assert_eq!(result["isError"], false, "scan should succeed");
let text = result["content"][0]["text"].as_str().expect("text content");
let report: Value = serde_json::from_str(text).expect("scan report is json");
assert!(
report["schema_version"].is_string(),
"scan report carries schema metadata"
);
assert!(report["report"].is_object(), "scan report carries findings");
}
#[test]
fn mcp_server_reports_unknown_tool_as_in_band_error() {
let temp = tempfile::tempdir().expect("temp dir");
let responses = run_mcp(
&[
r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"nope","arguments":{}}}"#,
],
temp.path(),
);
let result = &responses[0]["result"];
assert_eq!(result["isError"], true);
assert!(
result["content"][0]["text"]
.as_str()
.expect("text")
.contains("unknown tool")
);
}