Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 72 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ license = false
eula = false

[dependencies]
rust-mcp-sdk = {version="0.7", default-features = false, features = [
rust-mcp-sdk = {version="0.8", default-features = false, features = [
"server",
"macros",
"stdio",
"2025_06_18",
"stdio"
] }

thiserror = { version = "2.0" }
Expand Down
Empty file modified docs/_media/favicon.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,4 @@


<sup>◾ generated by [mcp-discovery](https://github.com/rust-mcp-stack/mcp-discovery)</sup>
<!-- mcp-discovery-render-end -->
<!-- mcp-discovery-render-end -->
20 changes: 17 additions & 3 deletions src/fs_service/io/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,25 @@ use tokio::{
const MAX_CONCURRENT_FILE_READ: usize = 5;

impl FileSystemService {
pub async fn read_text_file(&self, file_path: &Path) -> ServiceResult<String> {
pub async fn read_text_file(
&self,
file_path: &Path,
with_line_numbers: bool,
) -> ServiceResult<String> {
let allowed_directories = self.allowed_directories().await;
let valid_path = self.validate_path(file_path, allowed_directories)?;
let content = tokio::fs::read_to_string(valid_path).await?;
Ok(content)

if with_line_numbers {
Ok(content
.lines()
.enumerate()
.map(|(i, line)| format!("{:>6} | {}", i + 1, line))
.collect::<Vec<_>>()
.join("\n"))
} else {
Ok(content)
}
}

/// Reads the first n lines from a text file, preserving line endings.
Expand Down Expand Up @@ -116,7 +130,7 @@ impl FileSystemService {
let start_pos = if line_count <= n {
0 // Read from start if fewer than n lines
} else {
*newline_positions.get(n-1).unwrap_or(&0) + 1
*newline_positions.get(n - 1).unwrap_or(&0) + 1
};

// Read forward from start_pos
Expand Down
29 changes: 13 additions & 16 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ use crate::{error::ServiceResult, fs_service::FileSystemService, tools::*};
use async_trait::async_trait;
use rust_mcp_sdk::McpServer;
use rust_mcp_sdk::mcp_server::ServerHandler;
use rust_mcp_sdk::schema::RootsListChangedNotification;
use rust_mcp_sdk::schema::{
CallToolRequest, CallToolResult, InitializeRequest, InitializeResult, ListToolsRequest,
ListToolsResult, RpcError, schema_utils::CallToolError,
CallToolRequestParams, InitializeRequestParams, NotificationParams, PaginatedRequestParams,
};
use rust_mcp_sdk::schema::{
CallToolResult, InitializeResult, ListToolsResult, RpcError, schema_utils::CallToolError,
};
use std::cmp::Ordering;
use std::sync::Arc;
Expand Down Expand Up @@ -94,7 +95,7 @@ impl FileSystemHandler {
// client supports roots
let fs_service = self.fs_service.clone();
// retrieve roots from the client and update the allowed directories accordingly
let roots = match runtime.clone().list_roots(None).await {
let roots = match runtime.clone().request_root_list(None).await {
Ok(roots_result) => roots_result.roots,
Err(_err) => {
vec![]
Expand Down Expand Up @@ -144,7 +145,7 @@ impl ServerHandler for FileSystemHandler {

async fn handle_roots_list_changed_notification(
&self,
_notification: RootsListChangedNotification,
_params: Option<NotificationParams>,
runtime: Arc<dyn McpServer>,
) -> std::result::Result<(), RpcError> {
if self.mcp_roots_support {
Expand All @@ -160,7 +161,7 @@ impl ServerHandler for FileSystemHandler {

async fn handle_list_tools_request(
&self,
_: ListToolsRequest,
_params: Option<PaginatedRequestParams>,
_: Arc<dyn McpServer>,
) -> std::result::Result<ListToolsResult, RpcError> {
Ok(ListToolsResult {
Expand All @@ -172,33 +173,29 @@ impl ServerHandler for FileSystemHandler {

async fn handle_initialize_request(
&self,
initialize_request: InitializeRequest,
params: InitializeRequestParams,
runtime: Arc<dyn McpServer>,
) -> std::result::Result<InitializeResult, RpcError> {
runtime
.set_client_details(initialize_request.params.clone())
.set_client_details(params.clone())
.await
.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.
if server_info
.protocol_version
.cmp(&initialize_request.params.protocol_version)
== Ordering::Greater
{
server_info.protocol_version = initialize_request.params.protocol_version;
if server_info.protocol_version.cmp(&params.protocol_version) == Ordering::Greater {
server_info.protocol_version = params.protocol_version;
}
Ok(server_info)
}

async fn handle_call_tool_request(
&self,
request: CallToolRequest,
params: CallToolRequestParams,
_: Arc<dyn McpServer>,
) -> std::result::Result<CallToolResult, CallToolError> {
let tool_params: FileSystemTools =
FileSystemTools::try_from(request.params).map_err(CallToolError::new)?;
FileSystemTools::try_from(params).map_err(CallToolError::new)?;

// Verify write access for tools that modify the file system
if tool_params.require_write_access() {
Expand Down
27 changes: 22 additions & 5 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
use crate::handler::FileSystemHandler;
use crate::{cli::CommandArguments, error::ServiceResult};
use rust_mcp_sdk::mcp_server::McpServerOptions;
use rust_mcp_sdk::schema::{
Implementation, InitializeResult, LATEST_PROTOCOL_VERSION, ServerCapabilities,
ServerCapabilitiesTools,
Implementation, InitializeResult, ProtocolVersion, ServerCapabilities, ServerCapabilitiesTools,
};
use rust_mcp_sdk::{McpServer, StdioTransport, TransportOptions, mcp_server::server_runtime};
use rust_mcp_sdk::{ToMcpServerHandler, mcp_icon};

pub fn server_details() -> InitializeResult {
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())
title: Some("Filesystem MCP Server".to_string()),
description: Some(
"A fast and efficient tools for managing filesystem operations.".to_string(),
),
icons: vec![mcp_icon!(
src = "https://rust-mcp-stack.github.io/rust-mcp-filesystem/_media/rust-mcp-filesystem-128.png",
mime_type = "image/png",
sizes = ["128x128"]
)],
website_url: Some("https://rust-mcp-stack.github.io/rust-mcp-filesystem".into()),
},
capabilities: ServerCapabilities {
experimental: None,
Expand All @@ -20,18 +30,25 @@ pub fn server_details() -> InitializeResult {
resources: None,
tools: Some(ServerCapabilitiesTools { list_changed: None }),
completions: None,
tasks: None,
},
instructions: None,
meta: None,
protocol_version: LATEST_PROTOCOL_VERSION.to_string(),
protocol_version: ProtocolVersion::V2025_06_18.to_string(),
}
}

pub async fn start_server(args: CommandArguments) -> ServiceResult<()> {
let transport = StdioTransport::new(TransportOptions::default())?;

let handler = FileSystemHandler::new(&args)?;
let server = server_runtime::create_server(server_details(), transport, handler);
let server = server_runtime::create_server(McpServerOptions {
server_details: server_details(),
handler: handler.to_mcp_server_handler(),
task_store: None,
client_task_store: None,
transport,
});

server.start().await?;

Expand Down
7 changes: 6 additions & 1 deletion src/tools/calculate_directory_size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ pub enum FileSizeOutputFormat {
destructive_hint = false,
idempotent_hint = false,
open_world_hint = false,
read_only_hint = true
read_only_hint = true,
icons = [
(src = "https://rust-mcp-stack.github.io/rust-mcp-filesystem/_media/tool_icons/calculate_directory_size.png",
mime_type = "image/png",
sizes = ["128x128"])
],
)]
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, JsonSchema)]
pub struct CalculateDirectorySize {
Expand Down
7 changes: 6 additions & 1 deletion src/tools/create_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ use crate::fs_service::FileSystemService;
destructive_hint = false,
idempotent_hint = false,
open_world_hint = false,
read_only_hint = false
read_only_hint = false,
icons = [
(src = "https://rust-mcp-stack.github.io/rust-mcp-filesystem/_media/tool_icons/create_directory.png",
mime_type = "image/png",
sizes = ["128x128"])
],
)]
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, JsonSchema)]
pub struct CreateDirectory {
Expand Down
7 changes: 6 additions & 1 deletion src/tools/directory_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ use crate::fs_service::FileSystemService;
destructive_hint = false,
idempotent_hint = false,
open_world_hint = false,
read_only_hint = true
read_only_hint = true,
icons = [
(src = "https://rust-mcp-stack.github.io/rust-mcp-filesystem/_media/tool_icons/directory_tree.png",
mime_type = "image/png",
sizes = ["128x128"])
],
)]
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, JsonSchema)]
pub struct DirectoryTree {
Expand Down
Loading