diff --git a/crates/goose-mcp/src/developer/mod.rs b/crates/goose-mcp/src/developer/mod.rs index 8accfe0e8298..33e5f9712910 100644 --- a/crates/goose-mcp/src/developer/mod.rs +++ b/crates/goose-mcp/src/developer/mod.rs @@ -9,8 +9,9 @@ use indoc::formatdoc; use serde_json::Value; use std::{ collections::HashMap, + fs::File, future::Future, - io::Cursor, + io::{Cursor, Read}, path::{Path, PathBuf}, pin::Pin, }; @@ -975,9 +976,12 @@ impl DeveloperRouter { if path.is_file() { // Check file size first (400KB limit) const MAX_FILE_SIZE: u64 = 400 * 1024; // 400KB in bytes - const MAX_CHAR_COUNT: usize = 400_000; // 409600 chars = 400KB - let file_size = std::fs::metadata(path) + let f = File::open(path) + .map_err(|e| ToolError::ExecutionError(format!("Failed to open file: {}", e)))?; + + let file_size = f + .metadata() .map_err(|e| { ToolError::ExecutionError(format!("Failed to get file metadata: {}", e)) })? @@ -990,24 +994,18 @@ impl DeveloperRouter { file_size as f64 / 1024.0 ))); } + // Ensure we never read over that limit even if the file is being concurrently mutated + // (e.g. it's a log file) + let mut f = f.take(MAX_FILE_SIZE); let uri = Url::from_file_path(path) .map_err(|_| ToolError::ExecutionError("Invalid file path".into()))? .to_string(); - let content = std::fs::read_to_string(path) + let mut content = String::new(); + f.read_to_string(&mut content) .map_err(|e| ToolError::ExecutionError(format!("Failed to read file: {}", e)))?; - let char_count = content.chars().count(); - if char_count > MAX_CHAR_COUNT { - return Err(ToolError::ExecutionError(format!( - "File '{}' has too many characters ({}). Maximum character count is {}.", - path.display(), - char_count, - MAX_CHAR_COUNT - ))); - } - let lines: Vec<&str> = content.lines().collect(); let total_lines = lines.len(); @@ -1909,8 +1907,8 @@ mod tests { let many_chars_path = temp_dir.path().join("many_chars.txt"); let many_chars_str = many_chars_path.to_str().unwrap(); - // Create a file with more than 400K characters but less than 400KB - let content = "x".repeat(405_000); + // This is above MAX_FILE_SIZE + let content = "x".repeat(500_000); std::fs::write(&many_chars_path, content).unwrap(); let result = router @@ -1927,7 +1925,7 @@ mod tests { assert!(result.is_err()); let err = result.err().unwrap(); assert!(matches!(err, ToolError::ExecutionError(_))); - assert!(err.to_string().contains("too many characters")); + assert!(err.to_string().contains("is too large")); } // Let temp_dir drop naturally at end of scope