use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::time::UNIX_EPOCH;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
struct Startup {
exe: Option<PathBuf>,
build_id: String,
}
fn startup() -> &'static Startup {
static STARTUP: OnceLock<Startup> = OnceLock::new();
STARTUP.get_or_init(|| {
let exe = std::env::current_exe().ok();
let build_id = exe
.as_deref()
.and_then(file_build_id)
.unwrap_or_else(|| "unknown".to_string());
Startup { exe, build_id }
})
}
fn file_build_id(path: &Path) -> Option<String> {
let meta = std::fs::metadata(path).ok()?;
let mtime = meta.modified().ok()?.duration_since(UNIX_EPOCH).ok()?;
Some(format!("{:x}-{:x}", meta.len(), mtime.as_nanos()))
}
pub fn build_id() -> &'static str {
&startup().build_id
}
pub fn binary_replaced() -> bool {
let startup = startup();
let Some(exe) = &startup.exe else {
return false;
};
file_build_id(exe).as_deref() != Some(startup.build_id.as_str())
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ServerBuild {
pub version: String,
pub build_id: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub stale: bool,
}
impl ServerBuild {
pub fn current() -> Self {
Self {
version: crate::mcp_version().to_string(),
build_id: build_id().to_string(),
stale: binary_replaced(),
}
}
}