use anyhow::{Context, Result, bail};
use sha2::{Digest, Sha256};
use std::path::{Path, PathBuf};
#[cfg(windows)]
const MCP_SERVER_BIN: &str = "mur-mcp-server.exe";
#[cfg(not(windows))]
const MCP_SERVER_BIN: &str = "mur-mcp-server";
pub fn bundled_mcp_server_path() -> PathBuf {
crate::trust::mur_home()
.join("mcp-servers")
.join(MCP_SERVER_BIN)
}
pub fn ensure_bundled_mcp_server() -> Result<PathBuf> {
let target = bundled_mcp_server_path();
match locate_mcp_server_source() {
Some(src) => {
install_if_stale(&src, &target)?;
Ok(target)
}
None if target.is_file() => Ok(target),
None => bail!(
"mur-mcp-server not found next to `mur` or on PATH, and no copy at {}",
target.display()
),
}
}
fn locate_mcp_server_source() -> Option<PathBuf> {
if let Ok(exe) = std::env::current_exe()
&& let Some(dir) = exe.parent()
{
let sibling = dir.join(MCP_SERVER_BIN);
if sibling.is_file() {
return sibling.canonicalize().ok();
}
}
resolve_command(MCP_SERVER_BIN).ok()
}
fn install_if_stale(src: &Path, target: &Path) -> Result<()> {
if target.is_file() && sha256_file(src)? == sha256_file(target)? {
return Ok(());
}
let dir = target
.parent()
.ok_or_else(|| anyhow::anyhow!("target {} has no parent", target.display()))?;
std::fs::create_dir_all(dir).with_context(|| format!("create {}", dir.display()))?;
let tmp = dir.join(format!(".{MCP_SERVER_BIN}.{}.tmp", std::process::id()));
std::fs::copy(src, &tmp)
.with_context(|| format!("copy {} -> {}", src.display(), tmp.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o755))
.with_context(|| format!("chmod {}", tmp.display()))?;
}
std::fs::rename(&tmp, target)
.with_context(|| format!("rename {} -> {}", tmp.display(), target.display()))?;
Ok(())
}
fn sha256_file(path: &Path) -> Result<String> {
use std::io::Read;
let mut f = std::fs::File::open(path).with_context(|| format!("open {}", path.display()))?;
let mut hasher = Sha256::new();
let mut buf = [0u8; 65536];
loop {
let n = f
.read(&mut buf)
.with_context(|| format!("read {}", path.display()))?;
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
Ok(hex::encode(hasher.finalize()))
}
pub fn resolve_command(command: &str) -> Result<PathBuf> {
let p = Path::new(command);
if p.is_absolute() || command.contains('/') || command.contains('\\') {
return p
.canonicalize()
.with_context(|| format!("canonicalize {command}"));
}
let path_var = std::env::var_os("PATH")
.ok_or_else(|| anyhow::anyhow!("PATH env var unset; cannot resolve `{command}`"))?;
for dir in std::env::split_paths(&path_var) {
let candidate = dir.join(command);
if candidate.is_file() {
return candidate
.canonicalize()
.with_context(|| format!("canonicalize {}", candidate.display()));
}
#[cfg(target_os = "windows")]
{
let with_exe = dir.join(format!("{command}.exe"));
if with_exe.is_file() {
return with_exe
.canonicalize()
.with_context(|| format!("canonicalize {}", with_exe.display()));
}
}
}
bail!("could not find `{command}` on PATH");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn errors_on_missing_binary() {
assert!(resolve_command("definitely-not-a-real-binary-xyz123").is_err());
}
#[cfg(unix)]
#[test]
fn resolves_bare_program_on_path_to_absolute() {
let resolved = resolve_command("sh").expect("sh is on PATH");
assert!(
resolved.is_absolute(),
"expected absolute, got {resolved:?}"
);
assert!(resolved.exists());
}
#[test]
fn absolute_path_is_canonicalized() {
let tmp = tempfile::NamedTempFile::new().unwrap();
let resolved = resolve_command(tmp.path().to_str().unwrap()).unwrap();
assert!(resolved.is_absolute());
}
#[test]
fn install_if_stale_copies_then_is_idempotent_and_updates() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src-bin");
let target = dir.path().join("mcp-servers/mur-mcp-server"); std::fs::write(&src, b"v1").unwrap();
install_if_stale(&src, &target).unwrap();
assert_eq!(std::fs::read(&target).unwrap(), b"v1");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(&target).unwrap().permissions().mode();
assert_eq!(mode & 0o111, 0o111, "target must be executable");
}
install_if_stale(&src, &target).unwrap();
assert_eq!(std::fs::read(&target).unwrap(), b"v1");
std::fs::write(&src, b"v2-newer").unwrap();
install_if_stale(&src, &target).unwrap();
assert_eq!(std::fs::read(&target).unwrap(), b"v2-newer");
}
}