From be63fe17e24eefedf0fca23ffa1fdfb851d6a814 Mon Sep 17 00:00:00 2001 From: Ryan Parmeter Date: Mon, 10 Nov 2025 20:37:23 -0700 Subject: [PATCH] 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. --- src/mcp_server_gitea.rs | 100 +++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/src/mcp_server_gitea.rs b/src/mcp_server_gitea.rs index 40cfc99..4cca38b 100644 --- a/src/mcp_server_gitea.rs +++ b/src/mcp_server_gitea.rs @@ -239,10 +239,22 @@ fn build_docker_command(settings: &GiteaContextServerSettings) -> Result Result { - // 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 { /// /// 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) -> Result { @@ -266,33 +276,59 @@ fn resolve_binary_path(explicit_path: &Option) -> Result { 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