mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
fix(mcp): stop stdio cleanly after output failure
Wake the serving loop after protocol output fails and preserve the actual writer error. Use one process-owned stdin worker so the CLI can finish normal backend cleanup without waiting for client EOF, and terminate the output worker even when a progress hook is retained. Closes #1527 Co-Authored-By: OpenAI Codex <noreply@openai.com>
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
|
||||
mod alloc_release;
|
||||
|
||||
use std::io::{BufReader, Write};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
@@ -364,8 +364,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
|
||||
};
|
||||
eprintln!("lightcraft-cli mcp: serving MCP on stdio ({})", backend.describe());
|
||||
let mut server = Server::new(backend).with_command_tools(!compact);
|
||||
let stdin = std::io::stdin();
|
||||
server.serve(BufReader::new(stdin), std::io::stdout()).map_err(|e| e.to_string())
|
||||
server.serve_stdio(std::io::stdout()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn merge(args: &[String]) -> Result<(), String> {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//! A broken protocol output must not require the client to close its input pipe.
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn broken_stdout_exits_with_stdin_still_open() {
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_lightcraft-cli"))
|
||||
.arg("mcp")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let mut input = child.stdin.take().unwrap();
|
||||
drop(child.stdout.take());
|
||||
input.write_all(b"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"}\n{\"jsonrpc\":").unwrap();
|
||||
input.flush().unwrap();
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
let status = loop {
|
||||
if let Some(status) = child.try_wait().unwrap() {
|
||||
break status;
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
child.kill().unwrap();
|
||||
child.wait().unwrap();
|
||||
panic!("MCP did not exit after stdout failed while stdin stayed open");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
};
|
||||
let mut stderr = String::new();
|
||||
child.stderr.take().unwrap().read_to_string(&mut stderr).unwrap();
|
||||
assert!(stderr.contains("serving MCP on stdio"), "CLI must reach the MCP transport: {stderr}");
|
||||
assert!(!status.success(), "broken protocol output must be reported: {stderr}");
|
||||
assert!(stderr.contains("lightcraft-cli:"), "CLI must report the transport error: {stderr}");
|
||||
drop(input);
|
||||
}
|
||||
Reference in New Issue
Block a user