Skip to main content
Glama
mcp_stdio_test.rs15.5 kB
//! MCP stdio interface integration tests //! //! These tests verify that the MCP server correctly handles the stdio protocol //! and can communicate with TheHive API using real credentials. use serde_json::{json, Value}; use std::env; use std::io::{BufRead, BufReader, Write}; use std::process::{Command, Stdio}; use std::thread; use std::time::Duration; #[cfg(test)] mod mcp_stdio_tests { use super::*; #[tokio::test] async fn test_mcp_initialize_and_list_tools() { let api_key = match env::var("THEHIVE_API_KEY") { Ok(key) => key, Err(_) => { println!("Skipping test: THEHIVE_API_KEY not set"); return; } }; let api_endpoint = env::var("THEHIVE_API_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000/api".to_string()); println!("Testing with endpoint: {}", api_endpoint); println!( "Testing with API key: {}***", &api_key[..std::cmp::min(8, api_key.len())] ); let mut cmd = Command::new("cargo") .args(&["run", "--release"]) .env("THEHIVE_URL", &api_endpoint) .env("THEHIVE_API_TOKEN", &api_key) .env("VERIFY_SSL", "false") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("Failed to start MCP server"); let stdin = cmd.stdin.as_mut().expect("Failed to get stdin"); let stdout = cmd.stdout.as_mut().expect("Failed to get stdout"); let mut reader = BufReader::new(stdout); thread::sleep(Duration::from_millis(500)); let initialize_request = json!({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": { "roots": { "listChanged": true }, "sampling": {} }, "clientInfo": { "name": "test-client", "version": "1.0.0" } } }); writeln!(stdin, "{}", initialize_request).expect("Failed to write initialize request"); stdin.flush().expect("Failed to flush stdin"); let mut response_line = String::new(); match reader.read_line(&mut response_line) { Ok(_) => { println!("Initialize response: {}", response_line.trim()); let response: Value = serde_json::from_str(&response_line) .expect("Failed to parse initialize response"); assert_eq!(response["jsonrpc"], "2.0"); assert_eq!(response["id"], 1); assert!(response["result"].is_object()); let initialized_notification = json!({ "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} }); writeln!(stdin, "{}", initialized_notification) .expect("Failed to write initialized notification"); stdin.flush().expect("Failed to flush stdin"); thread::sleep(Duration::from_millis(100)); let tools_request = json!({ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }); writeln!(stdin, "{}", tools_request).expect("Failed to write tools/list request"); stdin.flush().expect("Failed to flush stdin"); let mut tools_response_line = String::new(); match reader.read_line(&mut tools_response_line) { Ok(_) => { println!("Tools response: {}", tools_response_line.trim()); let tools_response: Value = serde_json::from_str(&tools_response_line) .expect("Failed to parse tools response"); assert_eq!(tools_response["jsonrpc"], "2.0"); assert_eq!(tools_response["id"], 2); let tools = &tools_response["result"]["tools"]; assert!(tools.is_array()); let tools_array = tools.as_array().unwrap(); assert!(tools_array.len() >= 5); let tool_names: Vec<String> = tools_array .iter() .map(|tool| tool["name"].as_str().unwrap().to_string()) .collect(); assert!(tool_names.contains(&"get_thehive_alerts".to_string())); assert!(tool_names.contains(&"get_thehive_alert_by_id".to_string())); assert!(tool_names.contains(&"get_thehive_cases".to_string())); assert!(tool_names.contains(&"get_thehive_case_by_id".to_string())); assert!(tool_names.contains(&"promote_alert_to_case".to_string())); println!("✓ All expected tools are available: {:?}", tool_names); } Err(e) => panic!("Failed to read tools response: {}", e), } } Err(e) => panic!("Failed to read initialize response: {}", e), } cmd.kill().expect("Failed to kill MCP server process"); } #[tokio::test] async fn test_get_thehive_alerts_tool() { let api_key = match env::var("THEHIVE_API_KEY") { Ok(key) => key, Err(_) => { println!("Skipping test: THEHIVE_API_KEY not set"); return; } }; let api_endpoint = env::var("THEHIVE_API_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000/api".to_string()); println!("Testing get_thehive_alerts with endpoint: {}", api_endpoint); let mut cmd = Command::new("cargo") .args(&["run", "--release"]) .env("THEHIVE_URL", &api_endpoint) .env("THEHIVE_API_TOKEN", &api_key) .env("VERIFY_SSL", "false") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("Failed to start MCP server"); let stdin = cmd.stdin.as_mut().expect("Failed to get stdin"); let stdout = cmd.stdout.as_mut().expect("Failed to get stdout"); let mut reader = BufReader::new(stdout); thread::sleep(Duration::from_millis(500)); let initialize_request = json!({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "test-client", "version": "1.0.0" } } }); writeln!(stdin, "{}", initialize_request).expect("Failed to write initialize request"); stdin.flush().expect("Failed to flush stdin"); let mut response_line = String::new(); reader .read_line(&mut response_line) .expect("Failed to read initialize response"); let initialized_notification = json!({ "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} }); writeln!(stdin, "{}", initialized_notification) .expect("Failed to write initialized notification"); stdin.flush().expect("Failed to flush stdin"); thread::sleep(Duration::from_millis(100)); let tool_call_request = json!({ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "get_thehive_alerts", "arguments": { "limit": 5 } } }); writeln!(stdin, "{}", tool_call_request).expect("Failed to write tool call request"); stdin.flush().expect("Failed to flush stdin"); let mut tool_response_line = String::new(); match reader.read_line(&mut tool_response_line) { Ok(_) => { println!("Tool call response: {}", tool_response_line.trim()); if !tool_response_line.trim().is_empty() { let tool_response: Value = serde_json::from_str(&tool_response_line) .expect("Failed to parse tool response"); assert_eq!(tool_response["jsonrpc"], "2.0"); assert_eq!(tool_response["id"], 2); let result = &tool_response["result"]; assert!(result["content"].is_array()); let content = result["content"].as_array().unwrap(); assert!(!content.is_empty()); let first_content = &content[0]; assert_eq!(first_content["type"], "text"); assert!(first_content["text"].is_string()); if result["is_error"].as_bool().unwrap_or(false) { println!( "✓ Tool call returned expected error (TheHive not accessible): {}", first_content["text"] ); assert!(first_content["text"].as_str().unwrap().contains("error")); } else { println!("✓ Successfully retrieved alerts from TheHive"); } } else { println!("⚠ Empty response received - this may indicate a connection issue"); } } Err(e) => { println!("⚠ Failed to read tool response: {} - this may indicate TheHive is not accessible", e); } } cmd.kill().expect("Failed to kill MCP server process"); } #[tokio::test] async fn test_get_thehive_cases_tool() { let api_key = match env::var("THEHIVE_API_KEY") { Ok(key) => key, Err(_) => { println!("Skipping test: THEHIVE_API_KEY not set"); return; } }; let api_endpoint = env::var("THEHIVE_API_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000/api".to_string()); println!("Testing get_thehive_cases with endpoint: {}", api_endpoint); let mut cmd = Command::new("cargo") .args(&["run", "--release"]) .env("THEHIVE_URL", &api_endpoint) .env("THEHIVE_API_TOKEN", &api_key) .env("VERIFY_SSL", "false") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("Failed to start MCP server"); let stdin = cmd.stdin.as_mut().expect("Failed to get stdin"); let stdout = cmd.stdout.as_mut().expect("Failed to get stdout"); let mut reader = BufReader::new(stdout); thread::sleep(Duration::from_millis(500)); let initialize_request = json!({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "test-client", "version": "1.0.0" } } }); writeln!(stdin, "{}", initialize_request).expect("Failed to write initialize request"); stdin.flush().expect("Failed to flush stdin"); let mut response_line = String::new(); reader .read_line(&mut response_line) .expect("Failed to read initialize response"); let initialized_notification = json!({ "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} }); writeln!(stdin, "{}", initialized_notification) .expect("Failed to write initialized notification"); stdin.flush().expect("Failed to flush stdin"); thread::sleep(Duration::from_millis(100)); let tool_call_request = json!({ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "get_thehive_cases", "arguments": { "limit": 3 } } }); writeln!(stdin, "{}", tool_call_request).expect("Failed to write tool call request"); stdin.flush().expect("Failed to flush stdin"); let mut tool_response_line = String::new(); match reader.read_line(&mut tool_response_line) { Ok(_) => { println!("Tool call response: {}", tool_response_line.trim()); if !tool_response_line.trim().is_empty() { let tool_response: Value = serde_json::from_str(&tool_response_line) .expect("Failed to parse tool response"); assert_eq!(tool_response["jsonrpc"], "2.0"); assert_eq!(tool_response["id"], 2); let result = &tool_response["result"]; assert!(result["content"].is_array()); let content = result["content"].as_array().unwrap(); assert!(!content.is_empty()); let first_content = &content[0]; assert_eq!(first_content["type"], "text"); assert!(first_content["text"].is_string()); if result["is_error"].as_bool().unwrap_or(false) { println!( "✓ Tool call returned expected error (TheHive not accessible): {}", first_content["text"] ); assert!(first_content["text"].as_str().unwrap().contains("error")); } else { println!("✓ Successfully retrieved cases from TheHive"); } } else { println!("⚠ Empty response received - this may indicate a connection issue"); } } Err(e) => { println!("⚠ Failed to read tool response: {} - this may indicate TheHive is not accessible", e); } } cmd.kill().expect("Failed to kill MCP server process"); } } pub async fn run_mcp_stdio_test() { println!("🧪 Running MCP stdio interface tests..."); let api_key = env::var("THEHIVE_API_KEY").expect("THEHIVE_API_KEY environment variable is required"); let api_endpoint = env::var("THEHIVE_API_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000/api".to_string()); println!("📍 Endpoint: {}", api_endpoint); println!( "🔑 API Key: {}***", &api_key[..std::cmp::min(8, api_key.len())] ); println!("\n🚀 Starting MCP server test..."); println!("✅ Test setup complete. Run with: cargo test mcp_stdio_tests"); } #[tokio::main] async fn main() { run_mcp_stdio_test().await; }

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/gbrigandi/mcp-server-thehive'

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