mcpls_core/mcp/
server.rs

1//! MCP server implementation using rmcp.
2//!
3//! This module provides the MCP server that exposes LSP capabilities
4//! as MCP tools using the rmcp SDK.
5
6use std::sync::Arc;
7
8use rmcp::handler::server::wrapper::Parameters;
9use rmcp::model::{Implementation, ProtocolVersion, ServerCapabilities, ServerInfo};
10use rmcp::{ErrorData as McpError, ServerHandler, tool, tool_handler, tool_router};
11use tokio::sync::Mutex;
12
13use super::handlers::HandlerContext;
14use super::tools::{
15    CachedDiagnosticsParams, CallHierarchyCallsParams, CallHierarchyPrepareParams,
16    CodeActionsParams, CompletionsParams, DefinitionParams, DiagnosticsParams,
17    DocumentSymbolsParams, FormatDocumentParams, HoverParams, ReferencesParams, RenameParams,
18    ServerLogsParams, ServerMessagesParams, WorkspaceSymbolParams,
19};
20use crate::bridge::Translator;
21
22/// MCP server that exposes LSP capabilities as tools.
23#[derive(Clone)]
24pub struct McplsServer {
25    context: Arc<HandlerContext>,
26    tool_router: rmcp::handler::server::router::tool::ToolRouter<Self>,
27}
28
29#[tool_router]
30impl McplsServer {
31    /// Create a new MCP server with the given translator.
32    #[must_use]
33    pub fn new(translator: Arc<Mutex<Translator>>) -> Self {
34        let context = Arc::new(HandlerContext::new(translator));
35        Self {
36            context,
37            tool_router: Self::tool_router(),
38        }
39    }
40
41    /// Get hover information at a position in a file.
42    #[tool(
43        description = "Get hover information (type, documentation) at a position in a file. Returns type signatures, documentation comments, and inferred types for the symbol under cursor. Use this to understand what a variable, function, or type represents without navigating to its definition."
44    )]
45    async fn get_hover(
46        &self,
47        Parameters(HoverParams {
48            file_path,
49            line,
50            character,
51        }): Parameters<HoverParams>,
52    ) -> Result<String, McpError> {
53        let result = {
54            let mut translator = self.context.translator.lock().await;
55            translator.handle_hover(file_path, line, character).await
56        };
57
58        match result {
59            Ok(value) => serde_json::to_string(&value)
60                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
61            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
62        }
63    }
64
65    /// Get the definition location of a symbol.
66    #[tool(
67        description = "Get the definition location of a symbol at the specified position. Returns file path, line, and character where the symbol (function, variable, type, etc.) is defined. Use this to navigate from a symbol usage to its original declaration or implementation."
68    )]
69    async fn get_definition(
70        &self,
71        Parameters(DefinitionParams {
72            file_path,
73            line,
74            character,
75        }): Parameters<DefinitionParams>,
76    ) -> Result<String, McpError> {
77        let result = {
78            let mut translator = self.context.translator.lock().await;
79            translator
80                .handle_definition(file_path, line, character)
81                .await
82        };
83
84        match result {
85            Ok(value) => serde_json::to_string(&value)
86                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
87            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
88        }
89    }
90
91    /// Find all references to a symbol.
92    #[tool(
93        description = "Find all references to a symbol at the specified position. Returns a list of all locations (file, line, character) where the symbol is used across the workspace. Use this to understand how widely a function/variable/type is used before refactoring, or to find all call sites of a function."
94    )]
95    async fn get_references(
96        &self,
97        Parameters(ReferencesParams {
98            file_path,
99            line,
100            character,
101            include_declaration,
102        }): Parameters<ReferencesParams>,
103    ) -> Result<String, McpError> {
104        let result = {
105            let mut translator = self.context.translator.lock().await;
106            translator
107                .handle_references(file_path, line, character, include_declaration)
108                .await
109        };
110
111        match result {
112            Ok(value) => serde_json::to_string(&value)
113                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
114            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
115        }
116    }
117
118    /// Get diagnostics for a file.
119    #[tool(
120        description = "Get diagnostics (errors, warnings) for a file. Triggers language server analysis and returns compilation errors, warnings, hints, and other issues with severity, message, and location. Use this to check code for problems before running or after making changes."
121    )]
122    async fn get_diagnostics(
123        &self,
124        Parameters(DiagnosticsParams { file_path }): Parameters<DiagnosticsParams>,
125    ) -> Result<String, McpError> {
126        let result = {
127            let mut translator = self.context.translator.lock().await;
128            translator.handle_diagnostics(file_path).await
129        };
130
131        match result {
132            Ok(value) => serde_json::to_string(&value)
133                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
134            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
135        }
136    }
137
138    /// Rename a symbol across the workspace.
139    #[tool(
140        description = "Rename a symbol across the workspace. Returns a list of text edits to apply across all files where the symbol is used. This is a safe refactoring operation that updates the symbol name consistently in declarations, usages, imports, and documentation. Use this instead of find-and-replace for reliable renaming."
141    )]
142    async fn rename_symbol(
143        &self,
144        Parameters(RenameParams {
145            file_path,
146            line,
147            character,
148            new_name,
149        }): Parameters<RenameParams>,
150    ) -> Result<String, McpError> {
151        let result = {
152            let mut translator = self.context.translator.lock().await;
153            translator
154                .handle_rename(file_path, line, character, new_name)
155                .await
156        };
157
158        match result {
159            Ok(value) => serde_json::to_string(&value)
160                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
161            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
162        }
163    }
164
165    /// Get code completion suggestions.
166    #[tool(
167        description = "Get code completion suggestions at a position in a file. Returns available completions including methods, functions, variables, types, keywords, and snippets with their documentation and type information. Use after typing a dot, colon, or partial identifier to see what can be inserted."
168    )]
169    async fn get_completions(
170        &self,
171        Parameters(CompletionsParams {
172            file_path,
173            line,
174            character,
175            trigger,
176        }): Parameters<CompletionsParams>,
177    ) -> Result<String, McpError> {
178        let result = {
179            let mut translator = self.context.translator.lock().await;
180            translator
181                .handle_completions(file_path, line, character, trigger)
182                .await
183        };
184
185        match result {
186            Ok(value) => serde_json::to_string(&value)
187                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
188            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
189        }
190    }
191
192    /// Get all symbols in a document.
193    #[tool(
194        description = "Get all symbols (functions, classes, variables) in a document. Returns a hierarchical outline of the file including functions, methods, classes, structs, enums, constants, and their locations. Use this to understand file structure, navigate to specific symbols, or get an overview of what a file contains."
195    )]
196    async fn get_document_symbols(
197        &self,
198        Parameters(DocumentSymbolsParams { file_path }): Parameters<DocumentSymbolsParams>,
199    ) -> Result<String, McpError> {
200        let result = {
201            let mut translator = self.context.translator.lock().await;
202            translator.handle_document_symbols(file_path).await
203        };
204
205        match result {
206            Ok(value) => serde_json::to_string(&value)
207                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
208            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
209        }
210    }
211
212    /// Format a document according to language server rules.
213    #[tool(
214        description = "Format a document according to the language server's formatting rules. Returns a list of text edits to apply for proper indentation, spacing, and style. The formatting follows language-specific conventions (rustfmt for Rust, prettier for JS/TS, etc.). Use this to automatically fix code style issues."
215    )]
216    async fn format_document(
217        &self,
218        Parameters(FormatDocumentParams {
219            file_path,
220            tab_size,
221            insert_spaces,
222        }): Parameters<FormatDocumentParams>,
223    ) -> Result<String, McpError> {
224        let result = {
225            let mut translator = self.context.translator.lock().await;
226            translator
227                .handle_format_document(file_path, tab_size, insert_spaces)
228                .await
229        };
230
231        match result {
232            Ok(value) => serde_json::to_string(&value)
233                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
234            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
235        }
236    }
237
238    /// Search for symbols across the workspace.
239    #[tool(
240        description = "Search for symbols across the entire workspace by name or pattern. Supports partial matching and fuzzy search to find functions, types, constants, etc. by name without knowing their exact location. Use this when you know the name of something but not which file it's in, or to discover related symbols."
241    )]
242    async fn workspace_symbol_search(
243        &self,
244        Parameters(WorkspaceSymbolParams {
245            query,
246            kind_filter,
247            limit,
248        }): Parameters<WorkspaceSymbolParams>,
249    ) -> Result<String, McpError> {
250        let result = {
251            let mut translator = self.context.translator.lock().await;
252            translator
253                .handle_workspace_symbol(query, kind_filter, limit)
254                .await
255        };
256
257        match result {
258            Ok(value) => serde_json::to_string(&value)
259                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
260            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
261        }
262    }
263
264    /// Get code actions for a range.
265    #[tool(
266        description = "Get available code actions (quick fixes, refactorings) for a range in a file. Returns suggested fixes for diagnostics, refactoring options (extract function, inline variable), and source actions (organize imports, generate code). Each action includes edits to apply. Use this to get IDE-style automated fixes and refactorings."
267    )]
268    async fn get_code_actions(
269        &self,
270        Parameters(CodeActionsParams {
271            file_path,
272            start_line,
273            start_character,
274            end_line,
275            end_character,
276            kind_filter,
277        }): Parameters<CodeActionsParams>,
278    ) -> Result<String, McpError> {
279        let result = {
280            let mut translator = self.context.translator.lock().await;
281            translator
282                .handle_code_actions(
283                    file_path,
284                    start_line,
285                    start_character,
286                    end_line,
287                    end_character,
288                    kind_filter,
289                )
290                .await
291        };
292
293        match result {
294            Ok(value) => serde_json::to_string(&value)
295                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
296            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
297        }
298    }
299
300    /// Prepare call hierarchy at a position.
301    #[tool(
302        description = "Prepare call hierarchy at a position, returns callable items. This is the first step for analyzing function call relationships. Returns a call hierarchy item that can be passed to get_incoming_calls or get_outgoing_calls. Use this on a function to start exploring its callers or callees."
303    )]
304    async fn prepare_call_hierarchy(
305        &self,
306        Parameters(CallHierarchyPrepareParams {
307            file_path,
308            line,
309            character,
310        }): Parameters<CallHierarchyPrepareParams>,
311    ) -> Result<String, McpError> {
312        let result = {
313            let mut translator = self.context.translator.lock().await;
314            translator
315                .handle_call_hierarchy_prepare(file_path, line, character)
316                .await
317        };
318
319        match result {
320            Ok(value) => serde_json::to_string(&value)
321                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
322            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
323        }
324    }
325
326    /// Get incoming calls (callers).
327    #[tool(
328        description = "Get functions that call the specified item (callers). Takes a call hierarchy item from prepare_call_hierarchy and returns all functions/methods that call it. Use this to trace backwards through the call graph and understand how a function is invoked and from where."
329    )]
330    async fn get_incoming_calls(
331        &self,
332        Parameters(CallHierarchyCallsParams { item }): Parameters<CallHierarchyCallsParams>,
333    ) -> Result<String, McpError> {
334        let result = {
335            let mut translator = self.context.translator.lock().await;
336            translator.handle_incoming_calls(item).await
337        };
338
339        match result {
340            Ok(value) => serde_json::to_string(&value)
341                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
342            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
343        }
344    }
345
346    /// Get outgoing calls (callees).
347    #[tool(
348        description = "Get functions called by the specified item (callees). Takes a call hierarchy item from prepare_call_hierarchy and returns all functions/methods it calls. Use this to trace forward through the call graph and understand what dependencies a function has."
349    )]
350    async fn get_outgoing_calls(
351        &self,
352        Parameters(CallHierarchyCallsParams { item }): Parameters<CallHierarchyCallsParams>,
353    ) -> Result<String, McpError> {
354        let result = {
355            let mut translator = self.context.translator.lock().await;
356            translator.handle_outgoing_calls(item).await
357        };
358
359        match result {
360            Ok(value) => serde_json::to_string(&value)
361                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
362            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
363        }
364    }
365
366    /// Get cached diagnostics for a file.
367    #[tool(
368        description = "Get cached diagnostics for a file from LSP server notifications. Returns diagnostics that were pushed by the language server (rather than requested on-demand). This is faster than get_diagnostics as it uses cached data. Use this to quickly check recent errors/warnings without triggering new analysis."
369    )]
370    async fn get_cached_diagnostics(
371        &self,
372        Parameters(CachedDiagnosticsParams { file_path }): Parameters<CachedDiagnosticsParams>,
373    ) -> Result<String, McpError> {
374        let result = {
375            let mut translator = self.context.translator.lock().await;
376            translator.handle_cached_diagnostics(&file_path)
377        };
378
379        match result {
380            Ok(value) => serde_json::to_string(&value)
381                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
382            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
383        }
384    }
385
386    /// Get recent LSP server log messages.
387    #[tool(
388        description = "Get recent LSP server log messages with optional level filtering. Returns internal log messages from the language server for debugging LSP issues. Filter by level (error, warning, info, debug) to focus on relevant messages. Use this to diagnose why the language server might not be working correctly."
389    )]
390    async fn get_server_logs(
391        &self,
392        Parameters(ServerLogsParams { limit, min_level }): Parameters<ServerLogsParams>,
393    ) -> Result<String, McpError> {
394        let result = {
395            let mut translator = self.context.translator.lock().await;
396            translator.handle_server_logs(limit, min_level)
397        };
398
399        match result {
400            Ok(value) => serde_json::to_string(&value)
401                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
402            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
403        }
404    }
405
406    /// Get recent LSP server messages.
407    #[tool(
408        description = "Get recent LSP server messages (showMessage notifications). Returns user-facing messages from the language server like prompts, warnings, and status updates that would normally appear in IDE popups. Use this to see important messages the language server wanted to communicate."
409    )]
410    async fn get_server_messages(
411        &self,
412        Parameters(ServerMessagesParams { limit }): Parameters<ServerMessagesParams>,
413    ) -> Result<String, McpError> {
414        let result = {
415            let mut translator = self.context.translator.lock().await;
416            translator.handle_server_messages(limit)
417        };
418
419        match result {
420            Ok(value) => serde_json::to_string(&value)
421                .map_err(|e| McpError::internal_error(format!("Serialization error: {e}"), None)),
422            Err(e) => Err(McpError::internal_error(e.to_string(), None)),
423        }
424    }
425}
426
427#[tool_handler]
428impl ServerHandler for McplsServer {
429    fn get_info(&self) -> ServerInfo {
430        ServerInfo {
431            protocol_version: ProtocolVersion::V_2024_11_05,
432            capabilities: ServerCapabilities::builder().enable_tools().build(),
433            server_info: Implementation {
434                name: "mcpls".to_string(),
435                title: Some("MCPLS - MCP to LSP Bridge".to_string()),
436                version: env!("CARGO_PKG_VERSION").to_string(),
437                icons: None,
438                website_url: Some("https://github.com/bug-ops/mcpls".to_string()),
439            },
440            instructions: Some(
441                concat!(
442                    "Universal MCP to LSP bridge. Exposes Language Server Protocol ",
443                    "capabilities as MCP tools for semantic code intelligence. ",
444                    "Supports hover, definition, references, diagnostics, rename, ",
445                    "completions, symbols, and formatting."
446                )
447                .to_string(),
448            ),
449        }
450    }
451}
452
453#[cfg(test)]
454#[allow(clippy::unwrap_used)]
455mod tests {
456    use super::*;
457
458    fn create_test_server() -> McplsServer {
459        let translator = Translator::new();
460        McplsServer::new(Arc::new(Mutex::new(translator)))
461    }
462
463    #[tokio::test]
464    async fn test_server_info() {
465        let server = create_test_server();
466        let info = server.get_info();
467
468        assert_eq!(info.protocol_version, ProtocolVersion::V_2024_11_05);
469        assert!(info.capabilities.tools.is_some());
470        assert_eq!(info.server_info.name, "mcpls");
471        assert!(info.instructions.is_some());
472    }
473
474    #[tokio::test]
475    async fn test_hover_tool_with_params() {
476        let server = create_test_server();
477        let params = Parameters(HoverParams {
478            file_path: "/nonexistent/file.rs".to_string(),
479            line: 1,
480            character: 1,
481        });
482
483        // This should return an error (no LSP server configured)
484        let result = server.get_hover(params).await;
485        assert!(result.is_err());
486    }
487
488    #[tokio::test]
489    async fn test_definition_tool_with_params() {
490        let server = create_test_server();
491        let params = Parameters(DefinitionParams {
492            file_path: "/test/file.rs".to_string(),
493            line: 10,
494            character: 5,
495        });
496
497        let result = server.get_definition(params).await;
498        assert!(result.is_err());
499    }
500
501    #[tokio::test]
502    async fn test_references_tool_with_params() {
503        let server = create_test_server();
504        let params = Parameters(ReferencesParams {
505            file_path: "/test/file.rs".to_string(),
506            line: 10,
507            character: 5,
508            include_declaration: false,
509        });
510
511        let result = server.get_references(params).await;
512        assert!(result.is_err());
513    }
514
515    #[tokio::test]
516    async fn test_diagnostics_tool_with_params() {
517        let server = create_test_server();
518        let params = Parameters(DiagnosticsParams {
519            file_path: "/test/file.rs".to_string(),
520        });
521
522        let result = server.get_diagnostics(params).await;
523        assert!(result.is_err());
524    }
525
526    #[tokio::test]
527    async fn test_rename_tool_with_params() {
528        let server = create_test_server();
529        let params = Parameters(RenameParams {
530            file_path: "/test/file.rs".to_string(),
531            line: 10,
532            character: 5,
533            new_name: "new_name".to_string(),
534        });
535
536        let result = server.rename_symbol(params).await;
537        assert!(result.is_err());
538    }
539
540    #[tokio::test]
541    async fn test_completions_tool_with_params() {
542        let server = create_test_server();
543        let params = Parameters(CompletionsParams {
544            file_path: "/test/file.rs".to_string(),
545            line: 10,
546            character: 5,
547            trigger: None,
548        });
549
550        let result = server.get_completions(params).await;
551        assert!(result.is_err());
552    }
553
554    #[tokio::test]
555    async fn test_document_symbols_tool_with_params() {
556        let server = create_test_server();
557        let params = Parameters(DocumentSymbolsParams {
558            file_path: "/test/file.rs".to_string(),
559        });
560
561        let result = server.get_document_symbols(params).await;
562        assert!(result.is_err());
563    }
564
565    #[tokio::test]
566    async fn test_format_document_tool_with_params() {
567        let server = create_test_server();
568        let params = Parameters(FormatDocumentParams {
569            file_path: "/test/file.rs".to_string(),
570            tab_size: 4,
571            insert_spaces: true,
572        });
573
574        let result = server.format_document(params).await;
575        assert!(result.is_err());
576    }
577
578    #[tokio::test]
579    async fn test_workspace_symbol_search_tool_with_params() {
580        let server = create_test_server();
581        let params = Parameters(WorkspaceSymbolParams {
582            query: "User".to_string(),
583            kind_filter: None,
584            limit: 100,
585        });
586        let result = server.workspace_symbol_search(params).await;
587        assert!(result.is_err());
588    }
589
590    #[tokio::test]
591    async fn test_code_actions_tool_with_params() {
592        let server = create_test_server();
593        let params = Parameters(CodeActionsParams {
594            file_path: "/test/file.rs".to_string(),
595            start_line: 10,
596            start_character: 5,
597            end_line: 10,
598            end_character: 15,
599            kind_filter: None,
600        });
601        let result = server.get_code_actions(params).await;
602        assert!(result.is_err());
603    }
604
605    #[tokio::test]
606    async fn test_prepare_call_hierarchy_tool_with_params() {
607        let server = create_test_server();
608        let params = Parameters(CallHierarchyPrepareParams {
609            file_path: "/test/file.rs".to_string(),
610            line: 10,
611            character: 5,
612        });
613        let result = server.prepare_call_hierarchy(params).await;
614        assert!(result.is_err());
615    }
616
617    #[tokio::test]
618    async fn test_incoming_calls_tool_with_params() {
619        let server = create_test_server();
620        let item = serde_json::json!({
621            "name": "test_function",
622            "kind": 12,
623            "uri": "file:///test/file.rs",
624            "range": {
625                "start": {"line": 0, "character": 0},
626                "end": {"line": 0, "character": 10}
627            },
628            "selectionRange": {
629                "start": {"line": 0, "character": 0},
630                "end": {"line": 0, "character": 10}
631            }
632        });
633        let params = Parameters(CallHierarchyCallsParams { item });
634        let result = server.get_incoming_calls(params).await;
635        assert!(result.is_err());
636    }
637
638    #[tokio::test]
639    async fn test_outgoing_calls_tool_with_params() {
640        let server = create_test_server();
641        let item = serde_json::json!({
642            "name": "test_function",
643            "kind": 12,
644            "uri": "file:///test/file.rs",
645            "range": {
646                "start": {"line": 0, "character": 0},
647                "end": {"line": 0, "character": 10}
648            },
649            "selectionRange": {
650                "start": {"line": 0, "character": 0},
651                "end": {"line": 0, "character": 10}
652            }
653        });
654        let params = Parameters(CallHierarchyCallsParams { item });
655        let result = server.get_outgoing_calls(params).await;
656        assert!(result.is_err());
657    }
658
659    #[tokio::test]
660    async fn test_cached_diagnostics_tool_with_params() {
661        use std::fs;
662
663        use tempfile::TempDir;
664
665        let server = create_test_server();
666
667        let temp_dir = TempDir::new().unwrap();
668        let test_file = temp_dir.path().join("test.rs");
669        fs::write(&test_file, "fn main() {}").unwrap();
670
671        let params = Parameters(CachedDiagnosticsParams {
672            file_path: test_file.to_str().unwrap().to_string(),
673        });
674
675        let result = server.get_cached_diagnostics(params).await;
676        assert!(result.is_ok());
677
678        let json_str = result.unwrap();
679        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
680        assert!(parsed.get("diagnostics").is_some());
681    }
682
683    #[tokio::test]
684    async fn test_cached_diagnostics_tool_nonexistent_file() {
685        let server = create_test_server();
686        let params = Parameters(CachedDiagnosticsParams {
687            file_path: "/nonexistent/file.rs".to_string(),
688        });
689
690        let result = server.get_cached_diagnostics(params).await;
691        assert!(result.is_err());
692    }
693
694    #[tokio::test]
695    async fn test_server_logs_tool_with_default_params() {
696        let server = create_test_server();
697        let params = Parameters(ServerLogsParams {
698            limit: 50,
699            min_level: None,
700        });
701
702        let result = server.get_server_logs(params).await;
703        assert!(result.is_ok());
704
705        let json_str = result.unwrap();
706        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
707        assert!(parsed.get("logs").is_some());
708    }
709
710    #[tokio::test]
711    async fn test_server_logs_tool_with_error_level() {
712        let server = create_test_server();
713        let params = Parameters(ServerLogsParams {
714            limit: 10,
715            min_level: Some("error".to_string()),
716        });
717
718        let result = server.get_server_logs(params).await;
719        assert!(result.is_ok());
720
721        let json_str = result.unwrap();
722        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
723        let logs = parsed.get("logs").unwrap().as_array().unwrap();
724        assert_eq!(logs.len(), 0);
725    }
726
727    #[tokio::test]
728    async fn test_server_logs_tool_with_warning_level() {
729        let server = create_test_server();
730        let params = Parameters(ServerLogsParams {
731            limit: 100,
732            min_level: Some("warning".to_string()),
733        });
734
735        let result = server.get_server_logs(params).await;
736        assert!(result.is_ok());
737    }
738
739    #[tokio::test]
740    async fn test_server_logs_tool_with_info_level() {
741        let server = create_test_server();
742        let params = Parameters(ServerLogsParams {
743            limit: 50,
744            min_level: Some("info".to_string()),
745        });
746
747        let result = server.get_server_logs(params).await;
748        assert!(result.is_ok());
749    }
750
751    #[tokio::test]
752    async fn test_server_logs_tool_with_debug_level() {
753        let server = create_test_server();
754        let params = Parameters(ServerLogsParams {
755            limit: 20,
756            min_level: Some("debug".to_string()),
757        });
758
759        let result = server.get_server_logs(params).await;
760        assert!(result.is_ok());
761    }
762
763    #[tokio::test]
764    async fn test_server_logs_tool_with_invalid_level() {
765        let server = create_test_server();
766        let params = Parameters(ServerLogsParams {
767            limit: 10,
768            min_level: Some("invalid_level".to_string()),
769        });
770
771        let result = server.get_server_logs(params).await;
772        assert!(result.is_err());
773    }
774
775    #[tokio::test]
776    async fn test_server_logs_tool_with_zero_limit() {
777        let server = create_test_server();
778        let params = Parameters(ServerLogsParams {
779            limit: 0,
780            min_level: None,
781        });
782
783        let result = server.get_server_logs(params).await;
784        assert!(result.is_ok());
785
786        let json_str = result.unwrap();
787        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
788        let logs = parsed.get("logs").unwrap().as_array().unwrap();
789        assert_eq!(logs.len(), 0);
790    }
791
792    #[tokio::test]
793    async fn test_server_messages_tool_with_default_params() {
794        let server = create_test_server();
795        let params = Parameters(ServerMessagesParams { limit: 20 });
796
797        let result = server.get_server_messages(params).await;
798        assert!(result.is_ok());
799
800        let json_str = result.unwrap();
801        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
802        assert!(parsed.get("messages").is_some());
803    }
804
805    #[tokio::test]
806    async fn test_server_messages_tool_with_custom_limit() {
807        let server = create_test_server();
808        let params = Parameters(ServerMessagesParams { limit: 5 });
809
810        let result = server.get_server_messages(params).await;
811        assert!(result.is_ok());
812
813        let json_str = result.unwrap();
814        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
815        let messages = parsed.get("messages").unwrap().as_array().unwrap();
816        assert_eq!(messages.len(), 0);
817    }
818
819    #[tokio::test]
820    async fn test_server_messages_tool_with_zero_limit() {
821        let server = create_test_server();
822        let params = Parameters(ServerMessagesParams { limit: 0 });
823
824        let result = server.get_server_messages(params).await;
825        assert!(result.is_ok());
826
827        let json_str = result.unwrap();
828        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
829        let messages = parsed.get("messages").unwrap().as_array().unwrap();
830        assert_eq!(messages.len(), 0);
831    }
832
833    #[tokio::test]
834    async fn test_server_messages_tool_with_large_limit() {
835        let server = create_test_server();
836        let params = Parameters(ServerMessagesParams { limit: 1000 });
837
838        let result = server.get_server_messages(params).await;
839        assert!(result.is_ok());
840    }
841}