From c6d5a15dc02f1fd30bfcd8eb398b441e70652975 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Sat, 21 Jun 2025 19:50:59 -0300 Subject: [PATCH 1/5] chore: add stale github action --- .github/workflows/stale.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..aba57fc --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,31 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" # Runs daily at midnight UTC + workflow_dispatch: # Allow manual trigger + +jobs: + stale: + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + ascending: true + stale-issue-message: "This issue is stale because it has been open 30 days with no activity. It will close in 7 days unless additional input is provided. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you. (This is an automated message)" + stale-pr-message: "This PR is stale because it has been open 90 days with no activity. It will close in 15 days unless additional input is provided. (This is an automated message)" + close-issue-message: "This issue was closed because it has been stalled for 37 days with no activity." + close-pr-message: "This PR was closed because it has been stalled for 105 days with no activity." + days-before-issue-stale: 30 + days-before-pr-stale: 90 + days-before-issue-close: 5 + days-before-pr-close: 15 + remove-pr-stale-when-updated: true + remove-issue-stale-when-updated: true + stale-issue-label: "stale" + stale-pr-label: "stale" + exempt-issue-labels: "roadmap,security" + exempt-pr-labels: "work-in-progress,wip" + operations-per-run: 300 From 01f956efdde5fdd0e5fd14f30e4ebdac53d728f7 Mon Sep 17 00:00:00 2001 From: Ali Hashemi <14126952+hashemix@users.noreply.github.com> Date: Fri, 4 Jul 2025 21:07:15 -0300 Subject: [PATCH 2/5] fix: directory tree tool result (#26) * fix: directory tree result * chore: fix typo * chore: clippy warnings --- rust-toolchain.toml | 3 + src/fs_service.rs | 94 +++++++++++++++++++++++++++++--- src/fs_service/utils.rs | 2 +- src/handler.rs | 2 +- src/tools/directory_tree.rs | 34 +++++++----- src/tools/read_multiple_files.rs | 4 +- 6 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..7855e6d --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.88.0" +components = ["rustfmt", "clippy"] diff --git a/src/fs_service.rs b/src/fs_service.rs index a9c6ad0..7c009f2 100644 --- a/src/fs_service.rs +++ b/src/fs_service.rs @@ -6,6 +6,7 @@ use grep::{ regex::RegexMatcherBuilder, searcher::{sinks::UTF8, BinaryDetection, Searcher}, }; +use serde_json::{json, Value}; use std::{ env, @@ -67,7 +68,7 @@ impl FileSystemService { .map_while(|dir| { let expand_result = expand_home(dir.into()); if !expand_result.is_dir() { - panic!("{}", format!("Error: {} is not a directory", dir)); + panic!("{}", format!("Error: {dir} is not a directory")); } Some(expand_result) }) @@ -179,7 +180,7 @@ impl FileSystemService { if target_path.exists() { return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, - format!("'{}' already exists!", target_zip_file), + format!("'{target_zip_file}' already exists!"), ) .into()); } @@ -269,7 +270,7 @@ impl FileSystemService { if target_path.exists() { return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, - format!("'{}' already exists!", target_zip_file), + format!("'{target_zip_file}' already exists!"), ) .into()); } @@ -326,7 +327,7 @@ impl FileSystemService { if target_dir_path.exists() { return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, - format!("'{}' directory already exists!", target_dir), + format!("'{target_dir}' directory already exists!"), ) .into()); } @@ -473,7 +474,7 @@ impl FileSystemService { let glob_pattern = if pattern.contains('*') { pattern.clone() } else { - format!("*{}*", pattern) + format!("*{pattern}*") }; Pattern::new(&glob_pattern) @@ -501,6 +502,85 @@ impl FileSystemService { Ok(result) } + /// Generates a JSON representation of a directory tree starting at the given path. + /// + /// This function recursively builds a JSON array object representing the directory structure, + /// where each entry includes a `name` (file or directory name), `type` ("file" or "directory"), + /// and for directories, a `children` array containing their contents. Files do not have a + /// `children` field. + /// + /// The function supports optional constraints to limit the tree size: + /// - `max_depth`: Limits the depth of directory traversal. + /// - `max_files`: Limits the total number of entries (files and directories). + /// + /// # IMPORTANT NOTE + /// + /// use max_depth or max_files could lead to partial or skewed representations of actual directory tree + pub fn directory_tree>( + &self, + root_path: P, + max_depth: Option, + max_files: Option, + current_count: &mut usize, + ) -> ServiceResult { + let valid_path = self.validate_path(root_path.as_ref())?; + + let metadata = fs::metadata(&valid_path)?; + if !metadata.is_dir() { + return Err(ServiceError::FromString( + "Root path must be a directory".into(), + )); + } + + let mut children = Vec::new(); + + if max_depth != Some(0) { + for entry in WalkDir::new(valid_path) + .min_depth(1) + .max_depth(1) + .follow_links(true) + .into_iter() + .filter_map(|e| e.ok()) + { + let child_path = entry.path(); + let metadata = fs::metadata(child_path)?; + + let entry_name = child_path + .file_name() + .ok_or(ServiceError::FromString("Invalid path".to_string()))? + .to_string_lossy() + .into_owned(); + + // Increment the count for this entry + *current_count += 1; + + // Check if we've exceeded max_files (if set) + if let Some(max) = max_files { + if *current_count > max { + continue; // Skip this entry but continue processing others + } + } + + let mut json_entry = json!({ + "name": entry_name, + "type": if metadata.is_dir() { "directory" } else { "file" } + }); + + if metadata.is_dir() { + let next_depth = max_depth.map(|d| d - 1); + let child_children = + self.directory_tree(child_path, next_depth, max_files, current_count)?; + json_entry + .as_object_mut() + .unwrap() + .insert("children".to_string(), child_children); + } + children.push(json_entry); + } + } + Ok(Value::Array(children)) + } + pub fn create_unified_diff( &self, original_content: &str, @@ -519,8 +599,8 @@ impl FileSystemService { let patch = diff .unified_diff() .header( - format!("{}\toriginal", file_name).as_str(), - format!("{}\tmodified", file_name).as_str(), + format!("{file_name}\toriginal").as_str(), + format!("{file_name}\tmodified").as_str(), ) .context_radius(4) .to_string(); diff --git a/src/fs_service/utils.rs b/src/fs_service/utils.rs index 3bd11a7..189c69d 100644 --- a/src/fs_service/utils.rs +++ b/src/fs_service/utils.rs @@ -82,7 +82,7 @@ pub fn format_bytes(bytes: u64) -> String { return format!("{:.2} {}", bytes as f64 / threshold as f64, unit); } } - format!("{} bytes", bytes) + format!("{bytes} bytes") } pub async fn write_zip_entry( diff --git a/src/handler.rs b/src/handler.rs index f8637a7..55b9b0d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -77,7 +77,7 @@ impl ServerHandler for MyServerHandler { ) -> std::result::Result { runtime .set_client_details(initialize_request.params.clone()) - .map_err(|err| RpcError::internal_error().with_message(format!("{}", err)))?; + .map_err(|err| RpcError::internal_error().with_message(format!("{err}")))?; let mut server_info = runtime.server_info().to_owned(); // Provide compatibility for clients using older MCP protocol versions. diff --git a/src/tools/directory_tree.rs b/src/tools/directory_tree.rs index 21dce17..3d7c0cd 100644 --- a/src/tools/directory_tree.rs +++ b/src/tools/directory_tree.rs @@ -1,9 +1,8 @@ -use std::path::Path; - use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use serde_json::json; +use crate::error::ServiceError; use crate::fs_service::FileSystemService; #[mcp_tool( @@ -11,6 +10,8 @@ use crate::fs_service::FileSystemService; description = concat!("Get a recursive tree view of files and directories as a JSON structure. ", "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. ", "Files have no children array, while directories always have a children array (which may be empty). ", + "If the 'max_depth' parameter is provided, the traversal will be limited to the specified depth. ", + "As a result, the returned directory structure may be incomplete or provide a skewed representation of the full directory tree, since deeper-level files and subdirectories beyond the specified depth will be excluded. ", "The output is formatted with 2-space indentation for readability. Only works within allowed directories."), destructive_hint = false, idempotent_hint = false, @@ -21,28 +22,31 @@ use crate::fs_service::FileSystemService; pub struct DirectoryTreeTool { /// The root path of the directory tree to generate. pub path: String, + /// Limits the depth of directory traversal + pub max_depth: Option, } impl DirectoryTreeTool { pub async fn run_tool( params: Self, context: &FileSystemService, ) -> std::result::Result { + let mut entry_counter: usize = 0; let entries = context - .list_directory(Path::new(¶ms.path)) - .await + .directory_tree( + params.path, + params.max_depth.map(|v| v as usize), + None, + &mut entry_counter, + ) .map_err(CallToolError::new)?; - let json_tree: Vec = entries - .iter() - .map(|entry| { - json!({ - "name": entry.file_name().to_str().unwrap_or_default(), - "type": if entry.path().is_dir(){"directory"}else{"file"} - }) - }) - .collect(); - let json_str = - serde_json::to_string_pretty(&json!(json_tree)).map_err(CallToolError::new)?; + if entry_counter == 0 { + return Err(CallToolError::new(ServiceError::FromString( + "Could not find any entries".to_string(), + ))); + } + + let json_str = serde_json::to_string_pretty(&json!(entries)).map_err(CallToolError::new)?; Ok(CallToolResult::text_content(json_str, None)) } } diff --git a/src/tools/read_multiple_files.rs b/src/tools/read_multiple_files.rs index 666e97b..d0f079f 100644 --- a/src/tools/read_multiple_files.rs +++ b/src/tools/read_multiple_files.rs @@ -40,8 +40,8 @@ impl ReadMultipleFilesTool { .map_err(CallToolError::new); content.map_or_else( - |err| format!("{}: Error - {}", path, err), - |value| format!("{}:\n{}\n", path, value), + |err| format!("{path}: Error - {err}"), + |value| format!("{path}:\n{value}\n"), ) } }) From 15121c8d1605366ea5185f6a9e2ffd7036693d13 Mon Sep 17 00:00:00 2001 From: Ali Hashemi <14126952+hashemix@users.noreply.github.com> Date: Fri, 4 Jul 2025 21:35:04 -0300 Subject: [PATCH 3/5] feat: add list_directory_with_sizes MCP Tool for Directory Listing with File Sizes (#27) * feat: add new list_directory_with_sizes tool * feat: add list_directory_with_sizes * chore: fix clippy warnings --- src/handler.rs | 3 + src/tools.rs | 6 +- src/tools/list_directory_with_sizes.rs | 91 ++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/tools/list_directory_with_sizes.rs 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)) + } +} From cd6af1bfc14dab4b2ba68b014be860c8e9668667 Mon Sep 17 00:00:00 2001 From: Ali Hashemi <14126952+hashemix@users.noreply.github.com> Date: Fri, 4 Jul 2025 22:20:43 -0300 Subject: [PATCH 4/5] feat!: upgrade to latest MCP protocol (2025-06-18) (#29) * feat: upgrade to protocol version 2025-06-18 * choreL update tools annotations * chore: update tests --- Cargo.lock | 56 ++++---------------------- Cargo.toml | 4 +- src/server.rs | 1 + src/tools/create_directory.rs | 7 ++-- src/tools/directory_tree.rs | 6 ++- src/tools/edit_file.rs | 4 +- src/tools/get_file_info.rs | 6 ++- src/tools/list_allowed_directories.rs | 6 ++- src/tools/list_directory.rs | 6 ++- src/tools/list_directory_with_sizes.rs | 6 ++- src/tools/move_file.rs | 7 ++-- src/tools/read_files.rs | 6 ++- src/tools/read_multiple_files.rs | 6 ++- src/tools/search_file.rs | 6 ++- src/tools/search_files_content.rs | 7 ++-- src/tools/write_file.rs | 11 +++-- src/tools/zip_unzip.rs | 16 ++++++-- tests/test_tools.rs | 8 ++-- 18 files changed, 90 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef86be0..eeb28c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,12 +389,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "futures" version = "0.3.31" @@ -630,39 +624,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "http", - "http-body", - "tokio", -] - [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1002,9 +963,9 @@ dependencies = [ [[package]] name = "rust-mcp-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375eba9388d9d5ffc8951e5f25296f9739be4e5700375ac7c74ad957a903b92b" +checksum = "5a495ee12964fa392044792db4e9da78c94756e186a5712e5d7e625c143419ff" dependencies = [ "proc-macro2", "quote", @@ -1015,9 +976,9 @@ dependencies = [ [[package]] name = "rust-mcp-schema" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a794de25669a2d21c5074ec5082f74f5e88863a112339fe90264d9e480b0ee8b" +checksum = "fec1ff3507a619b9945f60a94dac448541aef8c9803aa6192c30f4a932cb1499" dependencies = [ "serde", "serde_json", @@ -1025,13 +986,12 @@ dependencies = [ [[package]] name = "rust-mcp-sdk" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bed508dfa638d89b1a797ddfe1b4b4022db834036b3f1bcdf2909cea5be14a2" +checksum = "1fb7d9a1fdcf02efee5cd21a568f31c819b7f2d767c42b37cb79aab302ac5b35" dependencies = [ "async-trait", "futures", - "hyper", "rust-mcp-macros", "rust-mcp-schema", "rust-mcp-transport", @@ -1044,9 +1004,9 @@ dependencies = [ [[package]] name = "rust-mcp-transport" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6318216799507090512e6ffa7ea7fd26c6f2098c655cb72942dd5e221cfdea3d" +checksum = "d7f5468b8a00e800c3d708321216439ee1ad433592d725df93668bff75436348" dependencies = [ "async-trait", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 9fc5234..44084c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,10 +15,10 @@ license = false eula = false [dependencies] -rust-mcp-sdk = { version = "0.4", default-features = false, features = [ +rust-mcp-sdk = { version = "0.5", default-features = false, features = [ "server", "macros", - "2025_03_26", + "2025_06_18", ] } thiserror = { version = "2.0" } diff --git a/src/server.rs b/src/server.rs index 8ebaa88..af7151e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,6 +11,7 @@ pub fn server_details() -> InitializeResult { server_info: Implementation { name: "rust-mcp-filesystem".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), + title:Some("Filesystem MCP Server: fast and efficient tools for managing filesystem operations.".to_string()) }, capabilities: ServerCapabilities { experimental: None, diff --git a/src/tools/create_directory.rs b/src/tools/create_directory.rs index e84fe9b..48f01ff 100644 --- a/src/tools/create_directory.rs +++ b/src/tools/create_directory.rs @@ -1,12 +1,14 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "create_directory", + title="Create Directory", description = concat!("Create a new directory or ensure a directory exists. ", "Can create multiple nested directories in one operation. ", "If the directory already exists, this operation will succeed silently. ", @@ -33,9 +35,8 @@ impl CreateDirectoryTool { .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content( + Ok(CallToolResult::text_content(vec![TextContent::from( format!("Successfully created directory {}", ¶ms.path), - None, - )) + )])) } } diff --git a/src/tools/directory_tree.rs b/src/tools/directory_tree.rs index 3d7c0cd..8d2491e 100644 --- a/src/tools/directory_tree.rs +++ b/src/tools/directory_tree.rs @@ -1,4 +1,5 @@ use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use serde_json::json; @@ -7,6 +8,7 @@ use crate::fs_service::FileSystemService; #[mcp_tool( name = "directory_tree", + title= "Directory Tree", description = concat!("Get a recursive tree view of files and directories as a JSON structure. ", "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. ", "Files have no children array, while directories always have a children array (which may be empty). ", @@ -47,6 +49,8 @@ impl DirectoryTreeTool { } let json_str = serde_json::to_string_pretty(&json!(entries)).map_err(CallToolError::new)?; - Ok(CallToolResult::text_content(json_str, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + json_str, + )])) } } diff --git a/src/tools/edit_file.rs b/src/tools/edit_file.rs index 502a3fb..a29311c 100644 --- a/src/tools/edit_file.rs +++ b/src/tools/edit_file.rs @@ -1,6 +1,7 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; @@ -18,6 +19,7 @@ pub struct EditOperation { #[mcp_tool( name = "edit_file", + title="Edit File", description = concat!("Make line-based edits to a text file. ", "Each edit replaces exact line sequences with new content. ", "Returns a git-style diff showing the changes made. ", @@ -53,6 +55,6 @@ impl EditFileTool { .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content(diff, None)) + Ok(CallToolResult::text_content(vec![TextContent::from(diff)])) } } diff --git a/src/tools/get_file_info.rs b/src/tools/get_file_info.rs index fcb48cf..55c3a03 100644 --- a/src/tools/get_file_info.rs +++ b/src/tools/get_file_info.rs @@ -1,12 +1,14 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "get_file_info", + title="Get File Info", description = concat!("Retrieve detailed metadata about a file or directory. ", "Returns comprehensive information including size, creation time, ", "last modified time, permissions, and type. ", @@ -32,6 +34,8 @@ impl GetFileInfoTool { .get_file_stats(Path::new(¶ms.path)) .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content(stats.to_string(), None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + stats.to_string(), + )])) } } diff --git a/src/tools/list_allowed_directories.rs b/src/tools/list_allowed_directories.rs index 4e46d34..36e52c2 100644 --- a/src/tools/list_allowed_directories.rs +++ b/src/tools/list_allowed_directories.rs @@ -1,10 +1,12 @@ use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "list_allowed_directories", + title="List Allowed Directories", description = concat!("Returns a list of directories that the server has permission ", "to access Subdirectories within these allowed directories are also accessible. ", "Use this to identify which directories and their nested paths are available ", @@ -31,6 +33,8 @@ impl ListAllowedDirectoriesTool { .collect::>() .join("\n") ); - Ok(CallToolResult::text_content(result, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + result, + )])) } } diff --git a/src/tools/list_directory.rs b/src/tools/list_directory.rs index 5446ef8..cb0b95e 100644 --- a/src/tools/list_directory.rs +++ b/src/tools/list_directory.rs @@ -1,12 +1,14 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "list_directory", + title="List Directory", description = concat!("Get a detailed listing of all files and directories in a specified path. ", "Results clearly distinguish between files and directories with [FILE] and [DIR] ", "prefixes. This tool is essential for understanding directory structure and ", @@ -47,6 +49,8 @@ impl ListDirectoryTool { }) .collect(); - Ok(CallToolResult::text_content(formatted.join("\n"), None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + formatted.join("\n"), + )])) } } diff --git a/src/tools/list_directory_with_sizes.rs b/src/tools/list_directory_with_sizes.rs index e36801d..8929cf9 100644 --- a/src/tools/list_directory_with_sizes.rs +++ b/src/tools/list_directory_with_sizes.rs @@ -1,4 +1,5 @@ use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use std::fmt::Write; use std::path::Path; @@ -8,6 +9,7 @@ use crate::fs_service::FileSystemService; #[mcp_tool( name = "list_directory_with_sizes", + title="List Directory With File 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 " , @@ -86,6 +88,8 @@ impl ListDirectoryWithSizesTool { .format_directory_entries(entries) .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content(output, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + output, + )])) } } diff --git a/src/tools/move_file.rs b/src/tools/move_file.rs index b3a2cd1..adafaa7 100644 --- a/src/tools/move_file.rs +++ b/src/tools/move_file.rs @@ -1,12 +1,14 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "move_file", + title="Move File", description = concat!("Move or rename files and directories. Can move files between directories ", "and rename them in a single operation. If the destination exists, the ", "operation will fail. Works across different directories and can be used ", @@ -35,12 +37,11 @@ impl MoveFileTool { .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content( + Ok(CallToolResult::text_content(vec![TextContent::from( format!( "Successfully moved {} to {}", ¶ms.source, ¶ms.destination ), - None, - )) + )])) } } diff --git a/src/tools/read_files.rs b/src/tools/read_files.rs index fdd5b67..e4bc34a 100644 --- a/src/tools/read_files.rs +++ b/src/tools/read_files.rs @@ -1,12 +1,14 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "read_file", + title="Read File", description = concat!("Read the complete contents of a file from the file system. ", "Handles various text encodings and provides detailed error messages if the ", "file cannot be read. Use this tool when you need to examine the contents of ", @@ -32,6 +34,8 @@ impl ReadFileTool { .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content(content, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + content, + )])) } } diff --git a/src/tools/read_multiple_files.rs b/src/tools/read_multiple_files.rs index d0f079f..6df881f 100644 --- a/src/tools/read_multiple_files.rs +++ b/src/tools/read_multiple_files.rs @@ -2,12 +2,14 @@ use std::path::Path; use futures::future::join_all; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "read_multiple_files", + title="Read Multiple Files", description = concat!("Read the contents of multiple files simultaneously. ", "This is more efficient than reading files one by one when you need to analyze ", "or compare multiple files. Each file's content is returned with its ", @@ -49,6 +51,8 @@ impl ReadMultipleFilesTool { let contents = join_all(content_futures).await; - Ok(CallToolResult::text_content(contents.join("\n---\n"), None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + contents.join("\n---\n"), + )])) } } diff --git a/src/tools/search_file.rs b/src/tools/search_file.rs index 4df8f44..dfee718 100644 --- a/src/tools/search_file.rs +++ b/src/tools/search_file.rs @@ -1,11 +1,13 @@ use std::path::Path; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "search_files", + title="Search Files", description = concat!("Recursively search for files and directories matching a pattern. ", "Searches through all subdirectories from the starting path. The search ", "is case-insensitive and matches partial names. Returns full paths to all ", @@ -49,6 +51,8 @@ impl SearchFilesTool { } else { "No matches found".to_string() }; - Ok(CallToolResult::text_content(result, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + result, + )])) } } diff --git a/src/tools/search_files_content.rs b/src/tools/search_files_content.rs index a8911ef..e1e50e9 100644 --- a/src/tools/search_files_content.rs +++ b/src/tools/search_files_content.rs @@ -1,10 +1,12 @@ use crate::error::ServiceError; use crate::fs_service::{FileSearchResult, FileSystemService}; use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use std::fmt::Write; #[mcp_tool( name = "search_files_content", + title="Move Files Content", description = concat!("Searches for text or regex patterns in the content of files matching matching a GLOB pattern.", "Returns detailed matches with file path, line number, column number and a preview of matched text.", "By default, it performs a literal text search; if the 'is_regex' parameter is set to true, it performs a regular expression (regex) search instead.", @@ -76,10 +78,9 @@ impl SearchFilesContentTool { ServiceError::FromString("No matches found in the files content.".into()), ))); } - Ok(CallToolResult::text_content( + Ok(CallToolResult::text_content(vec![TextContent::from( params.format_result(results), - None, - )) + )])) } Err(err) => Ok(CallToolResult::with_error(CallToolError::new(err))), } diff --git a/src/tools/write_file.rs b/src/tools/write_file.rs index f83c398..f323950 100644 --- a/src/tools/write_file.rs +++ b/src/tools/write_file.rs @@ -1,4 +1,7 @@ -use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::{ + macros::{mcp_tool, JsonSchema}, + schema::TextContent, +}; use std::path::Path; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; @@ -6,6 +9,7 @@ use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "write_file", + title="Write File", description = concat!("Create a new file or completely overwrite an existing file with new content. ", "Use with caution as it will overwrite existing files without warning. ", "Handles text content with proper encoding. Only works within allowed directories."), @@ -32,9 +36,8 @@ impl WriteFileTool { .await .map_err(CallToolError::new)?; - Ok(CallToolResult::text_content( + Ok(CallToolResult::text_content(vec![TextContent::from( format!("Successfully wrote to {}", ¶ms.path), - None, - )) + )])) } } diff --git a/src/tools/zip_unzip.rs b/src/tools/zip_unzip.rs index 5e7609f..80a3e7c 100644 --- a/src/tools/zip_unzip.rs +++ b/src/tools/zip_unzip.rs @@ -1,10 +1,12 @@ use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; +use rust_mcp_sdk::schema::TextContent; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; use crate::fs_service::FileSystemService; #[mcp_tool( name = "zip_files", + title="Zip Files", description = concat!("Creates a ZIP archive by compressing files. ", "It takes a list of files to compress and a target path for the resulting ZIP file. ", "Both the source files and the target ZIP file should reside within allowed directories."), @@ -31,12 +33,15 @@ impl ZipFilesTool { .await .map_err(CallToolError::new)?; //TODO: return resource? - Ok(CallToolResult::text_content(result_content, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + result_content, + )])) } } #[mcp_tool( name = "unzip_file", + title = "Unzip Files", description = "Extracts the contents of a ZIP archive to a specified target directory. It takes a source ZIP file path and a target extraction directory. The tool decompresses all files and directories stored in the ZIP, recreating their structure in the target location. @@ -60,12 +65,15 @@ impl UnzipFileTool { .await .map_err(CallToolError::new)?; //TODO: return resource? - Ok(CallToolResult::text_content(result_content, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + result_content, + )])) } } #[mcp_tool( name = "zip_directory", + title = "Zip Directory", description = "Creates a ZIP archive by compressing a directory , including files and subdirectories matching a specified glob pattern. It takes a path to the folder and a glob pattern to identify files to compress and a target path for the resulting ZIP file. Both the source directory and the target ZIP file should reside within allowed directories." @@ -91,6 +99,8 @@ impl ZipDirectoryTool { .await .map_err(CallToolError::new)?; //TODO: return resource? - Ok(CallToolResult::text_content(result_content, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + result_content, + )])) } } diff --git a/tests/test_tools.rs b/tests/test_tools.rs index 9cccee9..8c46611 100644 --- a/tests/test_tools.rs +++ b/tests/test_tools.rs @@ -3,7 +3,7 @@ pub mod common; use common::setup_service; use rust_mcp_filesystem::tools::*; -use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResultContentItem}; +use rust_mcp_sdk::schema::{schema_utils::CallToolError, ContentBlock}; use std::fs; #[tokio::test] @@ -22,7 +22,7 @@ async fn test_create_directory_new_directory() { let content = call_result.content.first().unwrap(); match content { - CallToolResultContentItem::TextContent(text_content) => { + ContentBlock::TextContent(text_content) => { assert_eq!( text_content.text, format!( @@ -54,7 +54,7 @@ async fn test_create_directory_existing_directory() { let content = call_result.content.first().unwrap(); match content { - CallToolResultContentItem::TextContent(text_content) => { + ContentBlock::TextContent(text_content) => { assert_eq!( text_content.text, format!( @@ -85,7 +85,7 @@ async fn test_create_directory_nested() { let content = call_result.content.first().unwrap(); match content { - CallToolResultContentItem::TextContent(text_content) => { + ContentBlock::TextContent(text_content) => { assert_eq!( text_content.text, format!( From c6866be385b4b5693dde32246d59db48c56fb7e3 Mon Sep 17 00:00:00 2001 From: Ali Hashemi <14126952+hashemix@users.noreply.github.com> Date: Fri, 4 Jul 2025 22:22:28 -0300 Subject: [PATCH 5/5] chore: release (#28) * chore: release * docs/capabilities.md via mcp-discovery --------- Co-authored-by: GitHub Action --- .release-manifest.json | 2 +- CHANGELOG.md | 17 ++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- docs/_coverpage.md | 2 +- docs/capabilities.md | 95 ++++++++++++++++++++++++------------------ docs/guide/install.md | 28 ++++++------- docs/quickstart.md | 28 ++++++------- 8 files changed, 103 insertions(+), 73 deletions(-) diff --git a/.release-manifest.json b/.release-manifest.json index a397a7b..6c28b67 100644 --- a/.release-manifest.json +++ b/.release-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.10" + ".": "0.2.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1d6be..d452ce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [0.2.0](https://github.com/rust-mcp-stack/rust-mcp-filesystem/compare/v0.1.10...v0.2.0) (2025-07-05) + + +### ⚠ BREAKING CHANGES + +* upgrade to latest MCP protocol (2025-06-18) ([#29](https://github.com/rust-mcp-stack/rust-mcp-filesystem/issues/29)) + +### πŸš€ Features + +* Add list_directory_with_sizes MCP Tool for Directory Listing with File Sizes ([#27](https://github.com/rust-mcp-stack/rust-mcp-filesystem/issues/27)) ([15121c8](https://github.com/rust-mcp-stack/rust-mcp-filesystem/commit/15121c8d1605366ea5185f6a9e2ffd7036693d13)) +* Upgrade to latest MCP protocol (2025-06-18) ([#29](https://github.com/rust-mcp-stack/rust-mcp-filesystem/issues/29)) ([cd6af1b](https://github.com/rust-mcp-stack/rust-mcp-filesystem/commit/cd6af1bfc14dab4b2ba68b014be860c8e9668667)) + + +### πŸ› Bug Fixes + +* Directory tree tool result ([#26](https://github.com/rust-mcp-stack/rust-mcp-filesystem/issues/26)) ([01f956e](https://github.com/rust-mcp-stack/rust-mcp-filesystem/commit/01f956efdde5fdd0e5fd14f30e4ebdac53d728f7)) + ## [0.1.10](https://github.com/rust-mcp-stack/rust-mcp-filesystem/compare/v0.1.9...v0.1.10) (2025-06-18) diff --git a/Cargo.lock b/Cargo.lock index eeb28c3..56ed8ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -939,7 +939,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rust-mcp-filesystem" -version = "0.1.10" +version = "0.2.0" dependencies = [ "async-trait", "async_zip", diff --git a/Cargo.toml b/Cargo.toml index 44084c4..4177463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-mcp-filesystem" -version = "0.1.10" +version = "0.2.0" edition = "2021" repository = "https://github.com/rust-mcp-stack/rust-mcp-filesystem" authors = ["Ali Hashemi"] diff --git a/docs/_coverpage.md b/docs/_coverpage.md index 7ab0b5d..890c6fc 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -6,7 +6,7 @@ -# Rust MCP FileSystem (v0.1.10) +# Rust MCP FileSystem (v0.2.0) diff --git a/docs/capabilities.md b/docs/capabilities.md index 8a56775..b2cc04b 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -1,10 +1,10 @@ # Capabilities -## rust-mcp-filesystem 0.1.10 -| 🟒 Tools (15) | πŸ”΄ Prompts | πŸ”΄ Resources | πŸ”΄ Logging | πŸ”΄ Experimental | +## rust-mcp-filesystem 0.2.0 +| 🟒 Tools (16) | πŸ”΄ Prompts | πŸ”΄ Resources | πŸ”΄ Logging | πŸ”΄ Experimental | | --- | --- | --- | --- | --- | -## πŸ› οΈ Tools (15) +## πŸ› οΈ Tools (16) @@ -24,7 +24,7 @@ @@ -33,10 +33,11 @@ - + @@ -48,9 +49,9 @@ @@ -62,7 +63,7 @@ @@ -85,127 +86,139 @@ + + + + + + - + - + - + - + - + - + - + - + @@ -215,5 +228,5 @@ -β—Ύ generated by [mcp-discovery](https://github.com/rust-mcp-stack/mcp-discovery) +β—Ύ generated by [mcp-discovery](https://github.com/rust-mcp-stack/mcp-discovery) \ No newline at end of file diff --git a/docs/guide/install.md b/docs/guide/install.md index 47c9557..e4833a0 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -7,13 +7,13 @@ ```sh -curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.1.10/rust-mcp-filesystem-installer.sh | sh +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.2.0/rust-mcp-filesystem-installer.sh | sh ``` #### **PowerShell script** ```sh -powershell -ExecutionPolicy Bypass -c "irm https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.1.10/rust-mcp-filesystem-installer.ps1 | iex" +powershell -ExecutionPolicy Bypass -c "irm https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.2.0/rust-mcp-filesystem-installer.ps1 | iex" ``` @@ -38,78 +38,78 @@ brew install rust-mcp-stack/tap/rust-mcp-filesystem diff --git a/docs/quickstart.md b/docs/quickstart.md index b05275d..b14ddf1 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -7,13 +7,13 @@ ```sh -curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.1.10/rust-mcp-filesystem-installer.sh | sh +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.2.0/rust-mcp-filesystem-installer.sh | sh ``` #### **PowerShell script** ```sh -powershell -ExecutionPolicy Bypass -c "irm https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.1.10/rust-mcp-filesystem-installer.ps1 | iex" +powershell -ExecutionPolicy Bypass -c "irm https://github.com/rust-mcp-stack/rust-mcp-filesystem/releases/download/v0.2.0/rust-mcp-filesystem-installer.ps1 | iex" ``` @@ -38,78 +38,78 @@ brew install rust-mcp-stack/tap/rust-mcp-filesystem
Create a new directory or ensure a directory exists. Can create multiple nested directories in one operation. If the directory already exists, this operation will succeed silently. Perfect for setting up directory structures for projects or ensuring required paths exist. Only works within allowed directories.
    -
  • path : string
  • +
  • path : string
directory_tree Get a recursive tree view of files and directories as a JSON structure. Each entry includes name, type (file/directory), and children for directories. Files have no children array, while directories always have a children array (which may be empty). The output is formatted with 2-space indentation for readability. Only works within allowed directories.Get a recursive tree view of files and directories as a JSON structure. Each entry includes name, type (file/directory), and children for directories. Files have no children array, while directories always have a children array (which may be empty). If the max_depth parameter is provided, the traversal will be limited to the specified depth. As a result, the returned directory structure may be incomplete or provide a skewed representation of the full directory tree, since deeper-level files and subdirectories beyond the specified depth will be excluded. The output is formatted with 2-space indentation for readability. Only works within allowed directories.
    -
  • path : string
  • +
  • max_depth : number
  • +
  • path : string
Make line-based edits to a text file. Each edit replaces exact line sequences with new content. Returns a git-style diff showing the changes made. Only works within allowed directories.
    -
  • dryRun : boolean
  • -
  • edits : {newText : string, oldText : string} [ ]
  • -
  • path : string
  • +
  • dryRun : boolean
  • +
  • edits : {newText : string, oldText : string} [ ]
  • +
  • path : string
Retrieve detailed metadata about a file or directory. Returns comprehensive information including size, creation time, last modified time, permissions, and type. This tool is perfect for understanding file characteristics without reading the actual content. Only works within allowed directories.
    -
  • path : string
  • +
  • path : string
Get a detailed listing of all files and directories in a specified path. Results clearly distinguish between files and directories with FILE and DIR prefixes. This tool is essential for understanding directory structure and finding specific files within a directory. Only works within allowed directories.
    -
  • path : string
  • +
  • path : string
7. + list_directory_with_sizes + 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. +
    +
  • path : string
  • +
+
8. move_file Move or rename files and directories. Can move files between directories and rename them in a single operation. If the destination exists, the operation will fail. Works across different directories and can be used for simple renaming within the same directory. Both source and destination must be within allowed directories.
    -
  • destination : string
  • -
  • source : string
  • +
  • destination : string
  • +
  • source : string
8.9. read_file Read the complete contents of a file from the file system. Handles various text encodings and provides detailed error messages if the file cannot be read. Use this tool when you need to examine the contents of a single file. Only works within allowed directories.
    -
  • path : string
  • +
  • path : string
9.10. read_multiple_files Read the contents of multiple files simultaneously. This is more efficient than reading files one by one when you need to analyze or compare multiple files. Each file's content is returned with its path as a reference. Failed reads for individual files won't stop the entire operation. Only works within allowed directories.
    -
  • paths : string [ ]
  • +
  • paths : string [ ]
10.11. search_files Recursively search for files and directories matching a pattern. Searches through all subdirectories from the starting path. The search is case-insensitive and matches partial names. Returns full paths to all matching items. Great for finding files when you don't know their exact location. Only searches within allowed directories.
    -
  • excludePatterns : string [ ]
  • -
  • path : string
  • -
  • pattern : string
  • +
  • excludePatterns : string [ ]
  • +
  • path : string
  • +
  • pattern : string
11.12. search_files_content Searches for text or regex patterns in the content of files matching matching a GLOB pattern.Returns detailed matches with file path, line number, column number and a preview of matched text.By default, it performs a literal text search; if the is_regex parameter is set to true, it performs a regular expression (regex) search instead.Ideal for finding specific code, comments, or text when you don’t know their exact location.
    -
  • excludePatterns : string [ ]
  • -
  • is_regex : boolean
  • -
  • path : string
  • -
  • pattern : string
  • -
  • query : string
  • +
  • excludePatterns : string [ ]
  • +
  • is_regex : boolean
  • +
  • path : string
  • +
  • pattern : string
  • +
  • query : string
12.13. unzip_file Extracts the contents of a ZIP archive to a specified target directory.
It takes a source ZIP file path and a target extraction directory.
The tool decompresses all files and directories stored in the ZIP, recreating their structure in the target location.
Both the source ZIP file and the target directory should reside within allowed directories.
    -
  • target_path : string
  • -
  • zip_file : string
  • +
  • target_path : string
  • +
  • zip_file : string
13.14. write_file Create a new file or completely overwrite an existing file with new content. Use with caution as it will overwrite existing files without warning. Handles text content with proper encoding. Only works within allowed directories.
    -
  • content : string
  • -
  • path : string
  • +
  • content : string
  • +
  • path : string
14.15. zip_directory Creates a ZIP archive by compressing a directory , including files and subdirectories matching a specified glob pattern.
It takes a path to the folder and a glob pattern to identify files to compress and a target path for the resulting ZIP file.
Both the source directory and the target ZIP file should reside within allowed directories.
    -
  • input_directory : string
  • -
  • pattern : string
  • -
  • target_zip_file : string
  • +
  • input_directory : string
  • +
  • pattern : string
  • +
  • target_zip_file : string
15.16. zip_files Creates a ZIP archive by compressing files. It takes a list of files to compress and a target path for the resulting ZIP file. Both the source files and the target ZIP file should reside within allowed directories.
    -
  • input_files : string [ ]
  • -
  • target_zip_file : string
  • +
  • input_files : string [ ]
  • +
  • target_zip_file : string
- rust-mcp-filesystem-aarch64-apple-darwin.tar.gz + rust-mcp-filesystem-aarch64-apple-darwin.tar.gz Apple Silicon macOS - checksum + checksum
- rust-mcp-filesystem-x86_64-apple-darwin.tar.gz + rust-mcp-filesystem-x86_64-apple-darwin.tar.gz Intel macOS - checksum + checksum
- rust-mcp-filesystem-x86_64-pc-windows-msvc.zip + rust-mcp-filesystem-x86_64-pc-windows-msvc.zip x64 Windows (zip) - checksum + checksum
- rust-mcp-filesystem-x86_64-pc-windows-msvc.msi + rust-mcp-filesystem-x86_64-pc-windows-msvc.msi x64 Windows (msi) - checksum + checksum
- rust-mcp-filesystem-aarch64-unknown-linux-gnu.tar.gz + rust-mcp-filesystem-aarch64-unknown-linux-gnu.tar.gz ARM64 Linux - checksum + checksum
- rust-mcp-filesystem-x86_64-unknown-linux-gnu.tar.gz + rust-mcp-filesystem-x86_64-unknown-linux-gnu.tar.gz x64 Linux - checksum + checksum
- rust-mcp-filesystem-aarch64-apple-darwin.tar.gz + rust-mcp-filesystem-aarch64-apple-darwin.tar.gz Apple Silicon macOS - checksum + checksum
- rust-mcp-filesystem-x86_64-apple-darwin.tar.gz + rust-mcp-filesystem-x86_64-apple-darwin.tar.gz Intel macOS - checksum + checksum
- rust-mcp-filesystem-x86_64-pc-windows-msvc.zip + rust-mcp-filesystem-x86_64-pc-windows-msvc.zip x64 Windows (zip) - checksum + checksum
- rust-mcp-filesystem-x86_64-pc-windows-msvc.msi + rust-mcp-filesystem-x86_64-pc-windows-msvc.msi x64 Windows (msi) - checksum + checksum
- rust-mcp-filesystem-aarch64-unknown-linux-gnu.tar.gz + rust-mcp-filesystem-aarch64-unknown-linux-gnu.tar.gz ARM64 Linux - checksum + checksum
- rust-mcp-filesystem-x86_64-unknown-linux-gnu.tar.gz + rust-mcp-filesystem-x86_64-unknown-linux-gnu.tar.gz x64 Linux - checksum + checksum