Skip to content

Commit 3ca810a

Browse files
authored
fix: ignore client root change notification when it is not enabled by server (#65)
* fix: duplicate tool name * fix: docker image * fix: respect mcp root command argument * fix: dependencies * chore: update toolchain
1 parent 45e8519 commit 3ca810a

File tree

8 files changed

+196
-333
lines changed

8 files changed

+196
-333
lines changed

Cargo.lock

Lines changed: 144 additions & 291 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-toolchain.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[toolchain]
2-
channel = "1.88.0"
2+
channel = "1.89.0"
33
components = ["rustfmt", "clippy"]

src/fs_service.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -792,10 +792,10 @@ impl FileSystemService {
792792
*current_count += 1;
793793

794794
// Check if we've exceeded max_files (if set)
795-
if let Some(max) = max_files {
796-
if *current_count > max {
797-
continue; // Skip this entry but continue processing others
798-
}
795+
if let Some(max) = max_files
796+
&& *current_count > max
797+
{
798+
continue; // Skip this entry but continue processing others
799799
}
800800

801801
let mut json_entry = json!({
@@ -1463,10 +1463,8 @@ impl FileSystemService {
14631463
.filter_map(|e| e.ok())
14641464
.all(|e| !e.file_type().is_file() || is_system_metadata_file(e.file_name())); // Directory is empty if no files are found in it or subdirs, ".DS_Store" will be ignores on Mac
14651465

1466-
if is_empty {
1467-
if let Some(path_str) = entry.path().to_str() {
1468-
empty_dirs.push(path_str.to_string());
1469-
}
1466+
if is_empty && let Some(path_str) = entry.path().to_str() {
1467+
empty_dirs.push(path_str.to_string());
14701468
}
14711469
}
14721470

@@ -1505,13 +1503,13 @@ impl FileSystemService {
15051503
.filter(|e| e.file_type().is_file()); // Only files
15061504

15071505
for entry in entries {
1508-
if let Ok(metadata) = entry.metadata() {
1509-
if let Some(path_str) = entry.path().to_str() {
1510-
size_map
1511-
.entry(metadata.len())
1512-
.or_default()
1513-
.push(path_str.to_string());
1514-
}
1506+
if let Ok(metadata) = entry.metadata()
1507+
&& let Some(path_str) = entry.path().to_str()
1508+
{
1509+
size_map
1510+
.entry(metadata.len())
1511+
.or_default()
1512+
.push(path_str.to_string());
15151513
}
15161514
}
15171515

src/fs_service/utils.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ pub fn normalize_path(path: &Path) -> PathBuf {
6666
}
6767

6868
pub fn expand_home(path: PathBuf) -> PathBuf {
69-
if let Some(home_dir) = home_dir() {
70-
if path.starts_with("~") {
71-
let stripped_path = path.strip_prefix("~").unwrap_or(&path);
72-
return home_dir.join(stripped_path);
73-
}
69+
if let Some(home_dir) = home_dir()
70+
&& path.starts_with("~")
71+
{
72+
let stripped_path = path.strip_prefix("~").unwrap_or(&path);
73+
return home_dir.join(stripped_path);
7474
}
7575
path
7676
}

src/handler.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,29 @@ impl FileSystemHandler {
7070
}
7171

7272
pub(crate) async fn update_allowed_directories(&self, runtime: Arc<dyn McpServer>) {
73-
// if client does not support roots
73+
// return if roots_support is not enabled
74+
if !self.mcp_roots_support {
75+
return;
76+
}
77+
7478
let allowed_directories = self.fs_service.allowed_directories().await;
79+
// if client does NOT support roots
7580
if !runtime.client_supports_root_list().unwrap_or(false) {
81+
// use allowed directories from command line
7682
if !allowed_directories.is_empty() {
77-
let _ = runtime.stderr_message(format!("Client does not support MCP Roots, using allowed directories set from server args:\n{}", allowed_directories
78-
.iter()
79-
.map(|p| p.display().to_string())
80-
.collect::<Vec<String>>()
81-
.join(",\n"))).await;
83+
// display message only if mcp_roots_support is enabled, otherwise this message will be redundant
84+
if self.mcp_roots_support {
85+
let _ = runtime.stderr_message("Client does not support MCP Roots. Allowed directories passed from command-line will be used.".to_string()).await;
86+
}
8287
} else {
83-
// let message = "Server cannot operate: No allowed directories available. Server was started without command-line directories and client either does not support MCP roots protocol or provided empty roots. Please either: 1) Start server with directory arguments, or 2) Use a client that supports MCP roots protocol and provides valid root directories.";
88+
// root lists not supported AND allowed directories are empty
8489
let message = "Server cannot operate: No allowed directories available. Server was started without command-line directories and client does not support MCP roots protocol. Please either: 1) Start server with directory arguments, or 2) Use a client that supports MCP roots protocol and provides valid root directories.";
8590
let _ = runtime.stderr_message(message.to_string()).await;
91+
std::process::exit(1); // exit the server
8692
}
8793
} else {
94+
// client supports roots
8895
let fs_service = self.fs_service.clone();
89-
let mcp_roots_support = self.mcp_roots_support;
9096
// retrieve roots from the client and update the allowed directories accordingly
9197
let roots = match runtime.clone().list_roots(None).await {
9298
Ok(roots_result) => roots_result.roots,
@@ -111,7 +117,7 @@ impl FileSystemHandler {
111117
}
112118
};
113119

114-
if valid_roots.is_empty() && !mcp_roots_support {
120+
if valid_roots.is_empty() {
115121
let message = if allowed_directories.is_empty() {
116122
"Server cannot operate: No allowed directories available. Server was started without command-line directories and client provided empty roots. Please either: 1) Start server with directory arguments, or 2) Use a client that supports MCP roots protocol and provides valid root directories."
117123
} else {
@@ -120,7 +126,6 @@ impl FileSystemHandler {
120126
let _ = runtime.stderr_message(message.to_string()).await;
121127
} else {
122128
let num_valid_roots = valid_roots.len();
123-
124129
fs_service.update_allowed_paths(valid_roots).await;
125130
let message = format!(
126131
"Updated allowed directories from MCP roots: {num_valid_roots} valid directories",
@@ -142,7 +147,14 @@ impl ServerHandler for FileSystemHandler {
142147
_notification: RootsListChangedNotification,
143148
runtime: Arc<dyn McpServer>,
144149
) -> std::result::Result<(), RpcError> {
145-
self.update_allowed_directories(runtime).await;
150+
if self.mcp_roots_support {
151+
self.update_allowed_directories(runtime).await;
152+
} else {
153+
let message =
154+
"Skipping ROOTS client updates, server launched without the --enable-roots flag."
155+
.to_string();
156+
let _ = runtime.stderr_message(message).await;
157+
};
146158
Ok(())
147159
}
148160

src/tools/read_media_file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::fs_service::FileSystemService;
99

1010
#[mcp_tool(
1111
name = "read_media_file",
12-
title="Read an Image or Audio file",
12+
title="Read a media (Image/Audio) file",
1313
description = concat!("Reads an image or audio file and returns its Base64-encoded content along with the corresponding MIME type. ",
1414
"The max_bytes argument could be used to enforce an upper limit on the size of a file to read ",
1515
"if the media file exceeds this limit, the operation will return an error instead of reading the media file. ",

src/tools/search_files_content.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rust_mcp_sdk::schema::{CallToolResult, schema_utils::CallToolError};
66
use std::fmt::Write;
77
#[mcp_tool(
88
name = "search_files_content",
9-
title="Move files content",
9+
title="Search files content",
1010
description = concat!("Searches for text or regex patterns in the content of files matching matching a GLOB pattern.",
1111
"Returns detailed matches with file path, line number, column number and a preview of matched text.",
1212
"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.",

tests/test_tools.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,16 @@ async fn ensure_tools_duplication() {
147147
duplicate_names.push(t.name.to_string());
148148
}
149149

150-
if let Some(title) = t.title {
151-
if !titles.insert(title.to_string()) {
152-
duplicate_titles.push(title.to_string());
153-
}
150+
if let Some(title) = t.title
151+
&& !titles.insert(title.to_string())
152+
{
153+
duplicate_titles.push(title.to_string());
154154
}
155155

156-
if let Some(description) = t.description {
157-
if !descriptions.insert(description.to_string()) {
158-
duplicate_descriptions.push(description.to_string());
159-
}
156+
if let Some(description) = t.description
157+
&& !descriptions.insert(description.to_string())
158+
{
159+
duplicate_descriptions.push(description.to_string());
160160
}
161161
}
162162

0 commit comments

Comments
 (0)