#![allow(unused)]
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
pub struct McpServer {
child: Child,
stdin: ChildStdin,
out: BufReader<ChildStdout>,
}
impl McpServer {
pub fn spawn() -> Self {
Self::spawn_impl(None)
}
pub fn spawn_in(cwd: &Path) -> Self {
Self::spawn_impl(Some(cwd))
}
fn spawn_impl(cwd: Option<&Path>) -> Self {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_forjar"));
cmd.arg("mcp")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null());
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let mut child = cmd
.spawn()
.expect("failed to spawn the release binary — `forjar mcp` is advertised in --help");
let stdin = child.stdin.take().expect("stdin");
let out = BufReader::new(child.stdout.take().expect("stdout"));
Self { child, stdin, out }
}
pub fn request(&mut self, id: u64, method: &str, params: &str) -> serde_json::Value {
let msg = format!(
"{{\"jsonrpc\":\"2.0\",\"id\":{id},\"method\":\"{method}\",\"params\":{params}}}\n"
);
self.stdin.write_all(msg.as_bytes()).expect("write");
self.stdin.flush().expect("flush");
for _ in 0..64 {
let mut line = String::new();
let n = self.out.read_line(&mut line).expect("read");
assert!(
n > 0,
"server closed stdout while awaiting id={id} ({method})"
);
let Ok(v) = serde_json::from_str::<serde_json::Value>(line.trim()) else {
continue;
};
if v.get("id").and_then(|i| i.as_u64()) == Some(id) {
return v;
}
}
panic!("no reply with id={id} for {method}");
}
pub fn notify(&mut self, method: &str) {
let msg = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"{method}\"}}\n");
self.stdin.write_all(msg.as_bytes()).expect("write");
self.stdin.flush().expect("flush");
}
pub fn initialize(&mut self) -> serde_json::Value {
let r = self.request(
1,
"initialize",
r#"{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"e2e","version":"0"}}"#,
);
self.notify("notifications/initialized");
r
}
pub fn tools_list(&mut self, id: u64) -> serde_json::Value {
self.request(id, "tools/list", "{}")
}
pub fn call_tool(
&mut self,
id: u64,
name: &str,
arguments: &serde_json::Value,
) -> serde_json::Value {
let params = serde_json::json!({ "name": name, "arguments": arguments });
self.request(id, "tools/call", ¶ms.to_string())
}
}
impl Drop for McpServer {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}