#[path = "common/mod.rs"]
mod common;
#[test]
#[ignore = "requires just binary and built task-mcp binary"]
fn e2e_stdio_list_returns_global_recipe() {
use std::io::{BufRead as _, Write as _};
use std::process::{Command, Stdio};
let project =
common::TempProject::with_global_recipes("# [allow-agent]\nhello:\n echo from-global\n");
let global_justfile = project.global_justfile().expect("global justfile exists");
let project_dir = project.project_path().to_path_buf();
let bin = env!("CARGO_BIN_EXE_task-mcp");
let mut child = Command::new(bin)
.arg("--mcp")
.env("TASK_MCP_LOAD_GLOBAL", "true")
.env("TASK_MCP_GLOBAL_JUSTFILE", &global_justfile)
.env("TASK_MCP_MODE", "all")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("failed to spawn task-mcp binary");
let stdin = child.stdin.as_mut().expect("stdin should be available");
let stdout = child.stdout.take().expect("stdout should be available");
let mut reader = std::io::BufReader::new(stdout);
let init_msg = serde_json::to_string(&serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "e2e-test", "version": "0.0.1" }
}
}))
.expect("serialize initialize");
writeln!(stdin, "{init_msg}").expect("write initialize");
let mut init_resp_line = String::new();
reader
.read_line(&mut init_resp_line)
.expect("read initialize response");
let init_resp: serde_json::Value =
serde_json::from_str(init_resp_line.trim()).expect("parse initialize response");
assert_eq!(init_resp["id"], 1, "initialize id mismatch");
assert!(
init_resp.get("error").is_none(),
"initialize must not return error: {init_resp}"
);
let notif_msg = serde_json::to_string(&serde_json::json!({
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
}))
.expect("serialize notifications/initialized");
writeln!(stdin, "{notif_msg}").expect("write notifications/initialized");
let session_msg = serde_json::to_string(&serde_json::json!({
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "session_start",
"arguments": { "workdir": project_dir.to_string_lossy() }
}
}))
.expect("serialize session_start");
writeln!(stdin, "{session_msg}").expect("write session_start");
let mut session_resp_line = String::new();
reader
.read_line(&mut session_resp_line)
.expect("read session_start response");
let session_resp: serde_json::Value =
serde_json::from_str(session_resp_line.trim()).expect("parse session_start response");
assert_eq!(session_resp["id"], 2, "session_start id mismatch");
assert!(
session_resp.get("error").is_none(),
"session_start must not return error: {session_resp}"
);
let list_msg = serde_json::to_string(&serde_json::json!({
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "list",
"arguments": {}
}
}))
.expect("serialize list");
writeln!(stdin, "{list_msg}").expect("write list");
let mut list_resp_line = String::new();
reader
.read_line(&mut list_resp_line)
.expect("read list response");
let _ = child.kill();
let _ = child.wait();
let list_resp: serde_json::Value =
serde_json::from_str(list_resp_line.trim()).expect("parse list response");
assert_eq!(list_resp["id"], 3, "list id mismatch");
assert!(
list_resp.get("error").is_none(),
"list must not return error: {list_resp}"
);
let content_text = list_resp["result"]["content"][0]["text"]
.as_str()
.expect("content[0].text should be present");
let list_payload: serde_json::Value =
serde_json::from_str(content_text).expect("parse list payload JSON");
let recipes = list_payload["recipes"]
.as_array()
.expect("list payload should contain a `recipes` array");
let names: Vec<&str> = recipes.iter().filter_map(|r| r["name"].as_str()).collect();
assert!(
names.contains(&"hello"),
"global recipe 'hello' should appear in list result: {names:?}"
);
}