fix(desktop): Improve shell Node detection for MCP startup

Thanks for the detailed investigation and fix!
This commit is contained in:
Doracoin 2026-06-10 21:30:44 +08:00 committed by GitHub
parent 9f0dae1754
commit 7234d9ebd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 63 additions and 15 deletions

View File

@ -1,4 +1,3 @@
use std::env;
use std::path::Path;
use std::process::Command;
use std::time::Duration;
@ -84,20 +83,26 @@ async fn fetch_latest_mcp_version() -> Result<String, String> {
}
fn installed_mcp_version() -> Option<String> {
let stdout = command_stdout("npm", &["list", "-g", MCP_PACKAGE_NAME, "--json", "--depth=0"]).ok()?;
let value = serde_json::from_str::<serde_json::Value>(&stdout).ok()?;
value
.get("dependencies")
.and_then(|dependencies| dependencies.get(MCP_PACKAGE_NAME))
.and_then(|package| package.get("version"))
.and_then(|version| version.as_str())
.map(ToOwned::to_owned)
let root = command_stdout("npm", &["root", "-g"]).ok()?;
let pkg_json_path = Path::new(root.trim()).join(MCP_PACKAGE_NAME).join("package.json");
let content = std::fs::read_to_string(pkg_json_path).ok()?;
let value: serde_json::Value = serde_json::from_str(&content).ok()?;
value.get("version")?.as_str().map(ToOwned::to_owned)
}
fn locate_mcp_bin() -> Option<String> {
let (command, args): (&str, &[&str]) =
if cfg!(windows) { ("where", &["dbx-mcp-server"]) } else { ("which", &["dbx-mcp-server"]) };
command_stdout(command, args).ok().and_then(first_non_empty_line)
if cfg!(windows) {
return locate_windows_command("dbx-mcp-server");
}
command_stdout("which", &["dbx-mcp-server"]).ok().and_then(first_non_empty_line)
}
#[cfg(windows)]
fn locate_windows_command(command: &str) -> Option<String> {
command_stdout("where", &[command]).ok().and_then(first_non_empty_line).or_else(|| {
let script = format!("(Get-Command {} -ErrorAction SilentlyContinue).Source", windows_shell_quote(command));
command_stdout("powershell.exe", &["-NoProfile", "-Command", &script]).ok().and_then(first_non_empty_line)
})
}
fn command_success(command: &str, args: &[&str]) -> bool {
@ -129,7 +134,15 @@ fn command_output(command: &str, args: &[&str]) -> Result<CommandOutput, String>
return direct;
}
run_command_through_user_shell(command, args).or(direct)
#[cfg(windows)]
{
return run_windows_command_candidates(command, args).or(direct);
}
#[cfg(not(windows))]
{
run_command_through_user_shell(command, args).or(direct)
}
}
fn run_command(command: &str, args: &[&str]) -> Result<CommandOutput, String> {
@ -142,8 +155,43 @@ fn run_command(command: &str, args: &[&str]) -> Result<CommandOutput, String> {
}
#[cfg(windows)]
fn run_command_through_user_shell(_command: &str, _args: &[&str]) -> Result<CommandOutput, String> {
Err("User shell fallback is not available on Windows.".to_string())
fn run_windows_command_candidates(command: &str, args: &[&str]) -> Result<CommandOutput, String> {
for candidate in windows_command_candidates(command) {
let output = run_command(&candidate, args);
if output.as_ref().is_ok_and(|output| output.success) {
return output;
}
}
run_command_through_user_shell(command, args)
}
#[cfg(windows)]
fn windows_command_candidates(command: &str) -> Vec<String> {
if Path::new(command).extension().is_some() {
return Vec::new();
}
["cmd", "exe", "ps1"].iter().map(|extension| format!("{command}.{extension}")).collect()
}
#[cfg(windows)]
fn run_command_through_user_shell(command: &str, args: &[&str]) -> Result<CommandOutput, String> {
let script = windows_command_script(command, args);
let mut output = run_command("powershell.exe", &["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &script])?;
output.stdout = stdout_after_shell_marker(&output.stdout);
Ok(output)
}
#[cfg(windows)]
fn windows_command_script(command: &str, args: &[&str]) -> String {
let mut words = Vec::with_capacity(args.len() + 1);
words.push(windows_shell_quote(command));
words.extend(args.iter().map(|arg| windows_shell_quote(arg)));
format!("Write-Output {}; & {}", windows_shell_quote(SHELL_COMMAND_MARKER), words.join(" "))
}
#[cfg(windows)]
fn windows_shell_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
#[cfg(not(windows))]