1use 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#[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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}