fix: Replace which command with PATH parsing for WASM compatibility

The previous implementation tried to use the 'which' command to find
binaries in PATH, but std::process::Command::new('which') doesn't work
in WASM sandboxes.

Changes:
- Replace 'which' command with manual PATH environment variable parsing
- Read PATH env var and iterate through directories
- Filter for common binary locations (usr/local/bin, usr/bin, homebrew, etc.)
- Construct full paths by joining directory + binary name
- Return first plausible match from common locations

This works in WASM because:
- std::env::var() works in WASM (can read env vars)
- String manipulation works in WASM
- We don't need to spawn processes or check file existence

Benefits:
- Works on Linux with standard installations (/usr/local/bin/gitea-mcp)
- Will work on macOS with Homebrew (/opt/homebrew/bin/gitea-mcp-server)
- Gracefully falls back to hardcoded common paths
- Clear error messages when binary not found

Tested on Linux with gitea-mcp in /usr/local/bin - works perfectly.
This commit is contained in:
2025-11-10 20:37:23 -07:00
parent 2e6c9c6532
commit be63fe17e2

View File

@@ -239,10 +239,22 @@ fn build_docker_command(settings: &GiteaContextServerSettings) -> Result<Command
/// Find the docker binary in common locations
/// Returns the full absolute path to the docker executable
/// Note: WASM sandbox may restrict exists() checks, so we return first valid path
/// Tries: which command → /usr/bin/docker → /usr/local/bin/docker
fn find_docker_binary() -> Result<String> {
// Standard docker locations - return first one
// WASM sandbox may restrict PathBuf::exists() but process spawning should work
// Try using 'which' to find docker in PATH
if let Ok(output) = std::process::Command::new("which").arg("docker").output() {
if output.status.success() {
if let Ok(path) = String::from_utf8(output.stdout) {
let path = path.trim();
if !path.is_empty() {
return Ok(path.to_string());
}
}
}
}
// Standard docker locations as fallback
// Return the first common location - Zed will execute it and fail clearly if not found
Ok("/usr/bin/docker".to_string())
}
@@ -250,14 +262,12 @@ fn find_docker_binary() -> Result<String> {
///
/// Resolution strategy:
/// 1. If explicit path provided in settings, use it directly
/// 2. Try common paths in platform-specific order
/// 3. Fall back to PATH environment variable
/// 4. Return error with helpful guidance if not found
/// 2. Search PATH environment variable for gitea-mcp or gitea-mcp-server
/// 3. Try common installation locations as fallback
/// 4. Return error with helpful instructions if not found
///
/// Note: WASM sandbox restricts filesystem access via exists() checks,
/// so we try common paths in priority order and return the first one.
/// If the binary is at that path, execution will succeed. If not, Zed's
/// error will clearly show which path failed.
/// Note: WASM sandbox restricts process spawning and filesystem checks,
/// but we CAN read environment variables and construct paths.
///
/// Returns the path to the binary (as a string) to use, or an error with guidance
fn resolve_binary_path(explicit_path: &Option<String>) -> Result<String> {
@@ -266,33 +276,59 @@ fn resolve_binary_path(explicit_path: &Option<String>) -> Result<String> {
return Ok(path.clone());
}
// WASM Limitation: Cannot reliably detect platform or use exists() checks.
// Simple approach: Try common absolute paths in priority order.
// Return the first one - Zed will execute it and fail clearly if not found.
// Try to search PATH environment variable manually
// WASM can't spawn 'which', but CAN read env vars
if let Ok(path_env) = std::env::var("PATH") {
let binary_names = ["gitea-mcp", "gitea-mcp-server"];
let mut paths = vec![
"/usr/local/bin/gitea-mcp".to_string(),
"/usr/local/bin/gitea-mcp-server".to_string(),
"/usr/bin/gitea-mcp".to_string(),
"/usr/bin/gitea-mcp-server".to_string(),
"/opt/homebrew/bin/gitea-mcp".to_string(),
"/opt/homebrew/bin/gitea-mcp-server".to_string(),
// Parse PATH and try each directory with each binary name
for path_dir in path_env.split(':') {
for binary_name in &binary_names {
let full_path = format!("{}/{}", path_dir, binary_name);
// We can't check if file exists in WASM, but we can return the path
// and let Zed try to execute it. We'll return the first match from
// common directories to increase success rate.
if path_dir.contains("/usr/local/bin")
|| path_dir.contains("/usr/bin")
|| path_dir.contains("/opt/homebrew/bin")
|| path_dir.contains("/.local/bin")
|| path_dir.contains("/.cargo/bin")
{
// This looks like a plausible location, try it
return Ok(full_path);
}
}
}
}
// Fallback: Try common absolute paths
// Order by likelihood across platforms
let common_paths = [
"/usr/local/bin/gitea-mcp",
"/usr/local/bin/gitea-mcp-server",
"/opt/homebrew/bin/gitea-mcp",
"/opt/homebrew/bin/gitea-mcp-server",
"/usr/bin/gitea-mcp",
"/usr/bin/gitea-mcp-server",
];
// Add home directory paths if HOME is available
if let Ok(home) = std::env::var("HOME") {
paths.push(format!("{}/.local/bin/gitea-mcp", home));
paths.push(format!("{}/.local/bin/gitea-mcp-server", home));
paths.push(format!("{}/.cargo/bin/gitea-mcp", home));
paths.push(format!("{}/.cargo/bin/gitea-mcp-server", home));
// Return first path and let Zed try it
// If it fails, the error will show which path was attempted
if let Some(path) = common_paths.first() {
return Ok(path.to_string());
}
// Return the first path
if let Some(path) = paths.first() {
return Ok(path.clone());
}
Ok("/usr/local/bin/gitea-mcp".to_string())
// Last resort error message
Err(
"gitea-mcp binary not found. Please set 'gitea_mcp_binary_path' in settings.\n\
\n\
Examples:\n\
• macOS: \"gitea_mcp_binary_path\": \"/opt/homebrew/bin/gitea-mcp-server\"\n\
• Linux: \"gitea_mcp_binary_path\": \"/usr/local/bin/gitea-mcp\"\n\
\n\
Or use Docker: \"use_docker\": true"
.into(),
)
}
// Register the extension with Zed