#![allow(dead_code)]
use crate::common::tmp_home;
use mitosis::protocol::CloneInfo;
use rmcp::model::{CallToolRequestParams, CallToolResult};
use rmcp::service::RunningService;
use rmcp::transport::{ConfigureCommandExt, TokioChildProcess};
use rmcp::{RoleClient, ServiceExt};
use std::path::Path;
use std::process::{Command, Output};
use std::sync::{Mutex, MutexGuard};
pub fn serial_home() -> (MutexGuard<'static, ()>, tempfile::TempDir) {
static SERIAL: Mutex<()> = Mutex::new(());
let guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
(guard, tmp_home())
}
pub fn router_port() -> String {
static PORT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
PORT.get_or_init(|| {
let r = u16::from_str_radix(&mitosis::names::random_hex(2), 16).unwrap();
(60_250 + r % 1200).to_string()
})
.clone()
}
pub fn mitosis(home: &Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_mitosis"))
.args(args)
.env("MITOSIS_HOME", home)
.env("MITOSIS_ROUTER_PORT", router_port())
.output()
.expect("run mitosis")
}
pub fn stdout(o: &Output) -> String {
String::from_utf8_lossy(&o.stdout).into_owned()
}
pub fn stderr(o: &Output) -> String {
String::from_utf8_lossy(&o.stderr).into_owned()
}
pub fn ok(home: &Path, args: &[&str]) -> String {
let o = mitosis(home, args);
assert!(
o.status.success(),
"mitosis {args:?} failed: {}",
stderr(&o)
);
stdout(&o)
}
pub fn list(home: &Path) -> Vec<CloneInfo> {
serde_json::from_str(&ok(home, &["list", "--json"])).expect("list --json parses")
}
pub struct Cleanup<'a>(pub &'a Path);
impl Drop for Cleanup<'_> {
fn drop(&mut self) {
if self.0.join("daemon.sock").exists() {
let out = mitosis(self.0, &["list", "--json"]);
for c in serde_json::from_slice::<Vec<CloneInfo>>(&out.stdout).unwrap_or_default() {
let _ = mitosis(self.0, &["destroy", &c.id]);
}
}
let _ = mitosis(self.0, &["daemon", "stop"]);
}
}
pub type McpClient = RunningService<RoleClient, ()>;
pub async fn mcp_client(home: &Path) -> McpClient {
mcp_client_in(home, None).await
}
pub async fn mcp_client_in(home: &Path, cwd: Option<&Path>) -> McpClient {
let cmd = tokio::process::Command::new(env!("CARGO_BIN_EXE_mitosis")).configure(|c| {
c.arg("mcp")
.env("MITOSIS_HOME", home)
.env("MITOSIS_ROUTER_PORT", router_port());
if let Some(d) = cwd {
c.current_dir(d);
}
});
().serve(TokioChildProcess::new(cmd).expect("spawn mitosis mcp"))
.await
.expect("mcp handshake")
}
pub async fn tool_names(client: &McpClient) -> Vec<String> {
let mut names: Vec<String> = client
.list_all_tools()
.await
.expect("list tools")
.into_iter()
.map(|t| t.name.into_owned())
.collect();
names.sort();
names
}
pub async fn call(
client: &McpClient,
tool: &'static str,
args: serde_json::Value,
) -> CallToolResult {
let args = args.as_object().cloned().unwrap_or_default();
client
.call_tool(CallToolRequestParams::new(tool).with_arguments(args))
.await
.expect("call tool")
}
pub fn result_text(r: &CallToolResult) -> String {
r.content
.iter()
.filter_map(|c| c.as_text())
.map(|t| t.text.as_str())
.collect()
}