diff --git a/src/mcp_server_gitea.rs b/src/mcp_server_gitea.rs index d3db507..52c6c04 100644 --- a/src/mcp_server_gitea.rs +++ b/src/mcp_server_gitea.rs @@ -10,21 +10,21 @@ use zed_extension_api::{ /// This extension launches a gitea-mcp binary (locally or via Docker) and communicates with it /// to provide Gitea repository access through Zed's AI assistant. /// -/// Binary Resolution Strategy: -/// 1. If `gitea_mcp_binary_path` is set in settings, use that exact path -/// 2. If `use_docker` is true, use Docker to run the gitea-mcp image -/// 3. Otherwise, search common system paths: -/// - /usr/local/bin/gitea-mcp -/// - ~/.local/bin/gitea-mcp -/// - ~/.cargo/bin/gitea-mcp -/// - /opt/homebrew/bin/gitea-mcp (macOS) -/// - Search in PATH environment variable +/// Configuration Requirements: +/// - EITHER set `gitea_mcp_binary_path` to the full path of the gitea-mcp binary +/// - OR set `use_docker: true` to run gitea-mcp in Docker /// -/// Transport modes: -/// - STDIO (default, recommended): Direct stdin/stdout communication -/// Works with Zed's extension API and gitea-mcp binary -/// - Docker: Runs gitea-mcp in a Docker container -/// Useful when binary isn't available on host system +/// WASM Limitation: This extension runs in a WebAssembly sandbox which cannot: +/// - Detect the host operating system +/// - Access PATH environment variable reliably +/// - Check if files exist on the filesystem +/// +/// Therefore, explicit configuration is required. +/// +/// Example paths: +/// - macOS (Homebrew): /opt/homebrew/bin/gitea-mcp-server +/// - Linux: /usr/local/bin/gitea-mcp +/// - Windows: C:\Program Files\gitea-mcp\gitea-mcp.exe struct GiteaModelContextExtension; #[derive(Debug, Deserialize, JsonSchema)] @@ -237,150 +237,70 @@ fn build_docker_command(settings: &GiteaContextServerSettings) -> Result Result { - // 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()) + // Just return "docker" - the system will find it in PATH + // If docker isn't installed or not in PATH, the execution will fail with a clear error + Ok("docker".to_string()) } -/// Resolve the gitea-mcp binary path with intelligent fallbacks +/// Resolve the gitea-mcp binary path /// -/// Resolution strategy: -/// 1. If explicit path provided in settings, use it directly -/// 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 +/// WASM Limitation: WebAssembly sandbox cannot reliably: +/// - Detect host OS (macOS vs Linux vs Windows) +/// - Access PATH environment variable +/// - Check if files exist +/// - Spawn commands like 'which' /// -/// Note: WASM sandbox restricts process spawning and filesystem checks, -/// but we CAN read environment variables and construct paths. +/// Therefore, this function REQUIRES explicit configuration. +/// Users MUST set either: +/// - gitea_mcp_binary_path: "/full/path/to/gitea-mcp" +/// - use_docker: true /// -/// Returns the path to the binary (as a string) to use, or an error with guidance +/// Returns the path to the binary or an error with configuration instructions fn resolve_binary_path(explicit_path: &Option) -> Result { - // If explicit path provided, use it (prioritize user configuration) + // Explicit path is REQUIRED in WASM environment if let Some(path) = explicit_path { return Ok(path.clone()); } - // 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"]; - - // Collect all candidate paths from PATH, then prioritize them - let mut candidates = Vec::new(); - - // DEBUG: Log PATH directories being considered - eprintln!("[TENDRIL DEBUG] PATH environment variable: {}", path_env); - eprintln!("[TENDRIL DEBUG] Searching for binaries: {:?}", binary_names); - - // Parse PATH and collect all plausible paths - for path_dir in path_env.split(':') { - for binary_name in &binary_names { - let full_path = format!("{}/{}", path_dir, binary_name); - - // Categorize by priority (lower number = higher priority) - let priority = if path_dir.contains("/opt/homebrew/bin") { - eprintln!( - "[TENDRIL DEBUG] Found Homebrew path (priority 1): {}", - full_path - ); - 1 // Highest: macOS Homebrew (M1/M2/M3/M4 Macs) - } else if path_dir.contains("/.local/bin") { - eprintln!( - "[TENDRIL DEBUG] Found .local path (priority 2): {}", - full_path - ); - 2 // User-local installations - } else if path_dir.contains("/.cargo/bin") { - eprintln!( - "[TENDRIL DEBUG] Found .cargo path (priority 3): {}", - full_path - ); - 3 // Cargo installations - } else if path_dir.contains("/usr/local/bin") { - eprintln!( - "[TENDRIL DEBUG] Found /usr/local path (priority 4): {}", - full_path - ); - 4 // Linux standard location - } else if path_dir.contains("/usr/bin") { - eprintln!( - "[TENDRIL DEBUG] Found /usr/bin path (priority 5): {}", - full_path - ); - 5 // System binaries - } else { - eprintln!("[TENDRIL DEBUG] Skipping non-standard path: {}", path_dir); - continue; // Skip non-standard locations - }; - - candidates.push((priority, full_path)); - } - } - - // Sort by priority and return the highest priority candidate - candidates.sort_by_key(|(priority, _)| *priority); - eprintln!( - "[TENDRIL DEBUG] All candidates after sorting: {:?}", - candidates - ); - - if let Some((priority, path)) = candidates.first() { - eprintln!( - "[TENDRIL DEBUG] Selected path (priority {}): {}", - priority, path - ); - return Ok(path.clone()); - } else { - eprintln!("[TENDRIL DEBUG] No candidates found in PATH"); - } - } else { - eprintln!("[TENDRIL DEBUG] PATH environment variable not found"); - } - - // 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", - ]; - - // 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()); - } - - // Last resort error message - Err( - "gitea-mcp binary not found. Please set 'gitea_mcp_binary_path' in settings.\n\ + // No path provided - return clear error with platform-specific instructions + Err("gitea-mcp binary path not configured.\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\ + Due to WebAssembly sandbox limitations, automatic binary detection is not possible.\n\ + Please add ONE of the following to your Zed settings:\n\ \n\ - Or use Docker: \"use_docker\": true" - .into(), - ) + Option 1 - Specify binary path explicitly:\n\ + {\n\ + \"context_servers\": {\n\ + \"tendril-gitea-mcp\": {\n\ + \"settings\": {\n\ + \"gitea_access_token\": \"your_token\",\n\ + \"gitea_mcp_binary_path\": \"/full/path/to/binary\"\n\ + }\n\ + }\n\ + }\n\ + }\n\ + \n\ + Common paths:\n\ + • macOS (Homebrew): /opt/homebrew/bin/gitea-mcp-server\n\ + • Linux: /usr/local/bin/gitea-mcp\n\ + • Windows: C:\\Program Files\\gitea-mcp\\gitea-mcp.exe\n\ + \n\ + Option 2 - Use Docker:\n\ + {\n\ + \"context_servers\": {\n\ + \"tendril-gitea-mcp\": {\n\ + \"settings\": {\n\ + \"gitea_access_token\": \"your_token\",\n\ + \"use_docker\": true\n\ + }\n\ + }\n\ + }\n\ + }" + .into()) } // Register the extension with Zed