Skip to main content

prism_mcp_rs/server/
http_server.rs

1//! HTTP-specific MCP server implementation
2//!
3//! Module provides a specialized MCP server that integrates directly with HTTP transport.
4
5use crate::core::error::McpResult;
6use crate::protocol::types::{JsonRpcRequest, JsonRpcResponse};
7use crate::server::mcp_server::McpServer;
8use crate::transport::http::HttpServerTransport;
9use crate::transport::traits::ServerTransport;
10use std::sync::Arc;
11use tokio::sync::Mutex;
12
13/// HTTP-specific MCP server that properly integrates with HTTP transport
14pub struct HttpMcpServer {
15    server: Arc<Mutex<McpServer>>,
16    transport: Option<HttpServerTransport>,
17}
18
19impl HttpMcpServer {
20    /// Create a new HTTP MCP server
21    pub fn new(name: String, version: String) -> Self {
22        Self {
23            server: Arc::new(Mutex::new(McpServer::new(name, version))),
24            transport: None,
25        }
26    }
27
28    /// Get a reference to the underlying MCP server
29    pub async fn server(&self) -> Arc<Mutex<McpServer>> {
30        self.server.clone()
31    }
32
33    /// Start the HTTP server with proper request handling integration
34    pub async fn start(&mut self, mut transport: HttpServerTransport) -> McpResult<()> {
35        // Set up the request handler to use the MCP server
36        let server_clone = self.server.clone();
37
38        transport
39            .set_request_handler(move |request: JsonRpcRequest| {
40                let server = server_clone.clone();
41                let (tx, rx) = tokio::sync::oneshot::channel();
42
43                tokio::spawn(async move {
44                    let server_guard = server.lock().await;
45                    let response = server_guard
46                        .handle_request(request)
47                        .await
48                        .unwrap_or_else(|e| {
49                            tracing::error!("Error handling HTTP request: {}", e);
50                            JsonRpcResponse {
51                                jsonrpc: "2.0".to_string(),
52                                id: serde_json::Value::Null,
53                                result: Some(serde_json::json!({
54                                    "error": {
55                                        "code": -32603,
56                                        "message": e.to_string()
57                                    }
58                                })),
59                            }
60                        });
61                    let _ = tx.send(response);
62                });
63
64                rx
65            })
66            .await;
67
68        // Start the transport
69        transport.start().await?;
70
71        self.transport = Some(transport);
72        Ok(())
73    }
74
75    /// Stop the HTTP server
76    pub async fn stop(&mut self) -> McpResult<()> {
77        if let Some(transport) = &mut self.transport {
78            transport.stop().await?;
79        }
80        self.transport = None;
81        Ok(())
82    }
83
84    /// Check if the server is running
85    pub fn is_running(&self) -> bool {
86        self.transport.as_ref().is_some_and(|t| t.is_running())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use serde_json::json;
94    use std::sync::Arc;
95    use tokio::sync::Mutex;
96
97    #[tokio::test]
98    async fn test_http_server_creation() {
99        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
100
101        assert!(!server.is_running());
102        assert!(server.transport.is_none());
103
104        // Test that we can get a reference to the underlying server
105        let server_ref = server.server().await;
106        let server_guard = server_ref.lock().await;
107
108        // Server should start not running
109        assert!(!server_guard.is_running().await);
110
111        // But should be able to initialize
112        server_guard.initialize().await.unwrap();
113        assert!(server_guard.is_running().await);
114        assert!(server_guard.is_initialized().await);
115    }
116
117    #[tokio::test]
118    async fn test_http_server_initialization() {
119        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
120
121        // Create a mock transport for testing
122        // Since HttpServerTransport might require actual HTTP setup, we'll test what we can
123        assert!(!server.is_running());
124
125        // Test server reference
126        let server_ref = server.server().await;
127        let _server_guard = server_ref.lock().await;
128
129        // Verify the server integration works
130    }
131
132    #[tokio::test]
133    async fn test_http_server_stop_when_not_running() {
134        let mut server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
135
136        // Should be able to stop a server that's not running without error
137        let result = server.stop().await;
138        assert!(result.is_ok());
139        assert!(!server.is_running());
140    }
141
142    #[tokio::test]
143    async fn test_http_server_state_transitions() {
144        let mut server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
145
146        // Initial state: not running
147        assert!(!server.is_running());
148
149        // After stop (when not running): still not running
150        let _ = server.stop().await;
151        assert!(!server.is_running());
152
153        // Test that transport is properly managed
154        assert!(server.transport.is_none());
155    }
156
157    #[tokio::test]
158    async fn test_http_server_concurrent_access() {
159        let server = Arc::new(Mutex::new(HttpMcpServer::new(
160            "test-server".to_string(),
161            "1.0.0".to_string(),
162        )));
163
164        let server1 = server.clone();
165        let server2 = server.clone();
166
167        // Test concurrent access to server state
168        let handle1 = tokio::spawn(async move {
169            let s = server1.lock().await;
170            s.is_running()
171        });
172
173        let handle2 = tokio::spawn(async move {
174            let s = server2.lock().await;
175            s.server().await
176        });
177
178        let (running, _server_ref) = tokio::join!(handle1, handle2);
179        assert!(!running.unwrap());
180    }
181
182    #[tokio::test]
183    async fn test_http_server_request_handler_setup() {
184        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
185
186        // Test that the server correctly sets up request handling
187        let server_ref = server.server().await;
188        let server_guard = server_ref.lock().await;
189
190        // Initialize server for testing
191        server_guard.initialize().await.unwrap();
192        assert!(server_guard.is_running().await);
193
194        // Test adding a tool to verify request handling setup
195        server_guard
196            .add_tool_with_closure(
197                "test-tool",
198                Some("Test tool for request handling"),
199                serde_json::json!({}),
200                |_args| Ok(vec![crate::protocol::types::ContentBlock::text("handled")]),
201            )
202            .await
203            .unwrap();
204
205        assert!(server_guard.has_tool("test-tool").await);
206    }
207
208    #[tokio::test]
209    async fn test_http_server_error_handling() {
210        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
211
212        // Test error handling in request processing
213        // The server should handle errors smoothly and return proper JSON-RPC error responses
214
215        let server_ref = server.server().await;
216
217        // Test that invalid requests are handled properly
218        // Create a malformed request to test error handling
219        let invalid_request = JsonRpcRequest {
220            jsonrpc: "2.0".to_string(),
221            id: json!("test-id"),
222            method: "invalid/method".to_string(),
223            params: Some(json!({})),
224        };
225
226        // The server should handle this smoothly
227        let server_guard = server_ref.lock().await;
228        let response = server_guard.handle_request(invalid_request).await;
229
230        // Should return an error response, not panic
231        match response {
232            Ok(_) => {
233                // Valid response (could be an error response)
234            }
235            Err(_) => {
236                // Error in processing, which is acceptable for invalid requests
237            }
238        }
239    }
240
241    #[tokio::test]
242    async fn test_http_server_multiple_requests() {
243        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
244        let server_ref = server.server().await;
245
246        // Test handling multiple requests
247        let requests = vec![
248            JsonRpcRequest {
249                jsonrpc: "2.0".to_string(),
250                id: json!(1),
251                method: "ping".to_string(),
252                params: Some(json!({})),
253            },
254            JsonRpcRequest {
255                jsonrpc: "2.0".to_string(),
256                id: json!(2),
257                method: "tools/list".to_string(),
258                params: Some(json!({})),
259            },
260        ];
261
262        for request in requests {
263            let server_guard = server_ref.lock().await;
264            let _response = server_guard.handle_request(request).await;
265            // Each request should be handled without panicking
266        }
267    }
268
269    #[tokio::test]
270    async fn test_http_server_response_format() {
271        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
272        let server_ref = server.server().await;
273
274        // Test that responses are properly formatted JSON-RPC
275        let ping_request = JsonRpcRequest {
276            jsonrpc: "2.0".to_string(),
277            id: json!("ping-test"),
278            method: "ping".to_string(),
279            params: Some(json!({})),
280        };
281
282        let server_guard = server_ref.lock().await;
283        let response = server_guard.handle_request(ping_request).await;
284
285        match response {
286            Ok(json_response) => {
287                // Should be a valid JSON-RPC response
288                assert_eq!(json_response.jsonrpc, "2.0");
289                assert_eq!(json_response.id, json!("ping-test"));
290                // Should have either result or error, but not both
291                assert!(json_response.result.is_some());
292            }
293            Err(_) => {
294                // Error responses are also acceptable for some methods
295            }
296        }
297    }
298
299    #[test]
300    fn test_http_server_sync_methods() {
301        let server = HttpMcpServer::new("test-server".to_string(), "1.0.0".to_string());
302
303        // Test synchronous methods
304        assert!(!server.is_running());
305
306        // Test that the server maintains proper state
307        assert!(server.transport.is_none());
308    }
309
310    #[tokio::test]
311    async fn test_http_server_lifecycle() {
312        let mut server = HttpMcpServer::new("lifecycle-test".to_string(), "1.0.0".to_string());
313
314        // Test complete lifecycle: create -> (start) -> stop
315        // Note: We can't easily test start() without a real HttpServerTransport
316        // but we can test the stop() method and state management
317
318        assert!(!server.is_running());
319
320        // Test stop when not running
321        let stop_result = server.stop().await;
322        assert!(stop_result.is_ok());
323        assert!(!server.is_running());
324
325        // Test that server can be stopped multiple times safely
326        let stop_result2 = server.stop().await;
327        assert!(stop_result2.is_ok());
328        assert!(!server.is_running());
329    }
330}