diff --git a/src/handler.rs b/src/handler.rs index 55b9b0d..a2841d7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -150,6 +150,9 @@ impl ServerHandler for MyServerHandler { FileSystemTools::SearchFilesContentTool(params) => { SearchFilesContentTool::run_tool(params, &self.fs_service).await } + FileSystemTools::ListDirectoryWithSizesTool(params) => { + ListDirectoryWithSizesTool::run_tool(params, &self.fs_service).await + } } } } diff --git a/src/tools.rs b/src/tools.rs index 3fbab26..fea0583 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -4,6 +4,7 @@ mod edit_file; mod get_file_info; mod list_allowed_directories; mod list_directory; +mod list_directory_with_sizes; mod move_file; mod read_files; mod read_multiple_files; @@ -18,6 +19,7 @@ pub use edit_file::{EditFileTool, EditOperation}; pub use get_file_info::GetFileInfoTool; pub use list_allowed_directories::ListAllowedDirectoriesTool; pub use list_directory::ListDirectoryTool; +pub use list_directory_with_sizes::ListDirectoryWithSizesTool; pub use move_file::MoveFileTool; pub use read_files::ReadFileTool; pub use read_multiple_files::ReadMultipleFilesTool; @@ -45,7 +47,8 @@ tool_box!( ZipFilesTool, UnzipFileTool, ZipDirectoryTool, - SearchFilesContentTool + SearchFilesContentTool, + ListDirectoryWithSizesTool ] ); @@ -68,6 +71,7 @@ impl FileSystemTools { | FileSystemTools::ListDirectoryTool(_) | FileSystemTools::ReadMultipleFilesTool(_) | FileSystemTools::SearchFilesContentTool(_) + | FileSystemTools::ListDirectoryWithSizesTool(_) | FileSystemTools::SearchFilesTool(_) => false, } } diff --git a/src/tools/list_directory_with_sizes.rs b/src/tools/list_directory_with_sizes.rs new file mode 100644 index 0000000..e36801d --- /dev/null +++ b/src/tools/list_directory_with_sizes.rs @@ -0,0 +1,91 @@ +use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; +use std::fmt::Write; +use std::path::Path; + +use crate::fs_service::utils::format_bytes; +use crate::fs_service::FileSystemService; + +#[mcp_tool( + name = "list_directory_with_sizes", + description = concat!("Get a detailed listing of all files and directories in a specified path, including sizes. " , + "Results clearly distinguish between files and directories with [FILE] and [DIR] prefixes. " , + "This tool is useful for understanding directory structure and " , + "finding specific files within a directory. Only works within allowed directories."), + destructive_hint = false, + idempotent_hint = false, + open_world_hint = false, + read_only_hint = true +)] +#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, JsonSchema)] +pub struct ListDirectoryWithSizesTool { + /// The path of the directory to list. + pub path: String, +} + +impl ListDirectoryWithSizesTool { + async fn format_directory_entries( + &self, + mut entries: Vec, + ) -> std::result::Result { + let mut file_count = 0; + let mut dir_count = 0; + let mut total_size: u64 = 0; + + // Estimate initial capacity: assume ~50 bytes per entry + summary + let mut output = String::with_capacity(entries.len() * 50 + 120); + + // Sort entries by file name + entries.sort_by_key(|a| a.file_name()); + + // build the output string + for entry in &entries { + let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); + + if entry.path().is_dir() { + writeln!(output, "[DIR] {file_name:<30}").map_err(CallToolError::new)?; + dir_count += 1; + } else if entry.path().is_file() { + let metadata = entry.metadata().await.map_err(CallToolError::new)?; + + let file_size = metadata.len(); + writeln!( + output, + "[FILE] {:<30} {:>10}", + file_name, + format_bytes(file_size) + ) + .map_err(CallToolError::new)?; + file_count += 1; + total_size += file_size; + } + } + + // Append summary + writeln!( + output, + "\nTotal: {file_count} files, {dir_count} directories" + ) + .map_err(CallToolError::new)?; + writeln!(output, "Total size: {}", format_bytes(total_size)).map_err(CallToolError::new)?; + + Ok(output) + } + + pub async fn run_tool( + params: Self, + context: &FileSystemService, + ) -> std::result::Result { + let entries = context + .list_directory(Path::new(¶ms.path)) + .await + .map_err(CallToolError::new)?; + + let output = params + .format_directory_entries(entries) + .await + .map_err(CallToolError::new)?; + Ok(CallToolResult::text_content(output, None)) + } +}