fix(mcp): resolve mise-managed server shims

Closes #6930
This commit is contained in:
t8y2
2026-08-22 01:03:17 +00:00
parent bb90c5febc
commit da49e03048
+175 -3
View File
@@ -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(