feat: publish HoloLake model-native living system source

This commit is contained in:
冰朔 2026-08-03 10:04:41 +08:00
commit c395dd3a99
2467 changed files with 615073 additions and 0 deletions

View file

@ -0,0 +1,303 @@
use std::fs;
#[cfg(all(desktop, target_os = "linux"))]
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
#[cfg(all(desktop, target_os = "linux"))]
use std::time::{Duration, Instant, SystemTime};
const VERSION_MARKER_FILE: &str = ".tolaria-version";
#[cfg(all(desktop, target_os = "linux"))]
const LOCK_FILE: &str = "mcp-server.lock";
const STAGING_DIR: &str = "mcp-server.staging";
const BACKUP_DIR: &str = "mcp-server.previous";
#[cfg(all(desktop, target_os = "linux"))]
const LOCK_TIMEOUT: Duration = Duration::from_secs(5);
#[cfg(all(desktop, target_os = "linux"))]
const STALE_LOCK_AFTER: Duration = Duration::from_secs(120);
#[cfg(all(desktop, target_os = "linux"))]
pub(super) fn ready_stable_mcp_server_dir() -> Option<PathBuf> {
let stable_dir = stable_mcp_server_dir().ok()?;
stable_mcp_server_dir_is_ready(&stable_dir).then_some(stable_dir)
}
#[cfg(all(desktop, target_os = "linux"))]
pub(crate) fn extract_mcp_server_to_stable_dir(app_version: &str) -> Result<PathBuf, String> {
let source_dir = super::mcp_server_dir()?;
let target_dir = stable_mcp_server_dir()?;
if !needs_extraction(app_version, &target_dir) {
return Ok(target_dir);
}
let _lock = ExtractionLock::acquire(&extraction_lock_path()?)?;
if !needs_extraction(app_version, &target_dir) {
return Ok(target_dir);
}
replace_stable_server_dir(&source_dir, &target_dir, app_version)?;
Ok(target_dir)
}
fn stable_mcp_server_dir() -> Result<PathBuf, String> {
dirs::data_dir()
.map(|data_dir| data_dir.join("tolaria").join("mcp-server"))
.ok_or_else(|| "Unable to resolve data directory for stable MCP server path".to_string())
}
fn stable_mcp_server_dir_is_ready(dir: &Path) -> bool {
mcp_server_dir_has_files(dir) && read_version_marker(dir).is_some()
}
fn mcp_server_dir_has_files(dir: &Path) -> bool {
dir.join("index.js").is_file() && dir.join("ws-bridge.js").is_file()
}
fn needs_extraction(app_version: &str, target_dir: &Path) -> bool {
!mcp_server_dir_has_files(target_dir)
|| read_version_marker(target_dir).as_deref() != Some(app_version)
}
fn read_version_marker(dir: &Path) -> Option<String> {
fs::read_to_string(dir.join(VERSION_MARKER_FILE))
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
fn write_version_marker(dir: &Path, app_version: &str) -> Result<(), String> {
let marker = dir.join(VERSION_MARKER_FILE);
fs::write(&marker, app_version)
.map_err(|e| format!("Failed to write version marker {}: {e}", marker.display()))
}
#[cfg(all(desktop, target_os = "linux"))]
fn extraction_lock_path() -> Result<PathBuf, String> {
let stable_dir = stable_mcp_server_dir()?;
stable_dir
.parent()
.map(|parent| parent.join(LOCK_FILE))
.ok_or_else(|| {
format!(
"Stable MCP server path has no parent: {}",
stable_dir.display()
)
})
}
fn replace_stable_server_dir(
source_dir: &Path,
target_dir: &Path,
app_version: &str,
) -> Result<(), String> {
let parent = target_dir.parent().ok_or_else(|| {
format!(
"Stable MCP server path has no parent: {}",
target_dir.display()
)
})?;
let staging_dir = parent.join(STAGING_DIR);
let backup_dir = parent.join(BACKUP_DIR);
remove_dir_if_exists(&staging_dir)?;
remove_dir_if_exists(&backup_dir)?;
copy_dir_all(source_dir, &staging_dir)?;
write_version_marker(&staging_dir, app_version)?;
swap_staging_into_place(&staging_dir, target_dir, &backup_dir)?;
Ok(())
}
fn swap_staging_into_place(
staging_dir: &Path,
target_dir: &Path,
backup_dir: &Path,
) -> Result<(), String> {
if target_dir.exists() {
fs::rename(target_dir, backup_dir)
.map_err(|e| format!("Failed to move stable MCP server aside: {e}"))?;
}
if let Err(error) = fs::rename(staging_dir, target_dir) {
if backup_dir.exists() {
let _ = fs::rename(backup_dir, target_dir);
}
return Err(format!("Failed to activate stable MCP server: {error}"));
}
remove_dir_if_exists(backup_dir)
}
fn copy_dir_all(source: &Path, target: &Path) -> Result<(), String> {
fs::create_dir_all(target)
.map_err(|e| format!("Failed to create {}: {e}", target.display()))?;
for entry in fs::read_dir(source).map_err(|e| {
format!(
"Failed to read MCP server directory {}: {e}",
source.display()
)
})? {
let entry = entry.map_err(|e| format!("Failed to read MCP server entry: {e}"))?;
let source_path = entry.path();
let target_path = target.join(entry.file_name());
if source_path.is_dir() {
copy_dir_all(&source_path, &target_path)?;
} else {
fs::copy(&source_path, &target_path).map_err(|e| {
format!(
"Failed to copy {} to {}: {e}",
source_path.display(),
target_path.display()
)
})?;
}
}
Ok(())
}
fn remove_dir_if_exists(path: &Path) -> Result<(), String> {
if path.exists() {
fs::remove_dir_all(path)
.map_err(|e| format!("Failed to remove {}: {e}", path.display()))?;
}
Ok(())
}
#[cfg(all(desktop, target_os = "linux"))]
struct ExtractionLock {
path: PathBuf,
}
#[cfg(all(desktop, target_os = "linux"))]
impl ExtractionLock {
fn acquire(path: &Path) -> Result<Self, String> {
let started = Instant::now();
loop {
match Self::try_create(path) {
Ok(()) => {
return Ok(Self {
path: path.to_path_buf(),
});
}
Err(error) if lock_is_stale(path) => {
let _ = fs::remove_file(path);
log::warn!("Removed stale MCP extraction lock after error: {error}");
}
Err(error) if started.elapsed() >= LOCK_TIMEOUT => return Err(error),
Err(_) => std::thread::sleep(Duration::from_millis(50)),
}
}
}
fn try_create(path: &Path) -> Result<(), String> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create MCP extraction lock dir: {e}"))?;
}
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.map_err(|e| format!("Failed to acquire MCP extraction lock: {e}"))?;
use std::io::Write;
writeln!(file, "{}", std::process::id())
.map_err(|e| format!("Failed to write MCP extraction lock: {e}"))
}
}
#[cfg(all(desktop, target_os = "linux"))]
impl Drop for ExtractionLock {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}
#[cfg(all(desktop, target_os = "linux"))]
fn lock_is_stale(path: &Path) -> bool {
path.metadata()
.and_then(|metadata| metadata.modified())
.ok()
.and_then(|modified| SystemTime::now().duration_since(modified).ok())
.is_some_and(|age| age >= STALE_LOCK_AFTER)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_server_dir(parent: &Path) -> PathBuf {
let dir = parent.join("server");
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("index.js"), "console.log('index');").unwrap();
fs::write(dir.join("ws-bridge.js"), "console.log('bridge');").unwrap();
dir
}
#[test]
fn stable_mcp_server_dir_uses_app_data_dir() {
let expected = dirs::data_dir()
.expect("data dir should exist")
.join("tolaria")
.join("mcp-server");
assert_eq!(stable_mcp_server_dir().unwrap(), expected);
}
#[test]
fn stable_mcp_server_dir_requires_marker_and_files() {
let tmp = tempfile::tempdir().unwrap();
let stable_dir = tmp.path().join("tolaria").join("mcp-server");
fs::create_dir_all(&stable_dir).unwrap();
fs::write(stable_dir.join("index.js"), "").unwrap();
fs::write(stable_dir.join("ws-bridge.js"), "").unwrap();
assert!(!stable_mcp_server_dir_is_ready(&stable_dir));
write_version_marker(&stable_dir, "2026.5.14").unwrap();
assert!(stable_mcp_server_dir_is_ready(&stable_dir));
}
#[test]
fn needs_extraction_tracks_version_marker() {
let target = tempfile::tempdir().unwrap();
fs::write(target.path().join("index.js"), "").unwrap();
fs::write(target.path().join("ws-bridge.js"), "").unwrap();
assert!(needs_extraction("2026.5.14", target.path()));
write_version_marker(target.path(), "2026.5.14").unwrap();
assert!(!needs_extraction("2026.5.14", target.path()));
assert!(needs_extraction("2026.5.15", target.path()));
}
#[test]
fn copy_dir_all_copies_nested_server_files() {
let tmp = tempfile::tempdir().unwrap();
let source = create_server_dir(tmp.path());
fs::create_dir_all(source.join("nested")).unwrap();
fs::write(source.join("nested").join("package.json"), "{}").unwrap();
let target = tmp.path().join("target");
copy_dir_all(&source, &target).unwrap();
assert!(target.join("index.js").is_file());
assert!(target.join("ws-bridge.js").is_file());
assert!(target.join("nested").join("package.json").is_file());
}
#[test]
fn replace_stable_server_dir_swaps_versioned_copy() {
let tmp = tempfile::tempdir().unwrap();
let source = create_server_dir(tmp.path());
let target = tmp.path().join("tolaria").join("mcp-server");
fs::create_dir_all(&target).unwrap();
fs::write(target.join("stale.txt"), "old").unwrap();
replace_stable_server_dir(&source, &target, "2026.5.14").unwrap();
assert!(target.join("index.js").is_file());
assert!(!target.join("stale.txt").exists());
assert_eq!(read_version_marker(&target), Some("2026.5.14".to_string()));
}
}

View file

@ -0,0 +1,278 @@
use std::path::{Path, PathBuf};
use serde_json::{Map, Value};
use super::{LEGACY_MCP_SERVER_NAME, MCP_SERVER_NAME};
const OPENCODE_MCP_KEY: &str = "mcp";
pub(super) fn config_path() -> Option<PathBuf> {
dirs::config_dir().map(|config_dir| config_dir.join("opencode").join("opencode.json"))
}
pub(super) fn build_entry(node_command: &str, index_js: &str) -> Value {
serde_json::json!({
"type": "local",
"command": [node_command, index_js],
"enabled": true,
"environment": {
"WS_UI_PORT": "9711"
}
})
}
pub(super) fn build_config_snippet(entry: &Value) -> Result<String, String> {
let mut servers = Map::new();
servers.insert(MCP_SERVER_NAME.to_string(), entry.clone());
let config = serde_json::json!({
"$schema": "https://opencode.ai/config.json",
OPENCODE_MCP_KEY: servers
});
serde_json::to_string_pretty(&config)
.map_err(|e| format!("Failed to serialize OpenCode MCP config snippet: {e}"))
}
pub(super) fn upsert_config(config_path: &Path, entry: &Value) -> Result<bool, String> {
let mut config = read_config_or_empty(config_path)?;
let servers = ensure_servers_object(&mut config)?;
let was_update =
servers.get(MCP_SERVER_NAME).is_some() || servers.get(LEGACY_MCP_SERVER_NAME).is_some();
servers.remove(LEGACY_MCP_SERVER_NAME);
servers.insert(MCP_SERVER_NAME.to_string(), entry.clone());
write_config(config_path, &config)?;
Ok(was_update)
}
pub(super) fn remove_config(config_path: &Path) -> Result<bool, String> {
if !config_path.exists() {
return Ok(false);
}
let mut config = read_config_or_empty(config_path)?;
let Some(config_object) = config.as_object_mut() else {
return Err("Config is not a JSON object".into());
};
let Some(servers_value) = config_object.get_mut(OPENCODE_MCP_KEY) else {
return Ok(false);
};
let Some(servers) = servers_value.as_object_mut() else {
return Err("mcp is not a JSON object".into());
};
let removed_primary = servers.remove(MCP_SERVER_NAME).is_some();
let removed_legacy = servers.remove(LEGACY_MCP_SERVER_NAME).is_some();
if !removed_primary && !removed_legacy {
return Ok(false);
}
if servers.is_empty() {
config_object.remove(OPENCODE_MCP_KEY);
}
write_config(config_path, &config)?;
Ok(true)
}
pub(super) fn read_registered_entry(config_path: &Path) -> Option<Value> {
let raw = std::fs::read_to_string(config_path).ok()?;
let config: Value = serde_json::from_str(&raw).ok()?;
config
.get(OPENCODE_MCP_KEY)
.and_then(Value::as_object)
.and_then(|servers| {
servers
.get(MCP_SERVER_NAME)
.or_else(|| servers.get(LEGACY_MCP_SERVER_NAME))
})
.cloned()
}
pub(super) fn entry_is_installed(entry: &Value) -> bool {
entry["type"].as_str() == Some("local")
&& entry["enabled"].as_bool() == Some(true)
&& entry["environment"]["WS_UI_PORT"].as_str() == Some("9711")
&& command_index_js_exists(entry)
}
fn command_index_js_exists(entry: &Value) -> bool {
entry["command"]
.as_array()
.and_then(|command| command.get(1))
.and_then(Value::as_str)
.is_some_and(|index_js| Path::new(index_js).exists())
}
fn read_config_or_empty(config_path: &Path) -> Result<Value, String> {
if !config_path.exists() {
return Ok(serde_json::json!({}));
}
let raw = std::fs::read_to_string(config_path)
.map_err(|e| format!("Cannot read {}: {e}", config_path.display()))?;
serde_json::from_str(&raw)
.map_err(|e| format!("Invalid JSON in {}: {e}", config_path.display()))
}
fn ensure_servers_object(config: &mut Value) -> Result<&mut Map<String, Value>, String> {
let servers = config
.as_object_mut()
.ok_or("Config is not a JSON object")?
.entry(OPENCODE_MCP_KEY)
.or_insert_with(|| serde_json::json!({}));
servers
.as_object_mut()
.ok_or_else(|| "mcp is not a JSON object".to_string())
}
fn write_config(config_path: &Path, config: &Value) -> Result<(), String> {
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("Cannot create dir {}: {e}", parent.display()))?;
}
let json = serde_json::to_string_pretty(config)
.map_err(|e| format!("Failed to serialize config: {e}"))?;
std::fs::write(config_path, json)
.map_err(|e| format!("Cannot write {}: {e}", config_path.display()))
}
#[cfg(test)]
mod tests {
use super::*;
fn read_config(config_path: &Path) -> Value {
let raw = std::fs::read_to_string(config_path).unwrap();
serde_json::from_str(&raw).unwrap()
}
fn write_config_json(config_path: &Path, config: Value) {
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(config_path, serde_json::to_string(&config).unwrap()).unwrap();
}
#[test]
fn build_entry_uses_opencode_schema_without_vault_path() {
let entry = build_entry("node", "/app/mcp-server/index.js");
assert_eq!(
entry,
serde_json::json!({
"type": "local",
"command": ["node", "/app/mcp-server/index.js"],
"enabled": true,
"environment": {
"WS_UI_PORT": "9711"
}
})
);
assert!(entry["environment"]["VAULT_PATH"].is_null());
}
#[test]
fn build_config_snippet_wraps_tolaria_entry_in_opencode_schema() {
let snippet =
build_config_snippet(&build_entry("node", "/app/mcp-server/index.js")).unwrap();
let config: Value = serde_json::from_str(&snippet).unwrap();
assert_eq!(
config,
serde_json::json!({
"$schema": "https://opencode.ai/config.json",
"mcp": {
"tolaria": {
"type": "local",
"command": ["node", "/app/mcp-server/index.js"],
"enabled": true,
"environment": {
"WS_UI_PORT": "9711"
}
}
}
})
);
assert!(config["mcpServers"].is_null());
}
#[test]
fn upsert_config_preserves_other_opencode_settings() {
let tmp = tempfile::tempdir().unwrap();
let config_path = tmp.path().join("opencode.json");
write_config_json(
&config_path,
serde_json::json!({
"$schema": "https://opencode.ai/config.json",
"mcp": {
"other": { "type": "local" }
}
}),
);
let was_update = upsert_config(&config_path, &build_entry("node", "/index.js")).unwrap();
let config = read_config(&config_path);
assert!(!was_update);
assert_eq!(config["$schema"], "https://opencode.ai/config.json");
assert!(config["mcp"]["other"].is_object());
assert_eq!(config["mcp"][MCP_SERVER_NAME]["command"][1], "/index.js");
}
#[test]
fn upsert_config_migrates_legacy_server_name() {
let tmp = tempfile::tempdir().unwrap();
let config_path = tmp.path().join("opencode.json");
write_config_json(
&config_path,
serde_json::json!({
"mcp": {
"laputa": { "type": "local", "command": ["node", "/old.js"] }
}
}),
);
let was_update = upsert_config(&config_path, &build_entry("node", "/new.js")).unwrap();
let config = read_config(&config_path);
assert!(was_update);
assert!(config["mcp"][LEGACY_MCP_SERVER_NAME].is_null());
assert_eq!(config["mcp"][MCP_SERVER_NAME]["command"][1], "/new.js");
}
#[test]
fn remove_config_removes_primary_and_legacy_entries() {
let tmp = tempfile::tempdir().unwrap();
let config_path = tmp.path().join("opencode.json");
write_config_json(
&config_path,
serde_json::json!({
"mcp": {
"tolaria": { "type": "local" },
"laputa": { "type": "local" },
"other": { "type": "local" }
}
}),
);
assert!(remove_config(&config_path).unwrap());
let config = read_config(&config_path);
assert!(config["mcp"][MCP_SERVER_NAME].is_null());
assert!(config["mcp"][LEGACY_MCP_SERVER_NAME].is_null());
assert!(config["mcp"]["other"].is_object());
}
#[test]
fn entry_is_installed_checks_opencode_shape_and_index_path() {
let tmp = tempfile::tempdir().unwrap();
let index_js = tmp.path().join("index.js");
std::fs::write(&index_js, "").unwrap();
let entry = build_entry("node", &index_js.to_string_lossy());
assert!(entry_is_installed(&entry));
let missing = build_entry("node", &tmp.path().join("missing.js").to_string_lossy());
assert!(!entry_is_installed(&missing));
}
}

View file

@ -0,0 +1,169 @@
use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
pub(super) fn runtime_resource_roots() -> Vec<PathBuf> {
let local_app_data = if cfg!(windows) {
non_empty_env_path("LOCALAPPDATA")
} else {
None
};
let current_exe = std::env::current_exe().ok();
runtime_resource_roots_for_env_and_exe(
non_empty_env_path("RESOURCEPATH"),
non_empty_env_path("APPDIR"),
local_app_data,
current_exe.as_deref(),
)
}
fn runtime_resource_roots_for_env_and_exe(
resource_path: Option<PathBuf>,
appdir: Option<PathBuf>,
local_app_data: Option<PathBuf>,
current_exe: Option<&Path>,
) -> Vec<PathBuf> {
let mut roots = Vec::new();
if let Some(resource_path) = resource_path {
push_resource_root(&mut roots, resource_path);
}
if let Some(current_exe) = current_exe {
push_current_exe_resource_roots(&mut roots, current_exe);
}
if let Some(appdir) = appdir {
push_resource_root(&mut roots, appdir.join("usr"));
push_resource_root(&mut roots, appdir.join("usr/lib/tolaria"));
push_resource_root(&mut roots, appdir.join("usr/lib/Tolaria"));
}
if let Some(local_app_data) = local_app_data {
push_resource_root(&mut roots, local_app_data.join("Tolaria"));
push_resource_root(&mut roots, local_app_data.join("tolaria"));
}
roots
}
fn push_current_exe_resource_roots(roots: &mut Vec<PathBuf>, current_exe: &Path) {
let Some(exe_dir) = current_exe.parent() else {
return;
};
push_resource_root(roots, exe_dir.to_path_buf());
push_resource_root(roots, exe_dir.join("resources"));
if let Some(resource_dir) = macos_app_resources_dir(current_exe) {
push_resource_root(roots, resource_dir);
}
}
fn macos_app_resources_dir(executable: &Path) -> Option<PathBuf> {
let macos_dir = executable.parent()?;
if macos_dir.file_name() != Some(OsStr::new("MacOS")) {
return None;
}
let contents_dir = macos_dir.parent()?;
if contents_dir.file_name() != Some(OsStr::new("Contents")) {
return None;
}
let app_dir = contents_dir.parent()?;
if app_dir.extension() != Some(OsStr::new("app")) {
return None;
}
Some(contents_dir.join("Resources"))
}
fn push_resource_root(roots: &mut Vec<PathBuf>, root: PathBuf) {
if !root.as_os_str().is_empty() && !roots.iter().any(|candidate| candidate == &root) {
roots.push(root);
}
}
fn non_empty_env_path(key: &str) -> Option<PathBuf> {
std::env::var_os(key)
.filter(|value| !value.is_empty())
.map(PathBuf::from)
}
pub(super) fn client_script_path(path: &Path) -> String {
strip_windows_verbatim_prefix(&path.to_string_lossy()).into_owned()
}
fn strip_windows_verbatim_prefix(path: &str) -> Cow<'_, str> {
const VERBATIM_PREFIX: &str = r"\\?\";
const VERBATIM_UNC_PREFIX: &str = r"\\?\UNC\";
if let Some(rest) = path.strip_prefix(VERBATIM_UNC_PREFIX) {
return Cow::Owned(format!(r"\\{rest}"));
}
path.strip_prefix(VERBATIM_PREFIX)
.map(Cow::Borrowed)
.unwrap_or_else(|| Cow::Borrowed(path))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn includes_windows_install_locations() {
let local_app_data = PathBuf::from(r"C:\Users\alex\AppData\Local");
let install_dir = local_app_data.join("Tolaria");
let roots =
runtime_resource_roots_for_env_and_exe(None, None, Some(local_app_data.clone()), None);
assert_eq!(roots.iter().filter(|root| *root == &install_dir).count(), 1);
assert!(roots.contains(&local_app_data.join("tolaria")));
let candidates =
super::super::mcp_server_dir_candidates(Path::new("/repo/mcp-server"), &roots);
assert!(candidates.contains(&install_dir.join("mcp-server")));
}
#[test]
fn includes_macos_app_bundle_resources_from_executable_path() {
let executable = PathBuf::from("/Applications/Tolaria.app/Contents/MacOS/Tolaria");
let roots = runtime_resource_roots_for_env_and_exe(None, None, None, Some(&executable));
assert!(roots.contains(&PathBuf::from(
"/Applications/Tolaria.app/Contents/Resources"
)));
let candidates =
super::super::mcp_server_dir_candidates(Path::new("/repo/mcp-server"), &roots);
assert!(candidates.contains(&PathBuf::from(
"/Applications/Tolaria.app/Contents/Resources/mcp-server"
)));
}
#[test]
fn client_script_path_strips_windows_extended_length_disk_prefix() {
let path = PathBuf::from(r"\\?\D:\Tolaria\mcp-server\index.js");
assert_eq!(client_script_path(&path), r"D:\Tolaria\mcp-server\index.js",);
}
#[test]
fn client_script_path_strips_windows_extended_length_unc_prefix() {
let path = PathBuf::from(r"\\?\UNC\server\share\Tolaria\mcp-server\index.js");
assert_eq!(
client_script_path(&path),
r"\\server\share\Tolaria\mcp-server\index.js",
);
}
#[test]
fn client_script_path_preserves_normal_paths_with_spaces() {
let path = PathBuf::from(r"D:\Program Files\Tolaria\mcp-server\index.js");
assert_eq!(
client_script_path(&path),
r"D:\Program Files\Tolaria\mcp-server\index.js",
);
}
}

View file

@ -0,0 +1,512 @@
use std::path::{Path, PathBuf};
use std::process::Command;
use super::subprocess;
/// A resolved runtime that can execute the MCP server scripts.
#[derive(Debug, Clone)]
pub(crate) struct McpRuntime {
pub(crate) kind: McpRuntimeKind,
pub(crate) binary: PathBuf,
}
/// Which JS runtime was selected for the MCP server.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum McpRuntimeKind {
Node,
Bun,
}
impl McpRuntimeKind {
fn binary_name(self) -> &'static str {
match self {
McpRuntimeKind::Node => node_binary_name(),
McpRuntimeKind::Bun => bun_binary_name(),
}
}
}
/// Find any supported MCP runtime, preferring Node over Bun.
pub(crate) fn find_mcp_runtime() -> Result<McpRuntime, String> {
let mut last_error = None;
for kind in [McpRuntimeKind::Node, McpRuntimeKind::Bun] {
if let Some(binary) = try_runtime(kind, &mut last_error) {
return Ok(McpRuntime { kind, binary });
}
}
Err(last_error.unwrap_or_else(|| {
"No supported MCP runtime found. Install Node.js 18+ or Bun 1+ and ensure it's on PATH."
.into()
}))
}
/// Find the `node` binary specifically. Used by Codex/CLI agent shims that
/// require Node and cannot fall back to Bun.
pub(crate) fn find_node() -> Result<PathBuf, String> {
let mut last_error = None;
if let Some(binary) = try_runtime(McpRuntimeKind::Node, &mut last_error) {
return Ok(binary);
}
Err(last_error.unwrap_or_else(|| {
format!(
"{} not found in PATH or common install locations",
McpRuntimeKind::Node.binary_name()
)
}))
}
fn try_runtime(kind: McpRuntimeKind, last_error: &mut Option<String>) -> Option<PathBuf> {
for path in runtime_binary_candidates(kind) {
match verify_runtime_version(kind, &path) {
Ok(()) => return Some(path),
Err(error) => *last_error = Some(error),
}
}
None
}
fn runtime_binary_candidates(kind: McpRuntimeKind) -> Vec<PathBuf> {
let command = kind.binary_name();
let mut candidates = find_on_path(command);
candidates.extend(find_in_user_shell(command));
candidates.extend(fallback_paths_for(kind));
candidates
}
fn fallback_paths_for(kind: McpRuntimeKind) -> Vec<PathBuf> {
match kind {
McpRuntimeKind::Node => fallback_node_paths(),
McpRuntimeKind::Bun => fallback_bun_paths(),
}
}
fn verify_runtime_version(kind: McpRuntimeKind, path: &Path) -> Result<(), String> {
match kind {
McpRuntimeKind::Node => verify_node_version(path),
McpRuntimeKind::Bun => verify_bun_version(path),
}
}
fn find_on_path(command: &str) -> Vec<PathBuf> {
lookup_command(command)
.output()
.ok()
.filter(|output| output.status.success())
.map(|output| lookup_paths(&output.stdout))
.unwrap_or_default()
}
fn find_in_user_shell(command: &str) -> Vec<PathBuf> {
user_shell_candidates()
.into_iter()
.filter(|shell| shell.exists())
.filter_map(|shell| command_path_from_shell(&shell, command))
.collect()
}
fn lookup_paths(stdout: &[u8]) -> Vec<PathBuf> {
String::from_utf8_lossy(stdout)
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(PathBuf::from)
.collect()
}
fn user_shell_candidates() -> Vec<PathBuf> {
let mut shells = Vec::new();
if let Some(shell) = std::env::var_os("SHELL") {
if !shell.is_empty() {
shells.push(PathBuf::from(shell));
}
}
shells.push(PathBuf::from("/bin/zsh"));
shells.push(PathBuf::from("/bin/bash"));
shells
}
fn command_path_from_shell(shell: &Path, command: &str) -> Option<PathBuf> {
subprocess::command(shell)
.arg("-lc")
.arg(format!("command -v {command}"))
.output()
.ok()
.and_then(|output| path_from_successful_output(&output))
}
fn path_from_successful_output(output: &std::process::Output) -> Option<PathBuf> {
if output.status.success() {
first_existing_path(&String::from_utf8_lossy(&output.stdout))
} else {
None
}
}
fn first_existing_path(stdout: &str) -> Option<PathBuf> {
stdout.lines().find_map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() {
return None;
}
let candidate = PathBuf::from(trimmed);
candidate.exists().then_some(candidate)
})
}
fn verify_node_version(node: &Path) -> Result<(), String> {
let output = subprocess::command(node)
.arg("--version")
.output()
.map_err(|e| format!("Failed to run {} --version: {e}", node.display()))?;
if !output.status.success() {
return Err(format!(
"{} --version failed; install Node.js 18+ and make it available on PATH",
node.display()
));
}
let raw_version = String::from_utf8_lossy(&output.stdout);
let Some(major) = node_major_version(&raw_version) else {
return Err(format!(
"Cannot parse Node.js version from '{}'",
raw_version.trim()
));
};
if major < 18 {
return Err(format!(
"Node.js 18+ is required for Tolaria MCP tools; found {}",
raw_version.trim()
));
}
Ok(())
}
fn node_major_version(version: &str) -> Option<u32> {
version
.trim()
.trim_start_matches('v')
.split('.')
.next()
.and_then(|major| major.parse().ok())
}
fn lookup_command(command: &str) -> Command {
#[cfg(windows)]
let mut cmd = subprocess::command("where.exe");
#[cfg(not(windows))]
let mut cmd = subprocess::command("which");
cmd.arg(command);
cmd
}
fn fallback_node_paths() -> Vec<PathBuf> {
let mut candidates = vec![
PathBuf::from("/opt/homebrew/bin/node"),
PathBuf::from("/usr/local/bin/node"),
];
#[cfg(not(windows))]
candidates.push(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/node"));
#[cfg(windows)]
{
if let Some(program_files) = std::env::var_os("ProgramFiles") {
candidates.push(PathBuf::from(program_files).join("nodejs").join("node.exe"));
}
if let Some(program_files_x86) = std::env::var_os("ProgramFiles(x86)") {
candidates.push(
PathBuf::from(program_files_x86)
.join("nodejs")
.join("node.exe"),
);
}
if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
candidates.push(
PathBuf::from(local_app_data)
.join("Programs")
.join("nodejs")
.join("node.exe"),
);
}
}
if let Some(home) = dirs::home_dir() {
candidates.extend(node_binary_candidates_for_home(&home));
}
candidates
.into_iter()
.filter(|path| path.is_file())
.collect()
}
fn node_binary_candidates_for_home(home: &Path) -> Vec<PathBuf> {
let mut candidates = vec![
home.join(".local/share/mise/shims")
.join(node_binary_name()),
home.join(".mise").join("shims").join(node_binary_name()),
home.join(".asdf").join("shims").join(node_binary_name()),
home.join(".volta").join("bin").join(node_binary_name()),
home.join(".linuxbrew").join("bin").join(node_binary_name()),
];
let nvm_dir = home.join(".nvm").join("versions").join("node");
if let Ok(entries) = std::fs::read_dir(nvm_dir) {
let mut versions = entries
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
.collect::<Vec<_>>();
versions.sort();
versions.reverse();
candidates.extend(
versions
.into_iter()
.map(|version| version.join("bin").join("node")),
);
}
candidates
}
fn node_binary_name() -> &'static str {
if cfg!(windows) {
"node.exe"
} else {
"node"
}
}
fn fallback_bun_paths() -> Vec<PathBuf> {
let mut candidates = vec![
PathBuf::from("/opt/homebrew/bin/bun"),
PathBuf::from("/usr/local/bin/bun"),
];
#[cfg(windows)]
{
if let Some(profile) = std::env::var_os("USERPROFILE") {
candidates.push(
PathBuf::from(profile)
.join(".bun")
.join("bin")
.join("bun.exe"),
);
}
}
if let Some(home) = dirs::home_dir() {
candidates.extend(bun_binary_candidates_for_home(&home));
}
candidates
.into_iter()
.filter(|path| path.is_file())
.collect()
}
fn bun_binary_candidates_for_home(home: &Path) -> Vec<PathBuf> {
vec![
home.join(".bun").join("bin").join(bun_binary_name()),
home.join(".local/share/mise/shims").join(bun_binary_name()),
home.join(".mise").join("shims").join(bun_binary_name()),
home.join(".asdf").join("shims").join(bun_binary_name()),
home.join(".proto").join("bin").join(bun_binary_name()),
]
}
fn bun_binary_name() -> &'static str {
if cfg!(windows) {
"bun.exe"
} else {
"bun"
}
}
fn verify_bun_version(bun: &Path) -> Result<(), String> {
let output = subprocess::command(bun)
.arg("--version")
.output()
.map_err(|e| format!("Failed to run {} --version: {e}", bun.display()))?;
if !output.status.success() {
return Err(format!(
"{} --version failed; install Bun 1+ and make it available on PATH",
bun.display()
));
}
let raw_version = String::from_utf8_lossy(&output.stdout);
let Some(major) = node_major_version(&raw_version) else {
return Err(format!(
"Cannot parse Bun version from '{}'",
raw_version.trim()
));
};
if major < 1 {
return Err(format!(
"Bun 1+ is required for Tolaria MCP tools; found {}",
raw_version.trim()
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_candidates_include(candidates: &[PathBuf], expected: &[PathBuf]) {
for candidate in expected {
assert!(
candidates.contains(candidate),
"missing {}",
candidate.display()
);
}
}
fn assert_home_binary_candidates_include(
home: &Path,
candidates: &[PathBuf],
expected_relative_paths: &[&str],
) {
let expected = expected_relative_paths
.iter()
.map(|relative| home.join(relative))
.collect::<Vec<_>>();
assert_candidates_include(candidates, &expected);
}
#[test]
fn lookup_paths_keep_non_empty_lines_in_order() {
let stdout = b"\nC:\\Program Files\\nodejs\\node.exe\r\nC:\\Other\\node.exe\r\n";
assert_eq!(
lookup_paths(stdout),
vec![
PathBuf::from("C:\\Program Files\\nodejs\\node.exe"),
PathBuf::from("C:\\Other\\node.exe"),
]
);
}
#[test]
fn first_existing_path_skips_empty_and_missing_lines() {
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("missing-node");
let node = dir.path().join("node");
std::fs::write(&node, "#!/bin/sh\n").unwrap();
let stdout = format!("\n{}\n{}\n", missing.display(), node.display());
assert_eq!(first_existing_path(&stdout), Some(node));
}
#[cfg(unix)]
#[test]
fn command_path_from_shell_finds_node_from_login_shell() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let node = dir.path().join("node");
std::fs::write(&node, "#!/bin/sh\n").unwrap();
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o755)).unwrap();
let shell = dir.path().join("shell");
std::fs::write(
&shell,
format!(
"#!/bin/sh\nif [ \"$1\" = \"-lc\" ]; then echo '{}'; fi\n",
node.display()
),
)
.unwrap();
std::fs::set_permissions(&shell, std::fs::Permissions::from_mode(0o755)).unwrap();
assert_eq!(command_path_from_shell(&shell, "node"), Some(node));
}
#[test]
fn node_major_version_accepts_current_node_output() {
assert_eq!(node_major_version("v24.13.1\n"), Some(24));
assert_eq!(node_major_version("18.19.0"), Some(18));
assert_eq!(node_major_version("not-node"), None);
}
#[test]
fn home_binary_candidates_include_shell_managed_installs() {
let home = PathBuf::from("/Users/alex");
let cases = [
(
node_binary_candidates_for_home(&home),
&[
".local/share/mise/shims/node",
".asdf/shims/node",
".volta/bin/node",
".linuxbrew/bin/node",
][..],
),
(
bun_binary_candidates_for_home(&home),
&[
".bun/bin/bun",
".local/share/mise/shims/bun",
".mise/shims/bun",
".asdf/shims/bun",
".proto/bin/bun",
][..],
),
];
for (candidates, expected_paths) in cases {
assert_home_binary_candidates_include(&home, &candidates, expected_paths);
}
}
#[test]
fn find_node_returns_valid_path() {
let node = find_node().unwrap();
assert!(node.exists(), "node binary should exist at {:?}", node);
assert!(
node.to_string_lossy().contains("node"),
"path should contain 'node': {:?}",
node
);
}
#[test]
fn find_mcp_runtime_returns_valid_runtime() {
let runtime = find_mcp_runtime().unwrap();
assert!(
runtime.binary.exists(),
"runtime binary should exist at {:?}",
runtime.binary
);
let expected = match runtime.kind {
McpRuntimeKind::Node => "node",
McpRuntimeKind::Bun => "bun",
};
assert!(
runtime.binary.to_string_lossy().contains(expected),
"path should contain '{expected}': {:?}",
runtime.binary
);
}
#[test]
fn verify_bun_version_accepts_real_bun_binary() {
let Ok(bun) = find_bun_for_test() else {
// Bun is optional on dev machines; skip when absent.
return;
};
verify_bun_version(&bun).expect("installed bun should satisfy version requirement");
}
fn find_bun_for_test() -> Result<PathBuf, String> {
let mut last_error = None;
if let Some(bin) = try_runtime(McpRuntimeKind::Bun, &mut last_error) {
return Ok(bin);
}
Err(last_error.unwrap_or_else(|| "bun not present in test environment".into()))
}
}

View file

@ -0,0 +1,78 @@
use std::ffi::OsStr;
use std::process::Command;
#[cfg(any(test, all(desktop, target_os = "linux")))]
const APPIMAGE_ENV_REMOVALS: [&str; 3] = ["LD_LIBRARY_PATH", "LD_PRELOAD", "GIT_EXEC_PATH"];
pub(super) fn command(program: impl AsRef<OsStr>) -> Command {
let mut command = crate::hidden_command(program);
sanitize_appimage_env(&mut command);
command
}
#[cfg(all(desktop, target_os = "linux"))]
fn sanitize_appimage_env(command: &mut Command) {
sanitize_appimage_env_for_launch(command, appimage_env_present());
}
#[cfg(not(all(desktop, target_os = "linux")))]
fn sanitize_appimage_env(_command: &mut Command) {}
#[cfg(any(test, all(desktop, target_os = "linux")))]
fn sanitize_appimage_env_for_launch(command: &mut Command, is_appimage: bool) {
if !is_appimage {
return;
}
for key in APPIMAGE_ENV_REMOVALS {
command.env_remove(key);
}
}
#[cfg(all(desktop, target_os = "linux"))]
fn appimage_env_present() -> bool {
["APPIMAGE", "APPDIR"]
.into_iter()
.any(|key| std::env::var(key).is_ok_and(|value| !value.trim().is_empty()))
}
#[cfg(test)]
mod tests {
use super::*;
fn command_envs(command: &Command) -> std::collections::HashMap<String, Option<String>> {
command
.get_envs()
.map(|(key, value)| {
(
key.to_string_lossy().to_string(),
value.map(|entry| entry.to_string_lossy().to_string()),
)
})
.collect()
}
#[test]
fn appimage_mcp_subprocesses_remove_loader_env() {
let mut command = crate::hidden_command("node");
sanitize_appimage_env_for_launch(&mut command, true);
let envs = command_envs(&command);
for key in APPIMAGE_ENV_REMOVALS {
assert_eq!(envs.get(key), Some(&None));
}
}
#[test]
fn non_appimage_mcp_subprocesses_keep_parent_env_unmodified() {
let mut command = crate::hidden_command("node");
sanitize_appimage_env_for_launch(&mut command, false);
let envs = command_envs(&command);
for key in APPIMAGE_ENV_REMOVALS {
assert!(!envs.contains_key(key));
}
}
}