mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 05:20:02 +00:00
@@ -340,7 +340,68 @@ pub(crate) async fn resolve_mcp_server_command() -> Result<(String, Vec<String>)
|
||||
|
||||
fn resolve_mcp_server_command_sync() -> Option<(String, Vec<String>)> {
|
||||
let runtime = resolve_node_runtime();
|
||||
resolve_managed_mcp_command(runtime.as_ref(), locate_mcp_bin)
|
||||
if let Some(command) = runtime.as_ref().and_then(mcp_command_for_runtime) {
|
||||
return Some(command);
|
||||
}
|
||||
|
||||
let path_shim = locate_mcp_bin();
|
||||
resolve_mise_mcp_command_from_path(runtime.as_ref(), path_shim.as_deref())
|
||||
.or_else(|| resolve_managed_mcp_command(runtime.as_ref(), || path_shim))
|
||||
}
|
||||
|
||||
fn resolve_mise_mcp_command_from_path(
|
||||
runtime: Option<&NodeRuntime>,
|
||||
shim_path: Option<&Path>,
|
||||
) -> Option<(String, Vec<String>)> {
|
||||
let shim_path = shim_path?;
|
||||
if !is_shims_command_path(shim_path) {
|
||||
return None;
|
||||
}
|
||||
let mise_path = locate_command("mise").map(PathBuf::from).and_then(|path| canonical_runtime_path(&path))?;
|
||||
resolve_mise_mcp_command(runtime, Some(shim_path), |command| mise_which_path(&mise_path, command))
|
||||
}
|
||||
|
||||
fn resolve_mise_mcp_command(
|
||||
runtime: Option<&NodeRuntime>,
|
||||
shim_path: Option<&Path>,
|
||||
mut resolve_mise_command: impl FnMut(&str) -> Option<PathBuf>,
|
||||
) -> Option<(String, Vec<String>)> {
|
||||
let runtime = runtime?;
|
||||
if !is_mcp_compatible_node_version(&runtime.node_version) {
|
||||
return None;
|
||||
}
|
||||
let shim_path = shim_path?;
|
||||
if !is_shims_command_path(shim_path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let runtime_node = canonical_runtime_path(&runtime.node_path)?;
|
||||
let mise_node = resolve_mise_command("node").and_then(|path| canonical_runtime_path(&path))?;
|
||||
if runtime_node != mise_node {
|
||||
return None;
|
||||
}
|
||||
|
||||
let shim_path = canonical_runtime_path(shim_path)?;
|
||||
let script_path = resolve_mise_command("dbx-mcp-server").and_then(|path| canonical_runtime_path(&path))?;
|
||||
if script_path == shim_path {
|
||||
return None;
|
||||
}
|
||||
let (_, package) = mcp_package_from_script(&script_path)?;
|
||||
let node_version = parse_node_version(&runtime.node_version)?;
|
||||
if !mcp_package_supports_node(&package, node_version) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((path_string(&runtime_node), vec![path_string(&package.script_path)]))
|
||||
}
|
||||
|
||||
fn is_shims_command_path(path: &Path) -> bool {
|
||||
path.is_file() && path.parent().and_then(Path::file_name).is_some_and(|name| name.eq_ignore_ascii_case("shims"))
|
||||
}
|
||||
|
||||
fn mise_which_path(mise_path: &Path, command: &str) -> Option<PathBuf> {
|
||||
let output = direct_command_stdout(mise_path, &["which", command]).ok()?;
|
||||
first_non_empty_line(output).and_then(|path| canonical_runtime_path(Path::new(&path)))
|
||||
}
|
||||
|
||||
fn resolve_managed_mcp_command(
|
||||
@@ -1134,8 +1195,9 @@ mod tests {
|
||||
use super::{
|
||||
canonical_runtime_path, is_mcp_compatible_node_version, mcp_command_for_runtime, mcp_native_binary_path_for,
|
||||
mcp_package, normalized_reported_path, npm_cli_candidates, parse_minimum_node_version, parse_node_version,
|
||||
prefer_runtime, require_managed_mcp_command, resolve_managed_mcp_command, stdout_after_shell_marker,
|
||||
NodeRuntime, NodeVersion, MCP_MIN_NODE_VERSION_REQUIREMENT, MCP_PACKAGE_NAME, SHELL_COMMAND_MARKER,
|
||||
prefer_runtime, require_managed_mcp_command, resolve_managed_mcp_command, resolve_mise_mcp_command,
|
||||
stdout_after_shell_marker, NodeRuntime, NodeVersion, MCP_MIN_NODE_VERSION_REQUIREMENT, MCP_PACKAGE_NAME,
|
||||
SHELL_COMMAND_MARKER,
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
use super::{shell_command_script, shell_quote};
|
||||
@@ -1389,6 +1451,116 @@ mod tests {
|
||||
assert_eq!(command.1, vec!["/runtime/node-24-mcp/dist/index.js"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_command_resolves_native_mise_shim_to_declared_package_entry() {
|
||||
use std::cell::Cell;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
let nonce = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
|
||||
let dir = std::env::temp_dir().join(format!("dbx mise shim test {} {nonce}", std::process::id()));
|
||||
let shim_dir = dir.join("mise").join("shims");
|
||||
let shim_path = shim_dir.join("dbx-mcp-server.exe");
|
||||
let node_path = dir.join("mise").join("installs").join("node").join("22.23.1").join("node.exe");
|
||||
let package_root = dir
|
||||
.join("mise")
|
||||
.join("installs")
|
||||
.join("npm-db-server")
|
||||
.join("node_modules")
|
||||
.join("@dbx-app")
|
||||
.join("mcp-server");
|
||||
let script_path = package_root.join("bin").join("dbx-mcp-server.js");
|
||||
|
||||
std::fs::create_dir_all(&shim_dir).unwrap();
|
||||
std::fs::create_dir_all(node_path.parent().unwrap()).unwrap();
|
||||
std::fs::create_dir_all(script_path.parent().unwrap()).unwrap();
|
||||
std::fs::write(&shim_path, "native mise shim\n").unwrap();
|
||||
std::fs::write(&node_path, "real node runtime\n").unwrap();
|
||||
std::fs::write(&script_path, "// declared MCP entry\n").unwrap();
|
||||
std::fs::write(
|
||||
package_root.join("package.json"),
|
||||
r#"{"name":"@dbx-app/mcp-server","version":"0.4.44","bin":{"dbx-mcp-server":"bin/dbx-mcp-server.js"},"engines":{"node":">=18.18.0"}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(super::node_script_from_launcher(&shim_path).is_none());
|
||||
let node_path = canonical_runtime_path(&node_path).unwrap();
|
||||
let script_path = canonical_runtime_path(&script_path).unwrap();
|
||||
let compatible_runtime = runtime_with_version_and_root(
|
||||
node_path.to_string_lossy().as_ref(),
|
||||
dir.join("npm-root").to_string_lossy().as_ref(),
|
||||
None,
|
||||
"v22.23.1",
|
||||
);
|
||||
|
||||
let command = resolve_mise_mcp_command(Some(&compatible_runtime), Some(&shim_path), |command| match command {
|
||||
"node" => Some(node_path.clone()),
|
||||
"dbx-mcp-server" => Some(script_path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(command.0, node_path.to_string_lossy());
|
||||
assert_eq!(command.1, vec![script_path.to_string_lossy()]);
|
||||
|
||||
let wrong_node = dir.join("other-node.exe");
|
||||
std::fs::write(&wrong_node, "wrong node runtime\n").unwrap();
|
||||
assert!(resolve_mise_mcp_command(Some(&compatible_runtime), Some(&shim_path), |command| match command {
|
||||
"node" => Some(wrong_node.clone()),
|
||||
"dbx-mcp-server" => Some(script_path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.is_none());
|
||||
|
||||
let old_runtime = runtime_with_version_and_root(
|
||||
node_path.to_string_lossy().as_ref(),
|
||||
dir.join("old-npm-root").to_string_lossy().as_ref(),
|
||||
None,
|
||||
"v18.17.1",
|
||||
);
|
||||
assert!(resolve_mise_mcp_command(Some(&old_runtime), Some(&shim_path), |command| match command {
|
||||
"node" => Some(node_path.clone()),
|
||||
"dbx-mcp-server" => Some(script_path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.is_none());
|
||||
|
||||
assert!(resolve_mise_mcp_command(Some(&compatible_runtime), Some(&shim_path), |_| None).is_none());
|
||||
let unrelated_script = package_root.join("bin").join("other.js");
|
||||
std::fs::write(&unrelated_script, "// not the declared entry\n").unwrap();
|
||||
assert!(resolve_mise_mcp_command(Some(&compatible_runtime), Some(&shim_path), |command| match command {
|
||||
"node" => Some(node_path.clone()),
|
||||
"dbx-mcp-server" => Some(unrelated_script.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.is_none());
|
||||
|
||||
let ordinary_bin = dir.join("bin");
|
||||
let ordinary_shim = ordinary_bin.join("dbx-mcp-server.exe");
|
||||
std::fs::create_dir_all(&ordinary_bin).unwrap();
|
||||
std::fs::write(&ordinary_shim, "ordinary native shim\n").unwrap();
|
||||
let resolver_calls = Cell::new(0);
|
||||
assert!(resolve_mise_mcp_command(Some(&compatible_runtime), Some(&ordinary_shim), |_| {
|
||||
resolver_calls.set(resolver_calls.get() + 1);
|
||||
None
|
||||
})
|
||||
.is_none());
|
||||
assert_eq!(resolver_calls.get(), 0);
|
||||
|
||||
std::fs::write(
|
||||
package_root.join("package.json"),
|
||||
r#"{"name":"@dbx-app/mcp-server","version":"0.4.44","bin":{"dbx-mcp-server":"bin/dbx-mcp-server.js"},"engines":{"node":">=23.0.0"}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(resolve_mise_mcp_command(Some(&compatible_runtime), Some(&shim_path), |command| match command {
|
||||
"node" => Some(node_path.clone()),
|
||||
"dbx-mcp-server" => Some(script_path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.is_none());
|
||||
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incompatible_runtime_does_not_fall_back_to_available_mcp_shim() {
|
||||
let incompatible = runtime_with_version_and_root(
|
||||
|
||||
Reference in New Issue
Block a user