#![cfg(feature = "mcp")]
mod common;
use common::mock_mcp_server;
use mixtape_core::mcp::{McpClient, McpServerConfig, McpTransport};
fn mock_client(name: &str) -> McpClient {
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
name,
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
);
McpClient::new(config).expect("Failed to create client")
}
#[tokio::test]
async fn test_connect_to_mock_server() {
let client = mock_client("test-server");
let result = client.connect().await;
assert!(result.is_ok(), "Failed to connect: {:?}", result.err());
client.disconnect().await.unwrap();
}
#[tokio::test]
async fn test_list_tools_from_mock_server() {
let client = mock_client("test-server");
let tools = client.list_tools().await.expect("Failed to list tools");
assert_eq!(tools.len(), 3);
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"echo"));
assert!(tool_names.contains(&"add"));
assert!(tool_names.contains(&"fail"));
let echo = tools.iter().find(|t| t.name == "echo").unwrap();
assert_eq!(echo.description, "Echo back the input");
assert!(echo.input_schema["properties"]["message"].is_object());
}
#[tokio::test]
async fn test_call_echo_tool() {
let client = mock_client("test-server");
let result = client
.call_tool(
"echo".to_string(),
serde_json::json!({"message": "Hello, MCP!"}),
)
.await
.expect("Failed to call tool");
let content = &result["content"];
assert!(content.is_array());
assert_eq!(content[0]["text"], "Hello, MCP!");
}
#[tokio::test]
async fn test_call_add_tool() {
let client = mock_client("test-server");
let result = client
.call_tool("add".to_string(), serde_json::json!({"a": 5, "b": 3}))
.await
.expect("Failed to call tool");
let content = &result["content"];
assert_eq!(content[0]["text"], "8");
}
#[tokio::test]
async fn test_call_failing_tool() {
let client = mock_client("test-server");
let result = client
.call_tool("fail".to_string(), serde_json::json!({}))
.await
.expect("Call should succeed even if tool reports error");
assert_eq!(result["isError"], true);
assert_eq!(result["content"][0]["text"], "This tool always fails");
}
#[tokio::test]
async fn test_idempotent_connect() {
let client = mock_client("test-server");
client.connect().await.unwrap();
client.connect().await.unwrap();
client.connect().await.unwrap();
let tools = client.list_tools().await.unwrap();
assert!(!tools.is_empty());
}
#[tokio::test]
async fn test_reconnect_after_disconnect() {
let client = mock_client("test-server");
client.connect().await.unwrap();
let tools1 = client.list_tools().await.unwrap();
client.disconnect().await.unwrap();
client.connect().await.unwrap();
let tools2 = client.list_tools().await.unwrap();
assert_eq!(tools1.len(), tools2.len());
}
#[tokio::test]
async fn test_lazy_connect_on_list_tools() {
let client = mock_client("test-server");
let tools = client.list_tools().await.expect("Lazy connect failed");
assert!(!tools.is_empty());
}
#[tokio::test]
async fn test_lazy_connect_on_call_tool() {
let client = mock_client("test-server");
let result = client
.call_tool("echo".to_string(), serde_json::json!({"message": "lazy"}))
.await
.expect("Lazy connect failed");
assert_eq!(result["content"][0]["text"], "lazy");
}
#[tokio::test]
async fn test_client_name() {
let client = mock_client("my-test-server");
assert_eq!(client.name(), "my-test-server");
}
#[tokio::test]
async fn test_tool_with_namespace() {
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"namespaced",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.with_namespace("test");
let client = McpClient::new(config).unwrap();
let tools = client.list_tools().await.unwrap();
assert_eq!(tools.len(), 3);
}
use common::{AutoApproveGrantStore, EventCollector, MockProvider};
use mixtape_core::Agent;
fn mock_mcp_config(name: &str) -> McpServerConfig {
let (cmd, args) = mock_mcp_server::command();
McpServerConfig::new(
name,
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
}
#[tokio::test]
async fn test_agent_add_mcp_server() {
let provider = MockProvider::new().with_text("Done");
let mut agent = Agent::builder().provider(provider).build().await.unwrap();
assert_eq!(agent.list_tools().len(), 0);
let config = mock_mcp_config("mock-server");
agent.add_mcp_server(config).await.unwrap();
let tools = agent.list_tools();
assert_eq!(tools.len(), 3);
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"mock-server_echo"));
assert!(tool_names.contains(&"mock-server_add"));
assert!(tool_names.contains(&"mock-server_fail"));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_add_mcp_server_without_namespace() {
let provider = MockProvider::new().with_text("Done");
let mut agent = Agent::builder().provider(provider).build().await.unwrap();
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"mock-server",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.without_namespace();
agent.add_mcp_server(config).await.unwrap();
let tools = agent.list_tools();
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"echo"));
assert!(tool_names.contains(&"add"));
assert!(tool_names.contains(&"fail"));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_add_mcp_server_with_custom_namespace() {
let provider = MockProvider::new().with_text("Done");
let mut agent = Agent::builder().provider(provider).build().await.unwrap();
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"server",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.with_namespace("custom");
agent.add_mcp_server(config).await.unwrap();
let tools = agent.list_tools();
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"custom_echo"));
assert!(tool_names.contains(&"custom_add"));
assert!(tool_names.contains(&"custom_fail"));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_add_mcp_server_with_tool_filter() {
let provider = MockProvider::new().with_text("Done");
let mut agent = Agent::builder().provider(provider).build().await.unwrap();
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"filtered",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.without_namespace()
.only_tools(["echo", "add"]);
agent.add_mcp_server(config).await.unwrap();
let tools = agent.list_tools();
assert_eq!(tools.len(), 2);
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"echo"));
assert!(tool_names.contains(&"add"));
assert!(!tool_names.contains(&"fail"));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_add_mcp_server_with_exclude_filter() {
let provider = MockProvider::new().with_text("Done");
let mut agent = Agent::builder().provider(provider).build().await.unwrap();
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"filtered",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.without_namespace()
.exclude_tools(["fail"]);
agent.add_mcp_server(config).await.unwrap();
let tools = agent.list_tools();
assert_eq!(tools.len(), 2);
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"echo"));
assert!(tool_names.contains(&"add"));
assert!(!tool_names.contains(&"fail"));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_builder_with_mcp_server() {
let provider = MockProvider::new().with_text("Done");
let config = mock_mcp_config("builder-test");
let agent = Agent::builder()
.provider(provider)
.with_mcp_server(config)
.build()
.await
.unwrap();
let tools = agent.list_tools();
assert_eq!(tools.len(), 3);
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_multiple_mcp_servers() {
let provider = MockProvider::new().with_text("Done");
let mut agent = Agent::builder().provider(provider).build().await.unwrap();
let config1 = mock_mcp_config("server1");
let config2 = mock_mcp_config("server2");
agent.add_mcp_server(config1).await.unwrap();
agent.add_mcp_server(config2).await.unwrap();
let tools = agent.list_tools();
assert_eq!(tools.len(), 6);
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(tool_names.contains(&"server1_echo"));
assert!(tool_names.contains(&"server2_echo"));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_use_mcp_tool() {
let provider = MockProvider::new()
.with_tool_use("echo", serde_json::json!({"message": "Hello from MCP!"}))
.with_text("The tool said: Hello from MCP!");
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"mcp",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.without_namespace();
let agent = Agent::builder()
.provider(provider)
.with_grant_store(AutoApproveGrantStore)
.with_mcp_server(config)
.build()
.await
.unwrap();
let response = agent.run("Echo something").await.unwrap();
assert_eq!(response, "The tool said: Hello from MCP!");
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_mcp_tool_events() {
let provider = MockProvider::new()
.with_tool_use("add", serde_json::json!({"a": 10, "b": 20}))
.with_text("The sum is 30");
let (cmd, args) = mock_mcp_server::command();
let config = McpServerConfig::new(
"math",
McpTransport::stdio(&cmd).args(args.iter().map(|s| s.as_str())),
)
.without_namespace();
let agent = Agent::builder()
.provider(provider)
.with_grant_store(AutoApproveGrantStore)
.with_mcp_server(config)
.build()
.await
.unwrap();
let collector = EventCollector::new();
let collector_clone = collector.clone();
agent.add_hook(collector);
agent.run("Add 10 and 20").await.unwrap();
let events = collector_clone.events();
assert!(events.contains(&"tool_requested".to_string()));
assert!(events.contains(&"tool_completed".to_string()));
agent.shutdown().await;
}
#[tokio::test]
async fn test_agent_shutdown_disconnects_mcp() {
let provider = MockProvider::new().with_text("Done");
let config = mock_mcp_config("shutdown-test");
let agent = Agent::builder()
.provider(provider)
.with_mcp_server(config)
.build()
.await
.unwrap();
assert_eq!(agent.list_tools().len(), 3);
agent.shutdown().await;
}