solidmcp 0.4.0

A high-level Rust toolkit for building Model Context Protocol (MCP) servers with type safety and minimal boilerplate. Supports tools, resources, and prompts with automatic JSON schema generation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
//! Comprehensive MCP Debugging Test
//!
//! This test systematically diagnoses MCP (Model Context Protocol)
//! integration issues in the solidmcp project. It provides detailed diagnostics
//! for connection, protocol, and tool execution problems.

mod mcp_test_helpers;
use futures_util::{SinkExt, StreamExt};
use mcp_test_helpers::{
    init_test_tracing, initialize_mcp_connection_with_server, receive_ws_message,
    with_mcp_test_server,
};
use serde_json::{json, Value};
use std::time::Duration;
// use tokio::time::timeout; // Commented out unused import
use tokio_tungstenite::tungstenite::Message;
use tracing::{debug, error, info, warn};

/// MCP Debugging Test Suite
///
/// This test systematically checks all aspects of MCP functionality:
/// 1. Server startup and availability
/// 2. WebSocket connection establishment
/// 3. Protocol handshake and initialization
/// 4. Tool discovery and listing
/// 5. Tool execution (echo and read_file)
/// 6. Error handling and recovery
/// 7. Performance and resource usage
#[tokio::test]
async fn test_mcp_comprehensive_debugging() {
    init_test_tracing();
    info!("🚀 Starting comprehensive MCP debugging test");

    // Phase 1: Server Availability Check
    let server_check = check_server_availability().await;
    if let Err(e) = server_check {
        error!("❌ Server availability check failed: {}", e);
        panic!("Server not available: {e}");
    }
    info!("✅ Server availability confirmed");

    // Phase 2: WebSocket Connection Test
    let ws_test = test_websocket_connection().await;
    if let Err(e) = ws_test {
        error!("❌ WebSocket connection test failed: {}", e);
        panic!("WebSocket connection failed: {e}");
    }
    info!("✅ WebSocket connection established");

    // Phase 3: Protocol Handshake Test
    let protocol_test = test_protocol_handshake().await;
    if let Err(e) = protocol_test {
        error!("❌ Protocol handshake test failed: {}", e);
        panic!("Protocol handshake failed: {e}");
    }
    info!("✅ Protocol handshake successful");

    // Phase 4: Tool Discovery Test
    let tools_test = test_tool_discovery().await;
    if let Err(e) = tools_test {
        error!("❌ Tool discovery test failed: {}", e);
        panic!("Tool discovery failed: {e}");
    }
    info!("✅ Tool discovery successful");

    // Phase 5: Tool Execution Test
    let execution_test = test_tool_execution().await;
    if let Err(e) = execution_test {
        error!("❌ Tool execution test failed: {}", e);
        panic!("Tool execution failed: {e}");
    }
    info!("✅ Tool execution successful");

    // Phase 6: Error Handling Test
    let error_test = test_error_handling().await;
    if let Err(e) = error_test {
        error!("❌ Error handling test failed: {}", e);
        panic!("Error handling failed: {e}");
    }
    info!("✅ Error handling validated");

    // Phase 7: Performance Test
    let perf_test = test_performance().await;
    if let Err(e) = perf_test {
        warn!("⚠️ Performance test failed: {}", e);
        // Don't fail the test for performance issues, just warn
    } else {
        info!("✅ Performance test passed");
    }

    info!("🎉 All MCP debugging tests completed successfully!");
}

/// Check if the MCP server is available by starting a test instance
async fn check_server_availability() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("🔍 Checking server availability...");

    // Try to start a test server instance
    with_mcp_test_server("availability_check", |server| async move {
        debug!("✅ Server is responding on port {}", server.port);
        Ok(())
    })
    .await?;

    debug!("✅ Server availability confirmed");
    Ok(())
}

/// Test basic WebSocket connection
async fn test_websocket_connection() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("🔌 Testing WebSocket connection...");

    with_mcp_test_server("websocket_test", |server| async move {
        let (_ws_stream, _) = tokio_tungstenite::connect_async(&server.ws_url()).await?;
        debug!("✅ WebSocket connection established");
        Ok(())
    })
    .await?;

    Ok(())
}

/// Test protocol handshake
async fn test_protocol_handshake() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("🤝 Testing protocol handshake...");

    with_mcp_test_server("handshake_test", |server| async move {
        let (ws_stream, _) = tokio_tungstenite::connect_async(&server.ws_url()).await?;
        let (mut write, mut read) = ws_stream.split();

        // Send initialize message
        let init_message = json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
                "protocolVersion": "2025-06-18",
                "capabilities": {},
                "clientInfo": {"name": "handshake-test", "version": "1.0.0"}
            }
        });

        write
            .send(Message::Text(serde_json::to_string(&init_message)?.into()))
            .await?;
        let _init_response = receive_ws_message(&mut read, Duration::from_secs(5)).await?;

        debug!("✅ Protocol handshake successful");
        Ok(())
    })
    .await?;

    Ok(())
}

/// Test tool discovery
async fn test_tool_discovery() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("🔍 Testing tool discovery...");

    with_mcp_test_server("tool_discovery_test", |server| async move {
        let (write, read) = initialize_mcp_connection_with_server(&server).await?;
        let (mut write, mut read) = (write, read);

        // Request tools list
        let tools_message = json!({
            "jsonrpc": "2.0",
            "id": 2,
            "method": "tools/list",
            "params": {}
        });

        write
            .send(Message::Text(serde_json::to_string(&tools_message)?.into()))
            .await?;

        let text = receive_ws_message(&mut read, Duration::from_secs(5)).await?;
        let response: Value = serde_json::from_str(&text)?;

        if response["jsonrpc"] != "2.0" || response["id"] != 2 {
            return Err("Invalid tools list response".into());
        }

        let tools = &response["result"]["tools"];
        if !tools.is_array() {
            return Err("Tools list response missing tools array".into());
        }

        let tool_names: Vec<String> = tools
            .as_array()
            .unwrap()
            .iter()
            .filter_map(|tool| tool["name"].as_str().map(|s| s.to_string()))
            .collect();

        if !tool_names.contains(&"echo".to_string()) {
            return Err("Echo tool not found in tools list".into());
        }

        if !tool_names.contains(&"read_file".to_string()) {
            return Err("Read file tool not found in tools list".into());
        }

        debug!("✅ Tool discovery successful: found {:?}", tool_names);
        Ok(())
    })
    .await?;

    Ok(())
}

/// Test tool execution (echo and read_file)
async fn test_tool_execution() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("🛠️ Testing tool execution...");

    with_mcp_test_server("tool_execution_test", |server| async move {
        let (write, read) = initialize_mcp_connection_with_server(&server).await?;
        let (mut write, mut read) = (write, read);

        // Test echo tool
        let echo_message = json!({
            "jsonrpc": "2.0",
            "id": 2,
            "method": "tools/call",
            "params": {
                "name": "echo",
                "arguments": {
                    "message": "Hello from MCP debugging test!"
                }
            }
        });

        write
            .send(Message::Text(serde_json::to_string(&echo_message)?.into()))
            .await?;

        let text = receive_ws_message(&mut read, Duration::from_secs(5)).await?;
        let response: Value = serde_json::from_str(&text)?;

        if response["jsonrpc"] != "2.0" || response["id"] != 2 {
            return Err("Invalid echo tool response".into());
        }

        let content = &response["result"]["content"][0]["text"];
        if !content.is_string() {
            return Err("Echo tool response missing content".into());
        }

        let content_str = content.as_str().unwrap();
        let parsed: Value = serde_json::from_str(content_str)?;

        if parsed["echo"] != "Hello from MCP debugging test!" {
            return Err("Echo tool returned unexpected message".into());
        }

        debug!("✅ Echo tool execution successful");

        // Test read_file tool (read Cargo.toml)
        let read_message = json!({
            "jsonrpc": "2.0",
            "id": 3,
            "method": "tools/call",
            "params": {
                "name": "read_file",
                "arguments": {
                    "file_path": "Cargo.toml"
                }
            }
        });

        write
            .send(Message::Text(serde_json::to_string(&read_message)?.into()))
            .await?;

        let text = receive_ws_message(&mut read, Duration::from_secs(5)).await?;
        let response: Value = serde_json::from_str(&text)?;

        if response["jsonrpc"] != "2.0" || response["id"] != 3 {
            return Err("Invalid read_file tool response".into());
        }

        let content = &response["result"]["content"][0]["text"];
        if !content.is_string() {
            return Err("Read file tool response missing content".into());
        }

        let content_str = content.as_str().unwrap();
        let parsed: Value = serde_json::from_str(content_str)?;

        if parsed["file_path"] != "Cargo.toml" {
            return Err("Read file tool returned wrong file path".into());
        }

        if !parsed["content"].is_string() {
            return Err("Read file tool response missing file content".into());
        }

        debug!("✅ Read file tool execution successful");
        Ok(())
    })
    .await?;

    Ok(())
}

/// Test error handling with invalid requests
async fn test_error_handling() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("🚫 Testing error handling...");

    with_mcp_test_server("error_handling_test", |server| async move {
        let (write, read) = initialize_mcp_connection_with_server(&server).await?;
        let (mut write, mut read) = (write, read);

        // Test unknown method
        let unknown_message = json!({
            "jsonrpc": "2.0",
            "id": 2,
            "method": "unknown_method",
            "params": {}
        });

        write
            .send(Message::Text(
                serde_json::to_string(&unknown_message)?.into(),
            ))
            .await?;

        let text = receive_ws_message(&mut read, Duration::from_secs(5)).await?;
        let response: Value = serde_json::from_str(&text)?;

        if response["jsonrpc"] != "2.0" || response["id"] != 2 {
            return Err("Invalid error response structure".into());
        }

        if !response["error"].is_object() {
            return Err("Error response missing error object".into());
        }

        let error_obj = &response["error"];
        if !error_obj["code"].is_number() || !error_obj["message"].is_string() {
            return Err("Error response missing code or message".into());
        }

        debug!("✅ Error handling for unknown method successful");

        // Test unknown tool
        let unknown_tool_message = json!({
            "jsonrpc": "2.0",
            "id": 3,
            "method": "tools/call",
            "params": {
                "name": "unknown_tool",
                "arguments": {}
            }
        });

        write
            .send(Message::Text(
                serde_json::to_string(&unknown_tool_message)?.into(),
            ))
            .await?;

        let text = receive_ws_message(&mut read, Duration::from_secs(5)).await?;
        let response: Value = serde_json::from_str(&text)?;

        if response["jsonrpc"] != "2.0" || response["id"] != 3 {
            return Err("Invalid tool error response structure".into());
        }

        if !response["error"].is_object() {
            return Err("Tool error response missing error object".into());
        }

        debug!("✅ Error handling for unknown tool successful");
        Ok(())
    })
    .await?;

    Ok(())
}

/// Test performance and resource usage
async fn test_performance() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    debug!("⚡ Testing performance...");

    let start_time = std::time::Instant::now();

    // Run multiple quick operations with a single server instance
    with_mcp_test_server("performance_test", |server| async move {
        for i in 0..5 {
            let (ws_stream, _) = tokio_tungstenite::connect_async(&server.ws_url()).await?;
            let (mut write, mut read) = ws_stream.split();

            // Quick initialize
            let init_message = json!({
                "jsonrpc": "2.0",
                "id": i,
                "method": "initialize",
                "params": {
                    "protocolVersion": "2025-06-18",
                    "capabilities": {},
                    "clientInfo": {"name": "perf-test", "version": "1.0.0"}
                }
            });

            write
                .send(Message::Text(serde_json::to_string(&init_message)?.into()))
                .await?;
            let _init_response = receive_ws_message(&mut read, Duration::from_secs(2)).await?;

            // Quick echo
            let echo_message = json!({
                "jsonrpc": "2.0",
                "id": i + 100,
                "method": "tools/call",
                "params": {
                    "name": "echo",
                    "arguments": {
                        "message": format!("Performance test {}", i)
                    }
                }
            });

            write
                .send(Message::Text(serde_json::to_string(&echo_message)?.into()))
                .await?;
            let _echo_response = receive_ws_message(&mut read, Duration::from_secs(2)).await?;
        }

        Ok(())
    })
    .await?;

    let elapsed = start_time.elapsed();

    if elapsed > Duration::from_secs(10) {
        debug!(
            "⚠️ Performance test took longer than expected: {:?}",
            elapsed
        );
    } else {
        debug!("✅ Performance test completed in {:?}", elapsed);
    }

    Ok(())
}