timeout_client/
timeout_client.rs1use std::env;
9use std::time::Duration;
10use tenx_mcp::{
11 client::{ClientConfig, MCPClient},
12 error::{MCPError, Result},
13 retry::RetryConfig,
14 schema::*,
15 transport::TcpTransport,
16};
17use tracing::{error, info};
18
19async fn test_reliable_operation(client: &mut MCPClient) -> Result<()> {
20 info!("\n=== Testing Reliable Operation ===");
21 info!("This should succeed immediately...");
22
23 match client
24 .call_tool("reliable_operation".to_string(), None)
25 .await
26 {
27 Ok(result) => {
28 info!("✓ Success: {:?}", result.content);
29 }
30 Err(e) => {
31 error!("✗ Unexpected failure: {}", e);
32 }
33 }
34
35 Ok(())
36}
37
38async fn test_flakey_operation(client: &mut MCPClient) -> Result<()> {
39 info!("\n=== Testing Flakey Operation ===");
40 info!("This operation fails 2 times before succeeding.");
41 info!("With retry enabled, it should eventually succeed...");
42
43 match client.call_tool("flakey_operation".to_string(), None).await {
44 Ok(result) => {
45 info!("✓ Success after retries: {:?}", result.content);
46 }
47 Err(e) => {
48 error!("✗ Failed even with retries: {}", e);
49 }
50 }
51
52 Ok(())
53}
54
55async fn test_slow_operation(client: &mut MCPClient) -> Result<()> {
56 info!("\n=== Testing Slow Operation ===");
57 info!("This operation takes 5 seconds, but our timeout is 2 seconds.");
58 info!("It should timeout and retry, but still fail...");
59
60 match client.call_tool("slow_operation".to_string(), None).await {
61 Ok(_) => {
62 error!("✗ Unexpected success - should have timed out");
63 }
64 Err(e) => {
65 info!("✓ Expected timeout: {}", e);
66 if !matches!(e, MCPError::Timeout { .. }) {
68 error!("Error was not a timeout: {:?}", e);
69 }
70 }
71 }
72
73 Ok(())
74}
75
76async fn test_broken_operation(client: &mut MCPClient) -> Result<()> {
77 info!("\n=== Testing Broken Operation ===");
78 info!("This operation always fails with a non-retryable error.");
79 info!("Should fail immediately without retries...");
80
81 match client.call_tool("broken_operation".to_string(), None).await {
82 Ok(_) => {
83 error!("✗ Unexpected success");
84 }
85 Err(e) => {
86 info!("✓ Expected non-retryable error: {}", e);
87 if e.is_retryable() {
89 error!("Error was retryable when it shouldn't be: {:?}", e);
90 }
91 }
92 }
93
94 Ok(())
95}
96
97async fn test_custom_retry_config(host: &str, port: u16) -> Result<()> {
98 info!("\n=== Testing Custom Retry Configuration ===");
99 info!("Creating a client with very short timeouts and few retries...");
100
101 let config = ClientConfig {
103 retry: RetryConfig {
104 max_attempts: 2,
105 initial_delay: Duration::from_millis(50),
106 max_delay: Duration::from_millis(100),
107 backoff_multiplier: 2.0,
108 timeout: Duration::from_millis(500), },
110 request_timeout: Duration::from_secs(1),
111 };
112
113 let mut client = MCPClient::with_config(config);
114 let transport = TcpTransport::new(format!("{host}:{port}"));
115
116 client.connect(Box::new(transport)).await?;
117 client.initialize(
118 Implementation {
119 name: "timeout-test-client-custom".to_string(),
120 version: "1.0.0".to_string(),
121 },
122 ClientCapabilities::default(),
123 ).await?;
124
125 match client.call_tool("slow_operation".to_string(), None).await {
127 Ok(_) => {
128 error!("✗ Unexpected success");
129 }
130 Err(e) => {
131 info!("✓ Quick timeout as expected: {}", e);
132 }
133 }
134
135 Ok(())
136}
137
138#[tokio::main]
139async fn main() -> Result<()> {
140 tracing_subscriber::fmt().with_target(false).init();
142
143 let args: Vec<String> = env::args().collect();
145
146 let (host, port) = if args.len() == 3 {
147 (
148 args[1].clone(),
149 args[2].parse::<u16>().expect("Invalid port number"),
150 )
151 } else if args.len() == 1 {
152 ("127.0.0.1".to_string(), 3001)
154 } else {
155 eprintln!("Usage: {} [host] [port]", args[0]);
156 eprintln!("Example: {} 127.0.0.1 3001", args[0]);
157 eprintln!("If no arguments provided, defaults to 127.0.0.1:3001");
158 std::process::exit(1);
159 };
160
161 info!("Starting Timeout Test Client");
162 info!("Connecting to {}:{}", host, port);
163
164 let retry_config = RetryConfig {
166 max_attempts: 3,
167 initial_delay: Duration::from_millis(100),
168 max_delay: Duration::from_secs(2),
169 backoff_multiplier: 2.0,
170 timeout: Duration::from_secs(2), };
172
173 let config = ClientConfig {
174 retry: retry_config,
175 request_timeout: Duration::from_secs(5),
176 };
177
178 let mut client = MCPClient::with_config(config);
179
180 let transport = TcpTransport::new(format!("{host}:{port}"));
182
183 info!("Connecting to server...");
185 client.connect(Box::new(transport)).await?;
186
187 let init_result = client
188 .initialize(
189 Implementation {
190 name: "timeout-test-client".to_string(),
191 version: "1.0.0".to_string(),
192 },
193 ClientCapabilities::default(),
194 )
195 .await?;
196
197 info!(
198 "Connected! Server: {} v{}",
199 init_result.server_info.name, init_result.server_info.version
200 );
201
202 info!("\nAvailable tools:");
204 let tools = client.list_tools().await?;
205 for tool in &tools.tools {
206 info!(
207 " - {}: {}",
208 tool.name,
209 tool.description.as_deref().unwrap_or("")
210 );
211 }
212
213 if let Err(e) = test_reliable_operation(&mut client).await {
215 error!("Reliable operation test failed: {}", e);
216 }
217
218 if let Err(e) = test_flakey_operation(&mut client).await {
219 error!("Flakey operation test failed: {}", e);
220 }
221
222 if let Err(e) = test_slow_operation(&mut client).await {
223 error!("Slow operation test failed: {}", e);
224 }
225
226 if let Err(e) = test_broken_operation(&mut client).await {
227 error!("Broken operation test failed: {}", e);
228 }
229
230 info!("\n{}", "=".repeat(50));
232 if let Err(e) = test_custom_retry_config(&host, port).await {
233 error!("Custom retry config test failed: {}", e);
234 }
235
236 info!("\n=== All tests completed ===");
237 info!("Summary:");
238 info!("- Reliable operation: Should succeed immediately");
239 info!("- Flakey operation: Should fail 2 times then succeed");
240 info!("- Slow operation: Should timeout after 2 seconds");
241 info!("- Broken operation: Should fail with non-retryable error");
242 info!("- Custom retry config: Demonstrates configuration options");
243
244 Ok(())
245}