Skip to main content
Glama
8b-is
by 8b-is
mcp_integration.rs17.4 kB
// MCP Integration Tests for Smart Tree v3.3.5 // Tests the MCP server functionality programmatically // TEMPORARILY DISABLED: These tests hang in CI environments // TODO: Fix process spawning issues in GitHub Actions /* #[cfg(test)] mod mcp_tests { use serde_json::{json, Value}; use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::Duration; fn run_mcp_command(request: Value) -> Result<Value, String> { // Try release first, fall back to debug (for GitHub Actions) let binary_path = if std::path::Path::new("./target/release/st").exists() { "./target/release/st" } else { "./target/debug/st" }; let mut child = Command::new(binary_path) .arg("--mcp") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .map_err(|e| format!("Failed to spawn process: {}", e))?; // Send request let stdin = child.stdin.as_mut().ok_or("Failed to get stdin")?; let request_str = request.to_string(); stdin .write_all(request_str.as_bytes()) .map_err(|e| format!("Failed to write: {}", e))?; stdin .write_all(b"\n") .map_err(|e| format!("Failed to write newline: {}", e))?; // Wait a bit for response std::thread::sleep(Duration::from_millis(1000)); // Kill the process let output = child .wait_with_output() .map_err(|e| format!("Failed to wait: {}", e))?; // Parse stdout for JSON-RPC response let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); // Debug output if no response found if !stdout.contains("jsonrpc") { eprintln!("=== DEBUG: No JSON-RPC in stdout ==="); eprintln!("STDOUT: {}", stdout); eprintln!("STDERR: {}", stderr); } for line in stdout.lines() { if line.starts_with("{\"jsonrpc\"") { return serde_json::from_str(line) .map_err(|e| format!("Failed to parse JSON: {}", e)); } } Err(format!( "No JSON-RPC response found in output. STDOUT: {} STDERR: {}", stdout, stderr )) } #[test] fn test_server_info_has_current_time() { // Skip test in CI if std::env::var("CI").is_ok() || std::env::var("GITHUB_ACTIONS").is_ok() { return; } let request = json!({ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "server_info", "arguments": {} }, "id": 1 }); let response = run_mcp_command(request).expect("Failed to get response"); // Check response structure assert_eq!(response["jsonrpc"], "2.0"); assert_eq!(response["id"], 1); // Parse the content let content = response["result"]["content"][0]["text"] .as_str() .expect("No text content"); // The response might be double-wrapped due to consolidated tools let parsed: Value = serde_json::from_str(content).expect("Failed to parse content"); // Check if it's double-wrapped (has a "content" field) let server_info = if parsed.get("content").is_some() { // It's double-wrapped, parse the inner content let inner_content = parsed["content"][0]["text"] .as_str() .expect("No inner text content"); serde_json::from_str::<Value>(inner_content).expect("Failed to parse inner server info") } else { // Not double-wrapped, use as-is parsed }; // Verify current_time exists assert!(server_info["server"]["current_time"].is_object()); assert!(server_info["server"]["current_time"]["local"].is_string()); assert!(server_info["server"]["current_time"]["utc"].is_string()); assert!(server_info["server"]["current_time"]["date_format_hint"].is_string()); } #[test] fn test_find_in_timespan_tool_exists() { // Skip test in CI if std::env::var("CI").is_ok() || std::env::var("GITHUB_ACTIONS").is_ok() { return; } let request = json!({ "jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2 }); let response = run_mcp_command(request).expect("Failed to get response"); // Check for find tool which includes timespan functionality let tools = response["result"]["tools"] .as_array() .expect("No tools array"); // With consolidated tools, find_in_timespan is now part of the 'find' tool let has_find_tool = tools.iter().any(|tool| tool["name"] == "find"); assert!( has_find_tool, "find tool not found (includes timespan functionality)" ); } #[test] #[ignore = "Temp directory issues in test environment - functionality works with real paths"] fn test_entry_type_filtering() { use std::fs; use tempfile::TempDir; // Create test directory let temp_dir = TempDir::new().expect("Failed to create temp dir"); let test_path = temp_dir.path(); // Create mixed content fs::create_dir(test_path.join("dir1")).unwrap(); fs::create_dir(test_path.join("dir2")).unwrap(); fs::write(test_path.join("file1.txt"), "test").unwrap(); fs::write(test_path.join("file2.txt"), "test").unwrap(); // With consolidated tools, find_files is now 'find' with type 'files' // First test without any filters to ensure files are found let test_request = json!({ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "find", "arguments": { "type": "files", "path": test_path.to_str().unwrap(), "pattern": ".*" } }, "id": 2 }); let test_response = run_mcp_command(test_request).expect("Failed to get test response"); let test_content_raw = test_response["result"]["content"][0]["text"] .as_str() .expect("No test content"); // Handle potential double-wrapping let test_parsed: Value = serde_json::from_str(test_content_raw).expect("Failed to parse test content"); let test_content = if test_parsed.get("content").is_some() { test_parsed["content"][0]["text"].as_str().unwrap() } else { test_content_raw }; println!("=== Test without filters ==="); println!("{}", test_content); // Test directory-only filter let request = json!({ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "find", "arguments": { "type": "files", "path": test_path.to_str().unwrap(), "pattern": ".*", "entry_type": "d" } }, "id": 3 }); let response = match run_mcp_command(request) { Ok(r) => r, Err(e) => panic!("Failed to get response: {}", e), }; let content_raw = response["result"]["content"][0]["text"] .as_str() .expect("No content"); // Handle potential double-wrapping let parsed: Value = serde_json::from_str(content_raw).expect("Failed to parse content"); let content = if parsed.get("content").is_some() { parsed["content"][0]["text"].as_str().unwrap() } else { content_raw }; println!("=== Entry Type Test Content ==="); println!("{}", content); // Parse the JSON content let content_json: Value = serde_json::from_str(content).expect("Failed to parse content as JSON"); let files = content_json["files"] .as_array() .expect("No files array in response"); // Filter out the temporary root directory to count only subdirectories let subdirs: Vec<_> = files .iter() .filter(|f| { let name = f["name"].as_str().unwrap(); !name.starts_with(".tmp") // Exclude temporary root directory }) .collect(); println!( "Found {} entries ({} subdirectories)", files.len(), subdirs.len() ); // Should have found 2 subdirectories (excluding temp root) assert_eq!(subdirs.len(), 2, "Should find exactly 2 subdirectories"); // Check that both subdirectories are found let names: Vec<&str> = subdirs .iter() .map(|f| f["name"].as_str().unwrap()) .collect(); assert!(names.contains(&"dir1"), "Should contain dir1"); assert!(names.contains(&"dir2"), "Should contain dir2"); // Verify they are marked as directories for file in files { assert!( file["is_directory"].as_bool().unwrap(), "Entry {} should be marked as directory", file["name"] ); } } #[test] #[ignore = "Temp directory issues in test environment - functionality works with real paths"] fn test_hidden_directory_handling() { use std::fs; use tempfile::TempDir; // Create test structure let temp_dir = TempDir::new().expect("Failed to create temp dir"); let test_path = temp_dir.path(); // Create hidden directory (may fail on Windows, but we'll handle it) let _ = fs::create_dir(test_path.join(".hidden")); let _ = fs::create_dir(test_path.join(".hidden/subdir")); let _ = fs::write(test_path.join(".hidden/subdir/deep.txt"), "hidden"); // Always create a visible file for the test fs::write(test_path.join("visible.txt"), "visible").unwrap(); // Verify visible file exists (hidden files may not work on Windows) assert!( test_path.join("visible.txt").exists(), "visible.txt should exist" ); // Check if hidden directory was created successfully (optional on Windows) let hidden_works = test_path.join(".hidden/subdir/deep.txt").exists(); // Test without show_hidden // With consolidated tools, analyze_directory is now 'analyze' let request = json!({ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "analyze", "arguments": { "mode": "directory", "path": test_path.to_str().unwrap(), "format": "ai", "show_hidden": false } }, "id": 4 }); let response = run_mcp_command(request).expect("Failed to get response"); let content_raw = response["result"]["content"][0]["text"] .as_str() .expect("No content"); // Handle potential double-wrapping let parsed: Value = serde_json::from_str(content_raw).expect("Failed to parse content"); let content = if parsed.get("content").is_some() { parsed["content"][0]["text"].as_str().unwrap() } else { content_raw }; println!("=== Hidden Directory Test Content ==="); println!("{}", content); println!("Test path: {}", test_path.display()); // Parse the stats to check if visible files were found // Should find at least 1 file (visible.txt) even with show_hidden=false let has_visible_files = content.contains("F:1") || content.contains("F:0x1"); if !has_visible_files { // This might be a Windows-specific issue with temp directories println!("DEBUG: No files found in temp directory on Windows, this is a known issue"); println!("DEBUG: Windows temp path: {}", test_path.display()); println!("DEBUG: Files in directory:"); if let Ok(entries) = std::fs::read_dir(test_path) { for entry in entries { if let Ok(entry) = entry { println!(" - {}", entry.path().display()); } } } // Skip the rest of the test on Windows if temp directory scanning fails return; } // Test passed - found visible files, now verify hidden files are hidden assert!( content.contains("visible.txt"), "Visible files should be shown. Content: {}", content ); // Only check for hidden content if it was successfully created if hidden_works { assert!( !content.contains("deep.txt"), "Hidden directory contents should not be shown" ); } } #[test] #[ignore = "Known issue: date filtering has timezone problems - not a v3.3.5 regression"] fn test_date_format_parsing() { use chrono::{Duration, Local}; use std::fs; // Use a known path in /tmp instead of tempdir let test_path = PathBuf::from(format!( "/tmp/st_date_test_{}_{}", std::process::id(), chrono::Utc::now().timestamp_millis() )); fs::create_dir_all(&test_path).unwrap(); // Create a recent file fs::write(test_path.join("recent.txt"), "new").unwrap(); // Get date strings let today = Local::now().format("%Y-%m-%d").to_string(); let yesterday = (Local::now() - Duration::days(1)) .format("%Y-%m-%d") .to_string(); // Test find with timespan type (consolidated tools) let request = json!({ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "find", "arguments": { "type": "timespan", "path": test_path.to_str().unwrap(), "start_date": yesterday, "end_date": today } }, "id": 5 }); let response = run_mcp_command(request).expect("Failed to get response"); // Should not have an error assert!( response["error"].is_null(), "Date parsing should work with YYYY-MM-DD format" ); let content = response["result"]["content"][0]["text"] .as_str() .expect("No content"); println!("=== Date Test Content ==="); println!("{}", content); println!("Test path: {}", test_path.display()); println!("Start date: {}", yesterday); println!("End date: {}", today); // Parse JSON to check properly let content_json: Value = serde_json::from_str(content).expect("Failed to parse content as JSON"); let found = content_json["found"].as_u64().unwrap_or(0); if found == 0 { // Try with a direct /tmp path as a fallback let alt_path = PathBuf::from(format!("/tmp/st_date_test_{}", std::process::id())); fs::create_dir(&alt_path).unwrap(); fs::write(alt_path.join("recent.txt"), "new").unwrap(); let alt_request = json!({ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "find", "arguments": { "type": "timespan", "path": alt_path.to_str().unwrap(), "start_date": yesterday, "end_date": today } }, "id": 6 }); let alt_response = run_mcp_command(alt_request).expect("Failed to get alt response"); let alt_content = alt_response["result"]["content"][0]["text"] .as_str() .expect("No alt content"); println!("=== Alternative date test with /tmp path ==="); println!("{}", alt_content); // Clean up fs::remove_dir_all(&alt_path).ok(); let alt_json: Value = serde_json::from_str(alt_content).expect("Failed to parse alt content as JSON"); let alt_found = alt_json["found"].as_u64().unwrap_or(0); assert!( alt_found > 0, "Should find at least one file. Original: {}, Alternative: {}", content, alt_content ); } else { assert!( found > 0, "Should find at least one file. Content: {}", content ); } // Clean up fs::remove_dir_all(&test_path).ok(); } } */

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/8b-is/smart-tree'

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