Skip to main content
Glama
path.rs6.12 kB
use std::path::{Path, PathBuf}; use rmcp::model::{ErrorCode, ErrorData}; /// Comprehensive path resolution utility for all MCP tools pub struct PathResolver; impl PathResolver { /// Resolve any path format to a canonical PathBuf /// Handles: relative paths, ~/ expansion, ./ prefix, absolute paths, .. resolution pub fn resolve_path<P: AsRef<Path>>(path: P) -> Result<PathBuf, ErrorData> { let path_str = path.as_ref().to_string_lossy(); // Handle tilde expansion let expanded_path = if path_str.starts_with("~/") { match dirs::home_dir() { Some(home) => home.join(&path_str[2..]), None => return Err(ErrorData::new( ErrorCode::INTERNAL_ERROR, "Cannot determine home directory", None, )), } } else if path_str == "~" { match dirs::home_dir() { Some(home) => home, None => return Err(ErrorData::new( ErrorCode::INTERNAL_ERROR, "Cannot determine home directory", None, )), } } else if path_str.starts_with("./") || path_str.starts_with("/") { // Already properly prefixed or absolute PathBuf::from(path_str.as_ref()) } else if path_str == "." { // Current directory std::env::current_dir().map_err(|e| ErrorData::new( ErrorCode::INTERNAL_ERROR, format!("Cannot get current directory: {}", e), None, ))? } else { // Relative path without ./ prefix - add current directory std::env::current_dir() .map_err(|e| ErrorData::new( ErrorCode::INTERNAL_ERROR, format!("Cannot get current directory: {}", e), None, ))? .join(path_str.as_ref()) }; // Canonicalize to resolve .. and . components and ensure path exists expanded_path.canonicalize().map_err(|e| ErrorData::new( ErrorCode::INVALID_PARAMS, format!("Invalid path '{}': {}", path_str, e), None, )) } /// Resolve path and ensure it's a file pub fn resolve_file_path<P: AsRef<Path>>(path: P) -> Result<PathBuf, ErrorData> { let resolved = Self::resolve_path(path)?; if !resolved.is_file() { return Err(ErrorData::new( ErrorCode::INVALID_PARAMS, format!("Path is not a file: {}", resolved.display()), None, )); } Ok(resolved) } /// Resolve path and ensure it's a directory pub fn resolve_directory_path<P: AsRef<Path>>(path: P) -> Result<PathBuf, ErrorData> { let resolved = Self::resolve_path(path)?; if !resolved.is_dir() { return Err(ErrorData::new( ErrorCode::INVALID_PARAMS, format!("Path is not a directory: {}", resolved.display()), None, )); } Ok(resolved) } /// Resolve path that can be either file or directory pub fn resolve_file_or_directory_path<P: AsRef<Path>>(path: P) -> Result<PathBuf, ErrorData> { let resolved = Self::resolve_path(path)?; if !resolved.exists() { return Err(ErrorData::new( ErrorCode::INVALID_PARAMS, format!("Path does not exist: {}", resolved.display()), None, )); } Ok(resolved) } } #[cfg(test)] mod tests { use super::*; use tempfile::TempDir; use std::fs; #[test] fn test_resolve_current_directory() { let result = PathResolver::resolve_path("."); assert!(result.is_ok()); assert!(result.unwrap().is_dir()); } #[test] fn test_resolve_relative_path() { let temp_dir = TempDir::new().unwrap(); let test_file = temp_dir.path().join("test.txt"); fs::write(&test_file, "test").unwrap(); // Change to temp directory let original_dir = std::env::current_dir().unwrap(); std::env::set_current_dir(temp_dir.path()).unwrap(); let result = PathResolver::resolve_path("test.txt"); assert!(result.is_ok()); let resolved_path = result.unwrap(); // Check that the resolved path ends with the expected file name // (handles macOS /private prefix canonicalization) assert!(resolved_path.ends_with("test.txt")); assert!(resolved_path.exists()); // Restore original directory std::env::set_current_dir(original_dir).unwrap(); } #[test] fn test_resolve_dotslash_path() { let result = PathResolver::resolve_path("./src"); // Should work if src directory exists if std::path::Path::new("src").exists() { assert!(result.is_ok()); } } #[test] fn test_file_validation() { let temp_dir = TempDir::new().unwrap(); let test_file = temp_dir.path().join("test.txt"); fs::write(&test_file, "test").unwrap(); let result = PathResolver::resolve_file_path(&test_file); assert!(result.is_ok()); // Test directory fails file validation let dir_result = PathResolver::resolve_file_path(temp_dir.path()); assert!(dir_result.is_err()); } #[test] fn test_directory_validation() { let temp_dir = TempDir::new().unwrap(); let test_file = temp_dir.path().join("test.txt"); fs::write(&test_file, "test").unwrap(); let result = PathResolver::resolve_directory_path(temp_dir.path()); assert!(result.is_ok()); // Test file fails directory validation let file_result = PathResolver::resolve_directory_path(&test_file); assert!(file_result.is_err()); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kensave/CodeCortX-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server