1use std::collections::HashMap;
2
3use rmcp::{
4 ErrorData as McpError, RoleServer,
5 handler::server::{router::tool::ToolRouter, wrapper::Parameters},
6 model::*,
7 schemars,
8 service::RequestContext,
9 tool, tool_router,
10};
11use tracing::instrument;
12
13use super::core::{NeovimMcpServer, find_get_all_targets};
14use super::lua_tools;
15use crate::neovim::client::TypeHierarchyItem;
16use crate::neovim::{
17 CallHierarchyItem, CodeAction, DocumentIdentifier, FormattingOptions, NeovimClient, Position,
18 PrepareRenameResult, Range, WorkspaceEdit, string_or_struct,
19};
20
21#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
23pub struct ConnectNvimRequest {
24 pub target: String,
26}
27
28#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
30pub struct ConnectionRequest {
31 pub connection_id: String,
33}
34
35#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
37pub struct BufferRequest {
38 pub connection_id: String,
40 pub id: u64,
42}
43
44#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
46pub struct BufferReadRequest {
47 pub connection_id: String,
49 #[serde(deserialize_with = "string_or_struct")]
53 pub document: DocumentIdentifier,
54 #[serde(default)]
56 pub start: i64,
57 #[serde(default = "default_end_line")]
59 pub end: i64,
60}
61
62fn default_end_line() -> i64 {
63 -1
64}
65
66#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
68pub struct ExecuteLuaRequest {
69 pub connection_id: String,
71 pub code: String,
73}
74
75#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
77pub struct WaitForLspReadyRequest {
78 pub connection_id: String,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub client_name: Option<String>,
83 #[serde(default = "default_timeout")]
85 pub timeout_ms: u64,
86}
87
88fn default_timeout() -> u64 {
89 5000
90}
91
92#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
94pub struct WorkspaceSymbolsParams {
95 pub connection_id: String,
97 pub lsp_client_name: String,
99 pub query: String,
101}
102
103#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
105pub struct CodeActionsParams {
106 pub connection_id: String,
108 #[serde(deserialize_with = "string_or_struct")]
112 pub document: DocumentIdentifier,
113 pub lsp_client_name: String,
115 pub start_line: u64,
117 pub start_character: u64,
119 pub end_line: u64,
121 pub end_character: u64,
123}
124
125#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
127pub struct HoverParam {
128 pub connection_id: String,
130 #[serde(deserialize_with = "string_or_struct")]
134 pub document: DocumentIdentifier,
135 pub lsp_client_name: String,
137 #[serde(flatten)]
139 pub position: Position,
140}
141
142#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
144pub struct DocumentSymbolsParams {
145 pub connection_id: String,
147 #[serde(deserialize_with = "string_or_struct")]
151 pub document: DocumentIdentifier,
152 pub lsp_client_name: String,
154}
155
156#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
158pub struct ReferencesParams {
159 pub connection_id: String,
161 #[serde(deserialize_with = "string_or_struct")]
165 pub document: DocumentIdentifier,
166 pub lsp_client_name: String,
168 #[serde(flatten)]
170 pub position: Position,
171 pub include_declaration: bool,
173}
174
175#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
177pub struct DefinitionParams {
178 pub connection_id: String,
180 #[serde(deserialize_with = "string_or_struct")]
184 pub document: DocumentIdentifier,
185 pub lsp_client_name: String,
187 #[serde(flatten)]
189 pub position: Position,
190}
191
192#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
194pub struct TypeDefinitionParams {
195 pub connection_id: String,
197 #[serde(deserialize_with = "string_or_struct")]
201 pub document: DocumentIdentifier,
202 pub lsp_client_name: String,
204 #[serde(flatten)]
206 pub position: Position,
207}
208
209#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
211pub struct ImplementationParams {
212 pub connection_id: String,
214 #[serde(deserialize_with = "string_or_struct")]
218 pub document: DocumentIdentifier,
219 pub lsp_client_name: String,
221 #[serde(flatten)]
223 pub position: Position,
224}
225
226#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
228pub struct DeclarationParams {
229 pub connection_id: String,
231 #[serde(deserialize_with = "string_or_struct")]
235 pub document: DocumentIdentifier,
236 pub lsp_client_name: String,
238 #[serde(flatten)]
240 pub position: Position,
241}
242
243#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
245pub struct ResolveCodeActionParams {
246 pub connection_id: String,
248 pub lsp_client_name: String,
250 #[serde(deserialize_with = "string_or_struct")]
254 pub code_action: CodeAction,
255}
256
257#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
259pub struct ApplyWorkspaceEditParams {
260 pub connection_id: String,
262 pub lsp_client_name: String,
264 #[serde(deserialize_with = "string_or_struct")]
268 pub workspace_edit: WorkspaceEdit,
269}
270
271#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
273pub struct RenameParams {
274 pub connection_id: String,
276 #[serde(deserialize_with = "string_or_struct")]
280 pub document: DocumentIdentifier,
281 pub lsp_client_name: String,
283 #[serde(flatten)]
285 pub position: Position,
286 pub new_name: String,
288 #[serde(default = "default_prepare_first")]
290 pub prepare_first: bool,
291}
292
293fn default_prepare_first() -> bool {
294 true
295}
296
297#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
299pub struct DocumentFormattingParams {
300 pub connection_id: String,
302 #[serde(deserialize_with = "string_or_struct")]
306 pub document: DocumentIdentifier,
307 pub lsp_client_name: String,
309 pub options: FormattingOptions,
311 #[serde(default)]
313 pub apply_edits: bool,
314}
315
316#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
318pub struct DocumentRangeFormattingParams {
319 pub connection_id: String,
321 #[serde(deserialize_with = "string_or_struct")]
325 pub document: DocumentIdentifier,
326 pub lsp_client_name: String,
328 pub start_line: u64,
330 pub start_character: u64,
332 pub end_line: u64,
334 pub end_character: u64,
336 #[serde(deserialize_with = "string_or_struct")]
338 pub options: FormattingOptions,
339 #[serde(default)]
341 pub apply_edits: bool,
342}
343
344#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
346pub struct LspOrganizeImportsParams {
347 pub connection_id: String,
349 #[serde(deserialize_with = "string_or_struct")]
353 pub document: DocumentIdentifier,
354 pub lsp_client_name: String,
356 #[serde(default = "default_true")]
358 pub apply_edits: bool,
359}
360
361fn default_true() -> bool {
362 true
363}
364
365#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
367pub struct NavigateParams {
368 pub connection_id: String,
370 #[serde(deserialize_with = "string_or_struct")]
374 pub document: DocumentIdentifier,
375 #[serde(flatten)]
377 pub position: Position,
378}
379
380#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
382pub struct CallHierarchyPrepareParams {
383 pub connection_id: String,
385 #[serde(deserialize_with = "string_or_struct")]
389 pub document: DocumentIdentifier,
390 pub lsp_client_name: String,
392 #[serde(flatten)]
394 pub position: Position,
395}
396
397#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
399pub struct CallHierarchyIncomingCallsParams {
400 pub connection_id: String,
402 pub lsp_client_name: String,
404 #[serde(deserialize_with = "string_or_struct")]
406 pub item: CallHierarchyItem,
407}
408
409#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
411pub struct CallHierarchyOutgoingCallsParams {
412 pub connection_id: String,
414 pub lsp_client_name: String,
416 #[serde(deserialize_with = "string_or_struct")]
418 pub item: CallHierarchyItem,
419}
420
421#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
423pub struct TypeHierarchyPrepareParams {
424 pub connection_id: String,
426 #[serde(deserialize_with = "string_or_struct")]
430 pub document: DocumentIdentifier,
431 pub lsp_client_name: String,
433 #[serde(flatten)]
435 pub position: Position,
436}
437
438#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
440pub struct TypeHierarchySupertypesParams {
441 pub connection_id: String,
443 pub lsp_client_name: String,
445 #[serde(deserialize_with = "string_or_struct")]
447 pub item: TypeHierarchyItem,
448}
449
450#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
452pub struct TypeHierarchySubtypesParams {
453 pub connection_id: String,
455 pub lsp_client_name: String,
457 #[serde(deserialize_with = "string_or_struct")]
459 pub item: TypeHierarchyItem,
460}
461
462macro_rules! include_files {
463 ($($key:ident),* $(,)?) => {{
464 let mut map = HashMap::new();
465 $(
466 map.insert(stringify!($key), include_str!(concat!("../../docs/tools/", stringify!($key), ".md")));
467 )*
468 map
469 }};
470}
471
472impl NeovimMcpServer {
473 pub fn tool_descriptions() -> HashMap<&'static str, &'static str> {
474 include_files! {
475 get_targets,
476 connect,
477 read,
478 buffer_diagnostics,
479 }
480 }
481
482 pub fn get_tool_extra_description(&self, name: &str) -> Option<String> {
483 if name == "get_targets" {
484 Some(self.get_connections_instruction())
485 } else {
486 None
487 }
488 }
489}
490
491#[tool_router]
492impl NeovimMcpServer {
493 #[tool]
494 #[instrument(skip(self))]
495 pub async fn get_targets(&self) -> Result<CallToolResult, McpError> {
496 let targets = find_get_all_targets();
497 if targets.is_empty() {
498 return Err(McpError::invalid_request(
499 "No Neovim targets found".to_string(),
500 None,
501 ));
502 }
503
504 Ok(CallToolResult::success(vec![Content::json(targets)?]))
505 }
506
507 #[tool]
508 #[instrument(skip(self))]
509 pub async fn connect(
510 &self,
511 Parameters(ConnectNvimRequest { target: path }): Parameters<ConnectNvimRequest>,
512 ctx: RequestContext<RoleServer>,
513 ) -> Result<CallToolResult, McpError> {
514 let connection_id = self.generate_shorter_connection_id(&path);
515
516 if let Some(mut old_client) = self.nvim_clients.get_mut(&connection_id) {
518 let _ = old_client.disconnect().await;
519 }
520
521 let mut client = NeovimClient::default();
522 client.connect_path(&path).await?;
523
524 self.setup_new_client(&connection_id, Box::new(client), &ctx)
525 .await?;
526
527 Ok(CallToolResult::success(vec![Content::json(
528 serde_json::json!({
529 "connection_id": connection_id,
530 }),
531 )?]))
532 }
533
534 #[tool(description = "Connect via TCP address")]
535 #[instrument(skip(self))]
536 pub async fn connect_tcp(
537 &self,
538 Parameters(ConnectNvimRequest { target: address }): Parameters<ConnectNvimRequest>,
539 ctx: RequestContext<RoleServer>,
540 ) -> Result<CallToolResult, McpError> {
541 let connection_id = self.generate_shorter_connection_id(&address);
542
543 if let Some(mut old_client) = self.nvim_clients.get_mut(&connection_id) {
545 let _ = old_client.disconnect().await;
546 }
547
548 let mut client = NeovimClient::default();
549 client.connect_tcp(&address).await?;
550
551 self.setup_new_client(&connection_id, Box::new(client), &ctx)
552 .await?;
553
554 Ok(CallToolResult::success(vec![Content::json(
555 serde_json::json!({
556 "connection_id": connection_id,
557 }),
558 )?]))
559 }
560
561 #[tool(description = "Disconnect from Neovim instance")]
562 #[instrument(skip(self))]
563 pub async fn disconnect(
564 &self,
565 Parameters(ConnectionRequest { connection_id }): Parameters<ConnectionRequest>,
566 ) -> Result<CallToolResult, McpError> {
567 let target = {
569 let client = self.get_connection(&connection_id)?;
570 client.target().unwrap_or_else(|| "Unknown".to_string())
571 };
572
573 if let Some((_, mut client)) = self.nvim_clients.remove(&connection_id) {
575 if let Err(e) = client.disconnect().await {
576 return Err(McpError::internal_error(
577 format!("Failed to disconnect: {e}"),
578 None,
579 ));
580 }
581 Ok(CallToolResult::success(vec![Content::json(
582 serde_json::json!({
583 "connection_id": connection_id,
584 "target": target,
585 }),
586 )?]))
587 } else {
588 Err(McpError::invalid_request(
589 format!("No Neovim connection found for ID: {connection_id}"),
590 None,
591 ))
592 }
593 }
594
595 #[tool(description = "List all open buffers")]
596 #[instrument(skip(self))]
597 pub async fn list_buffers(
598 &self,
599 Parameters(ConnectionRequest { connection_id }): Parameters<ConnectionRequest>,
600 ) -> Result<CallToolResult, McpError> {
601 let client = self.get_connection(&connection_id)?;
602 let buffers = client.get_buffers().await?;
603 Ok(CallToolResult::success(vec![Content::json(buffers)?]))
604 }
605
606 #[tool(description = "Execute Lua code")]
607 #[instrument(skip(self))]
608 pub async fn exec_lua(
609 &self,
610 Parameters(ExecuteLuaRequest {
611 connection_id,
612 code,
613 }): Parameters<ExecuteLuaRequest>,
614 ) -> Result<CallToolResult, McpError> {
615 let client = self.get_connection(&connection_id)?;
616 let result = client.execute_lua(&code).await?;
617 Ok(CallToolResult::success(vec![Content::json(
618 serde_json::json!({
619 "result": format!("{:?}", result)
620 }),
621 )?]))
622 }
623
624 #[tool(description = "Wait for LSP client to be ready and attached")]
625 #[instrument(skip(self))]
626 pub async fn wait_for_lsp_ready(
627 &self,
628 Parameters(WaitForLspReadyRequest {
629 connection_id,
630 client_name,
631 timeout_ms,
632 }): Parameters<WaitForLspReadyRequest>,
633 ) -> Result<CallToolResult, McpError> {
634 let client = self.get_connection(&connection_id)?;
635 client
636 .wait_for_lsp_ready(client_name.as_deref(), timeout_ms)
637 .await?;
638 Ok(CallToolResult::success(vec![Content::json(
639 serde_json::json!({
640 "message": "LSP client ready",
641 "client_name": client_name.unwrap_or_else(|| "any".to_string()),
642 "timeout_ms": timeout_ms
643 }),
644 )?]))
645 }
646
647 #[tool]
648 #[instrument(skip(self))]
649 pub async fn read(
650 &self,
651 Parameters(BufferReadRequest {
652 connection_id,
653 document,
654 start,
655 end,
656 }): Parameters<BufferReadRequest>,
657 ) -> Result<CallToolResult, McpError> {
658 let client = self.get_connection(&connection_id)?;
659 let text_content = client.read_document(document, start, end).await?;
660 Ok(CallToolResult::success(vec![Content::text(text_content)]))
661 }
662
663 #[tool]
664 #[instrument(skip(self))]
665 pub async fn buffer_diagnostics(
666 &self,
667 Parameters(BufferRequest { connection_id, id }): Parameters<BufferRequest>,
668 ) -> Result<CallToolResult, McpError> {
669 let client = self.get_connection(&connection_id)?;
670 let diagnostics = client.get_buffer_diagnostics(id).await?;
671 Ok(CallToolResult::success(vec![Content::json(diagnostics)?]))
672 }
673
674 #[tool(description = "Get workspace LSP clients")]
675 #[instrument(skip(self))]
676 pub async fn lsp_clients(
677 &self,
678 Parameters(ConnectionRequest { connection_id }): Parameters<ConnectionRequest>,
679 ) -> Result<CallToolResult, McpError> {
680 let client = self.get_connection(&connection_id)?;
681 let lsp_clients = client.lsp_get_clients().await?;
682 Ok(CallToolResult::success(vec![Content::json(lsp_clients)?]))
683 }
684
685 #[tool(description = "Search workspace symbols by query")]
686 #[instrument(skip(self))]
687 pub async fn lsp_workspace_symbols(
688 &self,
689 Parameters(WorkspaceSymbolsParams {
690 connection_id,
691 lsp_client_name,
692 query,
693 }): Parameters<WorkspaceSymbolsParams>,
694 ) -> Result<CallToolResult, McpError> {
695 let client = self.get_connection(&connection_id)?;
696 let symbols = client
697 .lsp_workspace_symbols(&lsp_client_name, &query)
698 .await?;
699 Ok(CallToolResult::success(vec![Content::json(symbols)?]))
700 }
701
702 #[tool(description = "Get LSP code actions")]
703 #[instrument(skip(self))]
704 pub async fn lsp_code_actions(
705 &self,
706 Parameters(CodeActionsParams {
707 connection_id,
708 document,
709 lsp_client_name,
710 start_line,
711 start_character,
712 end_line,
713 end_character,
714 }): Parameters<CodeActionsParams>,
715 ) -> Result<CallToolResult, McpError> {
716 let client = self.get_connection(&connection_id)?;
717 let start = Position {
718 line: start_line,
719 character: start_character,
720 };
721 let end = Position {
722 line: end_line,
723 character: end_character,
724 };
725 let range = Range { start, end };
726
727 let code_actions = client
728 .lsp_get_code_actions(&lsp_client_name, document, range)
729 .await?;
730 Ok(CallToolResult::success(vec![Content::json(code_actions)?]))
731 }
732
733 #[tool(description = "Get LSP hover information")]
734 #[instrument(skip(self))]
735 pub async fn lsp_hover(
736 &self,
737 Parameters(HoverParam {
738 connection_id,
739 document,
740 lsp_client_name,
741 position,
742 }): Parameters<HoverParam>,
743 ) -> Result<CallToolResult, McpError> {
744 let client = self.get_connection(&connection_id)?;
745 let hover = client
746 .lsp_hover(&lsp_client_name, document, position)
747 .await?;
748 Ok(CallToolResult::success(vec![Content::json(hover)?]))
749 }
750
751 #[tool(description = "Get document symbols")]
752 #[instrument(skip(self))]
753 pub async fn lsp_document_symbols(
754 &self,
755 Parameters(DocumentSymbolsParams {
756 connection_id,
757 document,
758 lsp_client_name,
759 }): Parameters<DocumentSymbolsParams>,
760 ) -> Result<CallToolResult, McpError> {
761 let client = self.get_connection(&connection_id)?;
762 let symbols = client
763 .lsp_document_symbols(&lsp_client_name, document)
764 .await?;
765 Ok(CallToolResult::success(vec![Content::json(symbols)?]))
766 }
767
768 #[tool(description = "Get LSP references")]
769 #[instrument(skip(self))]
770 pub async fn lsp_references(
771 &self,
772 Parameters(ReferencesParams {
773 connection_id,
774 document,
775 lsp_client_name,
776 position,
777 include_declaration,
778 }): Parameters<ReferencesParams>,
779 ) -> Result<CallToolResult, McpError> {
780 let client = self.get_connection(&connection_id)?;
781 let references = client
782 .lsp_references(&lsp_client_name, document, position, include_declaration)
783 .await?;
784 Ok(CallToolResult::success(vec![Content::json(references)?]))
785 }
786
787 #[tool(description = "Get LSP definition")]
788 #[instrument(skip(self))]
789 pub async fn lsp_definition(
790 &self,
791 Parameters(DefinitionParams {
792 connection_id,
793 document,
794 lsp_client_name,
795 position,
796 }): Parameters<DefinitionParams>,
797 ) -> Result<CallToolResult, McpError> {
798 let client = self.get_connection(&connection_id)?;
799 let definition = client
800 .lsp_definition(&lsp_client_name, document, position)
801 .await?;
802 Ok(CallToolResult::success(vec![Content::json(definition)?]))
803 }
804
805 #[tool(description = "Get LSP type definition")]
806 #[instrument(skip(self))]
807 pub async fn lsp_type_definition(
808 &self,
809 Parameters(TypeDefinitionParams {
810 connection_id,
811 document,
812 lsp_client_name,
813 position,
814 }): Parameters<TypeDefinitionParams>,
815 ) -> Result<CallToolResult, McpError> {
816 let client = self.get_connection(&connection_id)?;
817 let type_definition = client
818 .lsp_type_definition(&lsp_client_name, document, position)
819 .await?;
820 Ok(CallToolResult::success(vec![Content::json(
821 type_definition,
822 )?]))
823 }
824
825 #[tool(description = "Get LSP implementation")]
826 #[instrument(skip(self))]
827 pub async fn lsp_implementations(
828 &self,
829 Parameters(ImplementationParams {
830 connection_id,
831 document,
832 lsp_client_name,
833 position,
834 }): Parameters<ImplementationParams>,
835 ) -> Result<CallToolResult, McpError> {
836 let client = self.get_connection(&connection_id)?;
837 let implementation = client
838 .lsp_implementation(&lsp_client_name, document, position)
839 .await?;
840 Ok(CallToolResult::success(vec![Content::json(
841 implementation,
842 )?]))
843 }
844
845 #[tool(description = "Get LSP declaration")]
846 #[instrument(skip(self))]
847 pub async fn lsp_declaration(
848 &self,
849 Parameters(DeclarationParams {
850 connection_id,
851 document,
852 lsp_client_name,
853 position,
854 }): Parameters<DeclarationParams>,
855 ) -> Result<CallToolResult, McpError> {
856 let client = self.get_connection(&connection_id)?;
857 let declaration = client
858 .lsp_declaration(&lsp_client_name, document, position)
859 .await?;
860 Ok(CallToolResult::success(vec![Content::json(declaration)?]))
861 }
862
863 #[tool(description = "Resolve a code action that may have incomplete data")]
864 #[instrument(skip(self))]
865 pub async fn lsp_resolve_code_action(
866 &self,
867 Parameters(ResolveCodeActionParams {
868 connection_id,
869 lsp_client_name,
870 code_action,
871 }): Parameters<ResolveCodeActionParams>,
872 ) -> Result<CallToolResult, McpError> {
873 let client = self.get_connection(&connection_id)?;
874 let resolved_action = client
875 .lsp_resolve_code_action(&lsp_client_name, code_action)
876 .await?;
877 Ok(CallToolResult::success(vec![Content::json(
878 resolved_action,
879 )?]))
880 }
881
882 #[tool(description = "Apply workspace edits using Neovim's LSP utility functions")]
883 #[instrument(skip(self))]
884 pub async fn lsp_apply_edit(
885 &self,
886 Parameters(ApplyWorkspaceEditParams {
887 connection_id,
888 lsp_client_name,
889 workspace_edit,
890 }): Parameters<ApplyWorkspaceEditParams>,
891 ) -> Result<CallToolResult, McpError> {
892 let client = self.get_connection(&connection_id)?;
893 client
894 .lsp_apply_workspace_edit(&lsp_client_name, workspace_edit)
895 .await?;
896 Ok(CallToolResult::success(vec![Content::text("success")]))
897 }
898
899 #[tool(description = "Rename symbol across workspace using LSP with optional validation")]
900 #[instrument(skip(self))]
901 pub async fn lsp_rename(
902 &self,
903 Parameters(RenameParams {
904 connection_id,
905 document,
906 lsp_client_name,
907 position,
908 new_name,
909 prepare_first,
910 }): Parameters<RenameParams>,
911 ) -> Result<CallToolResult, McpError> {
912 let client = self.get_connection(&connection_id)?;
913
914 if prepare_first {
916 match client
917 .lsp_prepare_rename(&lsp_client_name, document.clone(), position.clone())
918 .await
919 {
920 Ok(Some(prepare_result)) => {
921 let prepare_info = match prepare_result {
923 PrepareRenameResult::Range(range) => {
924 format!("Range: {:?}", range)
925 }
926 PrepareRenameResult::RangeWithPlaceholder { range, placeholder } => {
927 format!("Range: {:?}, Current name: '{}'", range, placeholder)
928 }
929 PrepareRenameResult::DefaultBehavior { .. } => {
930 "Default behavior enabled".to_string()
931 }
932 };
933 tracing::debug!("Prepare rename successful: {}", prepare_info);
934 }
935 Ok(None) => {
936 return Err(McpError::invalid_request(
937 "Position is not renameable according to prepare rename".to_string(),
938 None,
939 ));
940 }
941 Err(e) => {
942 return Err(McpError::invalid_request(
943 format!("Prepare rename failed: {}", e),
944 None,
945 ));
946 }
947 }
948 }
949
950 let workspace_edit = client
952 .lsp_rename(&lsp_client_name, document, position, &new_name)
953 .await?;
954
955 if let Some(edit) = workspace_edit {
956 client
958 .lsp_apply_workspace_edit(&lsp_client_name, edit)
959 .await?;
960 Ok(CallToolResult::success(vec![Content::text(
961 "Rename completed successfully",
962 )]))
963 } else {
964 Err(McpError::invalid_request(
965 "Rename operation is not valid at this position".to_string(),
966 None,
967 ))
968 }
969 }
970
971 #[tool(description = "Format entire document using LSP with optional auto-apply")]
972 #[instrument(skip(self))]
973 pub async fn lsp_formatting(
974 &self,
975 Parameters(DocumentFormattingParams {
976 connection_id,
977 document,
978 lsp_client_name,
979 options,
980 apply_edits,
981 }): Parameters<DocumentFormattingParams>,
982 ) -> Result<CallToolResult, McpError> {
983 let client = self.get_connection(&connection_id)?;
984 let text_edits = client
985 .lsp_formatting(&lsp_client_name, document.clone(), options)
986 .await?;
987
988 if apply_edits {
989 client
991 .lsp_apply_text_edits(&lsp_client_name, document, text_edits)
992 .await?;
993 Ok(CallToolResult::success(vec![Content::text(
994 "Formatting applied successfully",
995 )]))
996 } else {
997 Ok(CallToolResult::success(vec![Content::json(text_edits)?]))
999 }
1000 }
1001
1002 #[tool(
1003 description = "Format a specific range in a document using LSP with optional auto-apply"
1004 )]
1005 #[instrument(skip(self))]
1006 pub async fn lsp_range_formatting(
1007 &self,
1008 Parameters(DocumentRangeFormattingParams {
1009 connection_id,
1010 document,
1011 lsp_client_name,
1012 start_line,
1013 start_character,
1014 end_line,
1015 end_character,
1016 options,
1017 apply_edits,
1018 }): Parameters<DocumentRangeFormattingParams>,
1019 ) -> Result<CallToolResult, McpError> {
1020 let client = self.get_connection(&connection_id)?;
1021 let start = Position {
1022 line: start_line,
1023 character: start_character,
1024 };
1025 let end = Position {
1026 line: end_line,
1027 character: end_character,
1028 };
1029 let range = Range { start, end };
1030
1031 let text_edits = client
1032 .lsp_range_formatting(&lsp_client_name, document.clone(), range, options)
1033 .await?;
1034
1035 if apply_edits {
1036 client
1038 .lsp_apply_text_edits(&lsp_client_name, document, text_edits)
1039 .await?;
1040 Ok(CallToolResult::success(vec![Content::text(
1041 "Range formatting applied successfully",
1042 )]))
1043 } else {
1044 Ok(CallToolResult::success(vec![Content::json(text_edits)?]))
1046 }
1047 }
1048
1049 #[tool(description = "Sort and organize imports")]
1050 #[instrument(skip(self))]
1051 pub async fn lsp_organize_imports(
1052 &self,
1053 Parameters(LspOrganizeImportsParams {
1054 connection_id,
1055 document,
1056 lsp_client_name,
1057 apply_edits,
1058 }): Parameters<LspOrganizeImportsParams>,
1059 ) -> Result<CallToolResult, McpError> {
1060 let client = self.get_connection(&connection_id)?;
1061
1062 let code_actions = client
1064 .lsp_get_organize_imports_actions(&lsp_client_name, document)
1065 .await?;
1066
1067 if code_actions.is_empty() {
1068 return Ok(CallToolResult::success(vec![Content::text(
1069 "No organize imports actions available for this document",
1070 )]));
1071 }
1072
1073 if !apply_edits {
1074 return Ok(CallToolResult::success(vec![Content::json(code_actions)?]));
1076 }
1077
1078 let action = code_actions[0].clone();
1080
1081 let resolved_action = if action.has_edit() {
1083 action
1084 } else {
1085 client
1086 .lsp_resolve_code_action(&lsp_client_name, action)
1087 .await?
1088 };
1089
1090 if let Some(edit) = resolved_action.edit() {
1092 client
1093 .lsp_apply_workspace_edit(&lsp_client_name, edit.clone())
1094 .await?;
1095 Ok(CallToolResult::success(vec![Content::text(
1096 "Imports organized successfully",
1097 )]))
1098 } else {
1099 Err(McpError::invalid_request(
1100 "Organize imports action does not contain workspace edit".to_string(),
1101 None,
1102 ))
1103 }
1104 }
1105
1106 #[tool(
1107 description = "Get the current cursor position: buffer id, buffer name, window id, and zero-based row/col index"
1108 )]
1109 #[instrument(skip(self))]
1110 pub async fn cursor_position(
1111 &self,
1112 Parameters(ConnectionRequest { connection_id }): Parameters<ConnectionRequest>,
1113 ) -> Result<CallToolResult, McpError> {
1114 let client = self.get_connection(&connection_id)?;
1115 let lua_code = include_str!("./lua/cursor_position.lua");
1116 let result = client.execute_lua(lua_code).await?;
1117
1118 let json_result = lua_tools::convert_nvim_value_to_json(result).map_err(|e| {
1120 McpError::internal_error(
1121 format!("Failed to convert cursor position result to JSON: {}", e),
1122 None,
1123 )
1124 })?;
1125
1126 Ok(CallToolResult::success(vec![Content::json(json_result)?]))
1127 }
1128
1129 #[tool(
1130 description = "Navigate to a specific position in the current buffer or open a file at a specific position"
1131 )]
1132 #[instrument(skip(self))]
1133 pub async fn navigate(
1134 &self,
1135 Parameters(NavigateParams {
1136 connection_id,
1137 document,
1138 position,
1139 }): Parameters<NavigateParams>,
1140 ) -> Result<CallToolResult, McpError> {
1141 let client = self.get_connection(&connection_id)?;
1142 let result = client.navigate(document, position).await?;
1143 Ok(CallToolResult::success(vec![Content::json(result)?]))
1144 }
1145
1146 #[tool(description = "Prepare call hierarchy for a symbol at a specific position")]
1147 #[instrument(skip(self))]
1148 pub async fn lsp_call_hierarchy_prepare(
1149 &self,
1150 Parameters(CallHierarchyPrepareParams {
1151 connection_id,
1152 document,
1153 lsp_client_name,
1154 position,
1155 }): Parameters<CallHierarchyPrepareParams>,
1156 ) -> Result<CallToolResult, McpError> {
1157 let client = self.get_connection(&connection_id)?;
1158 let result = client
1159 .lsp_call_hierarchy_prepare(&lsp_client_name, document, position)
1160 .await?;
1161 Ok(CallToolResult::success(vec![Content::json(result)?]))
1162 }
1163
1164 #[tool(description = "Get incoming calls for a call hierarchy item")]
1165 #[instrument(skip(self))]
1166 pub async fn lsp_call_hierarchy_incoming_calls(
1167 &self,
1168 Parameters(CallHierarchyIncomingCallsParams {
1169 connection_id,
1170 lsp_client_name,
1171 item,
1172 }): Parameters<CallHierarchyIncomingCallsParams>,
1173 ) -> Result<CallToolResult, McpError> {
1174 let client = self.get_connection(&connection_id)?;
1175 let result = client
1176 .lsp_call_hierarchy_incoming_calls(&lsp_client_name, item)
1177 .await?;
1178 Ok(CallToolResult::success(vec![Content::json(result)?]))
1179 }
1180
1181 #[tool(description = "Get outgoing calls for a call hierarchy item")]
1182 #[instrument(skip(self))]
1183 pub async fn lsp_call_hierarchy_outgoing_calls(
1184 &self,
1185 Parameters(CallHierarchyOutgoingCallsParams {
1186 connection_id,
1187 lsp_client_name,
1188 item,
1189 }): Parameters<CallHierarchyOutgoingCallsParams>,
1190 ) -> Result<CallToolResult, McpError> {
1191 let client = self.get_connection(&connection_id)?;
1192 let result = client
1193 .lsp_call_hierarchy_outgoing_calls(&lsp_client_name, item)
1194 .await?;
1195 Ok(CallToolResult::success(vec![Content::json(result)?]))
1196 }
1197
1198 #[tool(description = "Prepare type hierarchy for a symbol at a specific position")]
1199 #[instrument(skip(self))]
1200 pub async fn lsp_type_hierarchy_prepare(
1201 &self,
1202 Parameters(TypeHierarchyPrepareParams {
1203 connection_id,
1204 document,
1205 lsp_client_name,
1206 position,
1207 }): Parameters<TypeHierarchyPrepareParams>,
1208 ) -> Result<CallToolResult, McpError> {
1209 let client = self.get_connection(&connection_id)?;
1210 let result = client
1211 .lsp_type_hierarchy_prepare(&lsp_client_name, document, position)
1212 .await?;
1213 Ok(CallToolResult::success(vec![Content::json(result)?]))
1214 }
1215
1216 #[tool(description = "Get supertypes for a type hierarchy item")]
1217 #[instrument(skip(self))]
1218 pub async fn lsp_type_hierarchy_supertypes(
1219 &self,
1220 Parameters(TypeHierarchySupertypesParams {
1221 connection_id,
1222 lsp_client_name,
1223 item,
1224 }): Parameters<TypeHierarchySupertypesParams>,
1225 ) -> Result<CallToolResult, McpError> {
1226 let client = self.get_connection(&connection_id)?;
1227 let result = client
1228 .lsp_type_hierarchy_supertypes(&lsp_client_name, item)
1229 .await?;
1230 Ok(CallToolResult::success(vec![Content::json(result)?]))
1231 }
1232
1233 #[tool(description = "Get subtypes for a type hierarchy item")]
1234 #[instrument(skip(self))]
1235 pub async fn lsp_type_hierarchy_subtypes(
1236 &self,
1237 Parameters(TypeHierarchySubtypesParams {
1238 connection_id,
1239 lsp_client_name,
1240 item,
1241 }): Parameters<TypeHierarchySubtypesParams>,
1242 ) -> Result<CallToolResult, McpError> {
1243 let client = self.get_connection(&connection_id)?;
1244 let result = client
1245 .lsp_type_hierarchy_subtypes(&lsp_client_name, item)
1246 .await?;
1247 Ok(CallToolResult::success(vec![Content::json(result)?]))
1248 }
1249}
1250
1251pub fn build_tool_router() -> ToolRouter<NeovimMcpServer> {
1253 NeovimMcpServer::tool_router()
1254}