hanzo-mcp 0.8.8__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (167) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +4 -17
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +8 -17
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +2 -4
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +6 -7
  17. hanzo_mcp/tools/__init__.py +29 -32
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +23 -17
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  22. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  23. hanzo_mcp/tools/agent/cli_tools.py +76 -75
  24. hanzo_mcp/tools/agent/code_auth.py +1 -3
  25. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  26. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  27. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  28. hanzo_mcp/tools/agent/network_tool.py +7 -18
  29. hanzo_mcp/tools/agent/prompt.py +1 -5
  30. hanzo_mcp/tools/agent/review_tool.py +10 -25
  31. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  32. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  33. hanzo_mcp/tools/common/batch_tool.py +15 -45
  34. hanzo_mcp/tools/common/config_tool.py +9 -28
  35. hanzo_mcp/tools/common/context.py +1 -3
  36. hanzo_mcp/tools/common/critic_tool.py +1 -3
  37. hanzo_mcp/tools/common/decorators.py +2 -6
  38. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  39. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  40. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  41. hanzo_mcp/tools/common/mode.py +1 -5
  42. hanzo_mcp/tools/common/paginated_base.py +3 -11
  43. hanzo_mcp/tools/common/paginated_response.py +10 -30
  44. hanzo_mcp/tools/common/pagination.py +3 -9
  45. hanzo_mcp/tools/common/path_utils.py +34 -0
  46. hanzo_mcp/tools/common/permissions.py +14 -13
  47. hanzo_mcp/tools/common/personality.py +983 -701
  48. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  49. hanzo_mcp/tools/common/stats.py +7 -19
  50. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  51. hanzo_mcp/tools/common/tool_disable.py +2 -6
  52. hanzo_mcp/tools/common/tool_list.py +2 -6
  53. hanzo_mcp/tools/common/validation.py +1 -3
  54. hanzo_mcp/tools/compiler/__init__.py +8 -0
  55. hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  72. hanzo_mcp/tools/environment/__init__.py +8 -0
  73. hanzo_mcp/tools/environment/environment_detector.py +594 -0
  74. hanzo_mcp/tools/filesystem/__init__.py +28 -26
  75. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  76. hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
  77. hanzo_mcp/tools/filesystem/base.py +20 -12
  78. hanzo_mcp/tools/filesystem/content_replace.py +7 -12
  79. hanzo_mcp/tools/filesystem/diff.py +2 -10
  80. hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
  81. hanzo_mcp/tools/filesystem/edit.py +10 -18
  82. hanzo_mcp/tools/filesystem/find.py +312 -179
  83. hanzo_mcp/tools/filesystem/git_search.py +12 -24
  84. hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
  85. hanzo_mcp/tools/filesystem/read.py +14 -30
  86. hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
  87. hanzo_mcp/tools/filesystem/search.py +1160 -0
  88. hanzo_mcp/tools/filesystem/watch.py +2 -4
  89. hanzo_mcp/tools/filesystem/write.py +7 -10
  90. hanzo_mcp/tools/framework/__init__.py +8 -0
  91. hanzo_mcp/tools/framework/framework_modes.py +714 -0
  92. hanzo_mcp/tools/jupyter/base.py +6 -20
  93. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  94. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  95. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  96. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  97. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  98. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  99. hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
  100. hanzo_mcp/tools/mcp/mcp_add.py +3 -5
  101. hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
  102. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  103. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  104. hanzo_mcp/tools/memory/__init__.py +33 -40
  105. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  106. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  107. hanzo_mcp/tools/memory/memory_tools.py +7 -19
  108. hanzo_mcp/tools/search/find_tool.py +12 -34
  109. hanzo_mcp/tools/search/unified_search.py +27 -81
  110. hanzo_mcp/tools/shell/__init__.py +16 -4
  111. hanzo_mcp/tools/shell/auto_background.py +2 -6
  112. hanzo_mcp/tools/shell/base.py +1 -5
  113. hanzo_mcp/tools/shell/base_process.py +5 -7
  114. hanzo_mcp/tools/shell/bash_session.py +7 -24
  115. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  116. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  117. hanzo_mcp/tools/shell/command_executor.py +26 -79
  118. hanzo_mcp/tools/shell/logs.py +4 -16
  119. hanzo_mcp/tools/shell/npx.py +2 -8
  120. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  121. hanzo_mcp/tools/shell/pkill.py +4 -12
  122. hanzo_mcp/tools/shell/process_tool.py +2 -8
  123. hanzo_mcp/tools/shell/processes.py +5 -17
  124. hanzo_mcp/tools/shell/run_background.py +1 -3
  125. hanzo_mcp/tools/shell/run_command.py +1 -3
  126. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  127. hanzo_mcp/tools/shell/run_tool.py +56 -0
  128. hanzo_mcp/tools/shell/session_manager.py +2 -6
  129. hanzo_mcp/tools/shell/session_storage.py +2 -6
  130. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  131. hanzo_mcp/tools/shell/uvx.py +4 -14
  132. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  133. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  134. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  135. hanzo_mcp/tools/todo/todo.py +1 -3
  136. hanzo_mcp/tools/vector/__init__.py +97 -50
  137. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  138. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  139. hanzo_mcp/tools/vector/index_tool.py +3 -9
  140. hanzo_mcp/tools/vector/infinity_store.py +11 -30
  141. hanzo_mcp/tools/vector/mock_infinity.py +159 -0
  142. hanzo_mcp/tools/vector/node_tool.py +538 -0
  143. hanzo_mcp/tools/vector/project_manager.py +4 -12
  144. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  145. hanzo_mcp/tools/vector/vector.py +2 -6
  146. hanzo_mcp/tools/vector/vector_index.py +8 -8
  147. hanzo_mcp/tools/vector/vector_search.py +7 -21
  148. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  149. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  150. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  151. hanzo_mcp/tools/agent/swarm_tool.py +0 -723
  152. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  153. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  154. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  155. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  156. hanzo_mcp/tools/filesystem/grep.py +0 -467
  157. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  158. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  159. hanzo_mcp/tools/filesystem/tree.py +0 -270
  160. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  161. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  162. hanzo_mcp/tools/todo/todo_read.py +0 -143
  163. hanzo_mcp/tools/todo/todo_write.py +0 -374
  164. hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
  165. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  166. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  167. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
@@ -1,401 +1,602 @@
1
- """Language Server Protocol (LSP) tool for code intelligence.
1
+ """Enhanced Language Server Protocol (LSP) implementation with full protocol support.
2
2
 
3
- This tool provides on-demand LSP configuration and installation for various
4
- programming languages. It automatically installs language servers as needed
5
- and provides code intelligence features like go-to-definition, find references,
6
- rename symbol, and diagnostics.
3
+ This module provides a complete LSP client implementation with support for
4
+ all major programming languages including C, C++, Go, JavaScript, TypeScript,
5
+ Rust, Zig, and more.
7
6
  """
8
7
 
9
8
  import os
9
+ import sys
10
10
  import json
11
- import shutil
12
11
  import asyncio
13
12
  import logging
14
- from typing import Any, Dict, Optional
13
+ import subprocess
14
+ from typing import Any, Dict, List, Optional, Tuple
15
15
  from pathlib import Path
16
- from dataclasses import dataclass
16
+ from dataclasses import dataclass, field
17
+ from enum import Enum
17
18
 
18
19
  from hanzo_mcp.types import MCPResourceDocument
19
20
  from hanzo_mcp.tools.common.base import BaseTool
20
21
 
21
- # LSP server configurations
22
- LSP_SERVERS = {
22
+
23
+ class MessageType(Enum):
24
+ """LSP message types."""
25
+ REQUEST = "request"
26
+ RESPONSE = "response"
27
+ NOTIFICATION = "notification"
28
+
29
+
30
+ @dataclass
31
+ class LSPMessage:
32
+ """Represents an LSP message."""
33
+ type: MessageType
34
+ id: Optional[int] = None
35
+ method: Optional[str] = None
36
+ params: Optional[Dict[str, Any]] = None
37
+ result: Optional[Any] = None
38
+ error: Optional[Dict[str, Any]] = None
39
+
40
+
41
+ @dataclass
42
+ class Position:
43
+ """LSP position in a document."""
44
+ line: int
45
+ character: int
46
+
47
+ def to_dict(self) -> Dict[str, int]:
48
+ return {"line": self.line, "character": self.character}
49
+
50
+
51
+ @dataclass
52
+ class Range:
53
+ """LSP range in a document."""
54
+ start: Position
55
+ end: Position
56
+
57
+ def to_dict(self) -> Dict[str, Any]:
58
+ return {"start": self.start.to_dict(), "end": self.end.to_dict()}
59
+
60
+
61
+ @dataclass
62
+ class Location:
63
+ """LSP location."""
64
+ uri: str
65
+ range: Range
66
+
67
+ def to_dict(self) -> Dict[str, Any]:
68
+ return {"uri": self.uri, "range": self.range.to_dict()}
69
+
70
+
71
+ # Enhanced LSP server configurations with compilation support
72
+ ENHANCED_LSP_SERVERS = {
73
+ "c": {
74
+ "name": "clangd",
75
+ "install_cmd": {
76
+ "darwin": ["brew", "install", "llvm"],
77
+ "linux": ["sudo", "apt-get", "install", "-y", "clangd"],
78
+ },
79
+ "check_cmd": ["clangd", "--version"],
80
+ "start_cmd": ["clangd", "--background-index"],
81
+ "root_markers": ["compile_commands.json", "CMakeLists.txt", ".clangd"],
82
+ "file_extensions": [".c", ".h"],
83
+ "compile_cmd": ["clang", "-o", "{output}", "{input}"],
84
+ "run_cmd": ["./{output}"],
85
+ "capabilities": [
86
+ "definition", "references", "rename", "diagnostics",
87
+ "hover", "completion", "formatting", "code_actions"
88
+ ],
89
+ },
90
+ "cpp": {
91
+ "name": "clangd",
92
+ "install_cmd": {
93
+ "darwin": ["brew", "install", "llvm"],
94
+ "linux": ["sudo", "apt-get", "install", "-y", "clangd"],
95
+ },
96
+ "check_cmd": ["clangd", "--version"],
97
+ "start_cmd": ["clangd", "--background-index"],
98
+ "root_markers": ["compile_commands.json", "CMakeLists.txt", ".clangd"],
99
+ "file_extensions": [".cpp", ".cc", ".cxx", ".hpp", ".hxx"],
100
+ "compile_cmd": ["clang++", "-std=c++17", "-o", "{output}", "{input}"],
101
+ "run_cmd": ["./{output}"],
102
+ "capabilities": [
103
+ "definition", "references", "rename", "diagnostics",
104
+ "hover", "completion", "formatting", "code_actions"
105
+ ],
106
+ },
23
107
  "go": {
24
108
  "name": "gopls",
25
- "install_cmd": ["go", "install", "golang.org/x/tools/gopls@latest"],
109
+ "install_cmd": {
110
+ "all": ["go", "install", "golang.org/x/tools/gopls@latest"],
111
+ },
26
112
  "check_cmd": ["gopls", "version"],
27
113
  "start_cmd": ["gopls", "serve"],
28
114
  "root_markers": ["go.mod", "go.sum"],
29
115
  "file_extensions": [".go"],
116
+ "compile_cmd": ["go", "build", "-o", "{output}", "{input}"],
117
+ "run_cmd": ["./{output}"],
30
118
  "capabilities": [
31
- "definition",
32
- "references",
33
- "rename",
34
- "diagnostics",
35
- "hover",
36
- "completion",
119
+ "definition", "references", "rename", "diagnostics",
120
+ "hover", "completion", "formatting", "code_actions",
121
+ "implementation", "type_definition"
37
122
  ],
38
123
  },
39
- "python": {
40
- "name": "pylsp",
41
- "install_cmd": ["pip", "install", "python-lsp-server[all]"],
42
- "check_cmd": ["pylsp", "--version"],
43
- "start_cmd": ["pylsp"],
44
- "root_markers": ["pyproject.toml", "setup.py", "requirements.txt"],
45
- "file_extensions": [".py"],
124
+ "javascript": {
125
+ "name": "typescript-language-server",
126
+ "install_cmd": {
127
+ "all": ["npm", "install", "-g", "typescript", "typescript-language-server"],
128
+ },
129
+ "check_cmd": ["typescript-language-server", "--version"],
130
+ "start_cmd": ["typescript-language-server", "--stdio"],
131
+ "root_markers": ["package.json", "tsconfig.json", "jsconfig.json"],
132
+ "file_extensions": [".js", ".jsx", ".mjs"],
133
+ "run_cmd": ["node", "{input}"],
46
134
  "capabilities": [
47
- "definition",
48
- "references",
49
- "rename",
50
- "diagnostics",
51
- "hover",
52
- "completion",
135
+ "definition", "references", "rename", "diagnostics",
136
+ "hover", "completion", "formatting", "code_actions"
53
137
  ],
54
138
  },
55
139
  "typescript": {
56
140
  "name": "typescript-language-server",
57
- "install_cmd": [
58
- "npm",
59
- "install",
60
- "-g",
61
- "typescript",
62
- "typescript-language-server",
63
- ],
141
+ "install_cmd": {
142
+ "all": ["npm", "install", "-g", "typescript", "typescript-language-server"],
143
+ },
64
144
  "check_cmd": ["typescript-language-server", "--version"],
65
145
  "start_cmd": ["typescript-language-server", "--stdio"],
66
146
  "root_markers": ["tsconfig.json", "package.json"],
67
- "file_extensions": [".ts", ".tsx", ".js", ".jsx"],
147
+ "file_extensions": [".ts", ".tsx"],
148
+ "compile_cmd": ["tsc", "{input}", "--outFile", "{output}.js"],
149
+ "run_cmd": ["node", "{output}.js"],
68
150
  "capabilities": [
69
- "definition",
70
- "references",
71
- "rename",
72
- "diagnostics",
73
- "hover",
74
- "completion",
151
+ "definition", "references", "rename", "diagnostics",
152
+ "hover", "completion", "formatting", "code_actions",
153
+ "implementation", "type_definition"
75
154
  ],
76
155
  },
77
156
  "rust": {
78
157
  "name": "rust-analyzer",
79
- "install_cmd": ["rustup", "component", "add", "rust-analyzer"],
158
+ "install_cmd": {
159
+ "all": ["rustup", "component", "add", "rust-analyzer"],
160
+ },
80
161
  "check_cmd": ["rust-analyzer", "--version"],
81
162
  "start_cmd": ["rust-analyzer"],
82
163
  "root_markers": ["Cargo.toml"],
83
164
  "file_extensions": [".rs"],
165
+ "compile_cmd": ["rustc", "-o", "{output}", "{input}"],
166
+ "run_cmd": ["./{output}"],
84
167
  "capabilities": [
85
- "definition",
86
- "references",
87
- "rename",
88
- "diagnostics",
89
- "hover",
90
- "completion",
91
- "inlay_hints",
92
- ],
93
- },
94
- "java": {
95
- "name": "jdtls",
96
- "install_cmd": ["brew", "install", "jdtls"], # Or manual download
97
- "check_cmd": ["jdtls", "--version"],
98
- "start_cmd": ["jdtls"],
99
- "root_markers": ["pom.xml", "build.gradle", "build.gradle.kts"],
100
- "file_extensions": [".java"],
101
- "capabilities": [
102
- "definition",
103
- "references",
104
- "rename",
105
- "diagnostics",
106
- "hover",
107
- "completion",
108
- ],
109
- },
110
- "cpp": {
111
- "name": "clangd",
112
- "install_cmd": ["brew", "install", "llvm"], # Or apt-get install clangd
113
- "check_cmd": ["clangd", "--version"],
114
- "start_cmd": ["clangd"],
115
- "root_markers": ["compile_commands.json", "CMakeLists.txt"],
116
- "file_extensions": [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp"],
117
- "capabilities": [
118
- "definition",
119
- "references",
120
- "rename",
121
- "diagnostics",
122
- "hover",
123
- "completion",
168
+ "definition", "references", "rename", "diagnostics",
169
+ "hover", "completion", "formatting", "code_actions",
170
+ "inlay_hints", "implementation", "type_definition"
124
171
  ],
125
172
  },
126
- "ruby": {
127
- "name": "solargraph",
128
- "install_cmd": ["gem", "install", "solargraph"],
129
- "check_cmd": ["solargraph", "--version"],
130
- "start_cmd": ["solargraph", "stdio"],
131
- "root_markers": ["Gemfile", ".solargraph.yml"],
132
- "file_extensions": [".rb"],
173
+ "zig": {
174
+ "name": "zls",
175
+ "install_cmd": {
176
+ "darwin": ["brew", "install", "zls"],
177
+ "linux": ["wget", "-O", "/tmp/zls.tar.gz",
178
+ "https://github.com/zigtools/zls/releases/latest/download/zls-linux-x86_64.tar.gz",
179
+ "&&", "tar", "-xvf", "/tmp/zls.tar.gz", "-C", "/usr/local/bin"],
180
+ },
181
+ "check_cmd": ["zls", "--version"],
182
+ "start_cmd": ["zls"],
183
+ "root_markers": ["build.zig", "build.zig.zon"],
184
+ "file_extensions": [".zig"],
185
+ "compile_cmd": ["zig", "build-exe", "{input}", "-femit-bin={output}"],
186
+ "run_cmd": ["./{output}"],
133
187
  "capabilities": [
134
- "definition",
135
- "references",
136
- "diagnostics",
137
- "hover",
138
- "completion",
188
+ "definition", "references", "rename", "diagnostics",
189
+ "hover", "completion", "formatting", "code_actions",
190
+ "semantic_tokens"
139
191
  ],
140
192
  },
141
- "lua": {
142
- "name": "lua-language-server",
143
- "install_cmd": ["brew", "install", "lua-language-server"],
144
- "check_cmd": ["lua-language-server", "--version"],
145
- "start_cmd": ["lua-language-server"],
146
- "root_markers": [".luarc.json"],
147
- "file_extensions": [".lua"],
193
+ "python": {
194
+ "name": "pylsp",
195
+ "install_cmd": {
196
+ "all": ["pip", "install", "python-lsp-server[all]"],
197
+ },
198
+ "check_cmd": ["pylsp", "--version"],
199
+ "start_cmd": ["pylsp"],
200
+ "root_markers": ["pyproject.toml", "setup.py", "requirements.txt"],
201
+ "file_extensions": [".py"],
202
+ "run_cmd": ["python", "{input}"],
148
203
  "capabilities": [
149
- "definition",
150
- "references",
151
- "rename",
152
- "diagnostics",
153
- "hover",
154
- "completion",
204
+ "definition", "references", "rename", "diagnostics",
205
+ "hover", "completion", "formatting", "code_actions"
155
206
  ],
156
207
  },
157
208
  }
158
209
 
159
210
 
160
- @dataclass
161
- class LSPServer:
162
- """Represents an LSP server instance."""
163
-
164
- language: str
165
- process: Optional[asyncio.subprocess.Process]
166
- config: Dict[str, Any]
167
- root_uri: str
168
- initialized: bool = False
211
+ class LSPClient:
212
+ """Full LSP client implementation."""
213
+
214
+ def __init__(self, language: str, root_uri: str):
215
+ self.language = language
216
+ self.root_uri = root_uri
217
+ self.config = ENHANCED_LSP_SERVERS[language]
218
+ self.process: Optional[asyncio.subprocess.Process] = None
219
+ self.request_id = 0
220
+ self.pending_requests: Dict[int, asyncio.Future] = {}
221
+ self.initialized = False
222
+ self.logger = logging.getLogger(f"LSP.{language}")
223
+
224
+ async def start(self) -> bool:
225
+ """Start the LSP server process."""
226
+ try:
227
+ self.process = await asyncio.create_subprocess_exec(
228
+ *self.config["start_cmd"],
229
+ stdin=asyncio.subprocess.PIPE,
230
+ stdout=asyncio.subprocess.PIPE,
231
+ stderr=asyncio.subprocess.PIPE,
232
+ cwd=self.root_uri,
233
+ )
234
+
235
+ # Start message reader
236
+ asyncio.create_task(self._read_messages())
237
+
238
+ # Initialize
239
+ await self.initialize()
240
+
241
+ return True
242
+
243
+ except Exception as e:
244
+ self.logger.error(f"Failed to start LSP: {e}")
245
+ return False
246
+
247
+ async def initialize(self):
248
+ """Send initialize request."""
249
+ params = {
250
+ "processId": os.getpid(),
251
+ "clientInfo": {
252
+ "name": "hanzo-mcp",
253
+ "version": "0.8.11"
254
+ },
255
+ "locale": "en",
256
+ "rootPath": self.root_uri,
257
+ "rootUri": f"file://{self.root_uri}",
258
+ "capabilities": {
259
+ "workspace": {
260
+ "applyEdit": True,
261
+ "workspaceEdit": {
262
+ "documentChanges": True,
263
+ "resourceOperations": ["create", "rename", "delete"],
264
+ },
265
+ "didChangeConfiguration": {"dynamicRegistration": True},
266
+ "symbol": {"dynamicRegistration": True},
267
+ },
268
+ "textDocument": {
269
+ "synchronization": {
270
+ "dynamicRegistration": True,
271
+ "willSave": True,
272
+ "willSaveWaitUntil": True,
273
+ "didSave": True,
274
+ },
275
+ "completion": {
276
+ "dynamicRegistration": True,
277
+ "contextSupport": True,
278
+ "completionItem": {
279
+ "snippetSupport": True,
280
+ "commitCharactersSupport": True,
281
+ "documentationFormat": ["markdown", "plaintext"],
282
+ },
283
+ },
284
+ "hover": {
285
+ "dynamicRegistration": True,
286
+ "contentFormat": ["markdown", "plaintext"],
287
+ },
288
+ "signatureHelp": {"dynamicRegistration": True},
289
+ "definition": {"dynamicRegistration": True},
290
+ "references": {"dynamicRegistration": True},
291
+ "documentHighlight": {"dynamicRegistration": True},
292
+ "documentSymbol": {"dynamicRegistration": True},
293
+ "codeAction": {"dynamicRegistration": True},
294
+ "codeLens": {"dynamicRegistration": True},
295
+ "formatting": {"dynamicRegistration": True},
296
+ "rangeFormatting": {"dynamicRegistration": True},
297
+ "onTypeFormatting": {"dynamicRegistration": True},
298
+ "rename": {"dynamicRegistration": True, "prepareSupport": True},
299
+ "documentLink": {"dynamicRegistration": True},
300
+ },
301
+ },
302
+ }
303
+
304
+ result = await self.send_request("initialize", params)
305
+
306
+ if result:
307
+ # Send initialized notification
308
+ await self.send_notification("initialized", {})
309
+ self.initialized = True
310
+
311
+ return result
312
+
313
+ async def send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Optional[Any]:
314
+ """Send request and wait for response."""
315
+ self.request_id += 1
316
+ request_id = self.request_id
317
+
318
+ request = {
319
+ "jsonrpc": "2.0",
320
+ "id": request_id,
321
+ "method": method,
322
+ "params": params or {},
323
+ }
324
+
325
+ # Create future for response
326
+ future = asyncio.get_event_loop().create_future()
327
+ self.pending_requests[request_id] = future
328
+
329
+ # Send request
330
+ await self._send_message(request)
331
+
332
+ # Wait for response
333
+ try:
334
+ result = await asyncio.wait_for(future, timeout=10.0)
335
+ return result
336
+ except asyncio.TimeoutError:
337
+ self.logger.error(f"Request {method} timed out")
338
+ del self.pending_requests[request_id]
339
+ return None
340
+
341
+ async def send_notification(self, method: str, params: Optional[Dict[str, Any]] = None):
342
+ """Send notification (no response expected)."""
343
+ notification = {
344
+ "jsonrpc": "2.0",
345
+ "method": method,
346
+ "params": params or {},
347
+ }
348
+
349
+ await self._send_message(notification)
350
+
351
+ async def _send_message(self, message: Dict[str, Any]):
352
+ """Send message to LSP server."""
353
+ if not self.process or self.process.returncode is not None:
354
+ return
355
+
356
+ content = json.dumps(message)
357
+ content_bytes = content.encode("utf-8")
358
+
359
+ header = f"Content-Length: {len(content_bytes)}\r\n\r\n"
360
+ full_message = header.encode("utf-8") + content_bytes
361
+
362
+ self.process.stdin.write(full_message)
363
+ await self.process.stdin.drain()
364
+
365
+ async def _read_messages(self):
366
+ """Read messages from LSP server."""
367
+ buffer = b""
368
+
369
+ while self.process and self.process.returncode is None:
370
+ try:
371
+ chunk = await self.process.stdout.read(1024)
372
+ if not chunk:
373
+ break
374
+
375
+ buffer += chunk
376
+
377
+ # Parse messages
378
+ while True:
379
+ message, remaining = self._parse_message(buffer)
380
+ if not message:
381
+ break
382
+
383
+ buffer = remaining
384
+ await self._handle_message(message)
385
+
386
+ except Exception as e:
387
+ self.logger.error(f"Error reading messages: {e}")
388
+ break
389
+
390
+ def _parse_message(self, buffer: bytes) -> Tuple[Optional[Dict[str, Any]], bytes]:
391
+ """Parse LSP message from buffer."""
392
+ # Look for Content-Length header
393
+ header_end = buffer.find(b"\r\n\r\n")
394
+ if header_end == -1:
395
+ return None, buffer
396
+
397
+ header = buffer[:header_end].decode("utf-8")
398
+
399
+ # Parse content length
400
+ content_length = None
401
+ for line in header.split("\r\n"):
402
+ if line.startswith("Content-Length:"):
403
+ content_length = int(line.split(":")[1].strip())
404
+ break
405
+
406
+ if not content_length:
407
+ return None, buffer
408
+
409
+ # Check if we have full message
410
+ message_start = header_end + 4
411
+ message_end = message_start + content_length
412
+
413
+ if len(buffer) < message_end:
414
+ return None, buffer
415
+
416
+ # Parse message
417
+ message_bytes = buffer[message_start:message_end]
418
+ try:
419
+ message = json.loads(message_bytes.decode("utf-8"))
420
+ return message, buffer[message_end:]
421
+ except json.JSONDecodeError:
422
+ return None, buffer[message_end:]
423
+
424
+ async def _handle_message(self, message: Dict[str, Any]):
425
+ """Handle incoming LSP message."""
426
+ if "id" in message and "result" in message:
427
+ # Response to our request
428
+ request_id = message["id"]
429
+ if request_id in self.pending_requests:
430
+ future = self.pending_requests.pop(request_id)
431
+ future.set_result(message.get("result"))
432
+
433
+ elif "id" in message and "error" in message:
434
+ # Error response
435
+ request_id = message["id"]
436
+ if request_id in self.pending_requests:
437
+ future = self.pending_requests.pop(request_id)
438
+ future.set_exception(Exception(message["error"]))
439
+
440
+ elif "method" in message:
441
+ # Server request or notification
442
+ # Handle server-initiated requests here if needed
443
+ pass
444
+
445
+ async def shutdown(self):
446
+ """Shutdown the LSP server."""
447
+ if self.initialized:
448
+ await self.send_request("shutdown")
449
+ await self.send_notification("exit")
450
+
451
+ if self.process and self.process.returncode is None:
452
+ self.process.terminate()
453
+ await self.process.wait()
169
454
 
170
455
 
171
456
  class LSPTool(BaseTool):
172
- """Language Server Protocol tool for code intelligence.
173
-
174
- This tool automatically configures and manages LSP servers for various
175
- programming languages. It installs language servers on-demand and provides
176
- code intelligence features.
177
-
178
- Features:
179
- - Auto-installation of language servers
180
- - Go-to-definition
181
- - Find references
182
- - Rename symbol
183
- - Get diagnostics
184
- - Hover information
185
- - Code completion
186
-
187
- Example usage:
188
-
189
- 1. Find definition of a Go function:
190
- lsp("definition", file="main.go", line=10, character=15)
191
-
192
- 2. Find all references to a Python class:
193
- lsp("references", file="models.py", line=25, character=10)
194
-
195
- 3. Rename a TypeScript variable:
196
- lsp("rename", file="app.ts", line=30, character=20, new_name="newVarName")
197
-
198
- 4. Get diagnostics for a Rust file:
199
- lsp("diagnostics", file="lib.rs")
200
-
201
- The tool automatically detects the language based on file extension and
202
- installs the appropriate language server if not already available.
203
- """
204
-
457
+ """Enhanced LSP tool with full protocol support and compilation capabilities."""
458
+
205
459
  name = "lsp"
206
- description = """Language Server Protocol tool for code intelligence.
460
+ description = """Enhanced Language Server Protocol tool with compilation support.
207
461
 
208
462
  Actions:
209
- - definition: Go to definition of symbol at position
210
- - references: Find all references to symbol
463
+ - definition: Go to definition of symbol
464
+ - references: Find all references
211
465
  - rename: Rename symbol across codebase
212
- - diagnostics: Get errors and warnings for file
213
- - hover: Get hover information at position
214
- - completion: Get code completions at position
466
+ - diagnostics: Get errors and warnings
467
+ - hover: Get hover information
468
+ - completion: Get code completions
469
+ - compile: Compile source code
470
+ - run: Compile and run code
215
471
  - status: Check LSP server status
216
472
 
217
- The tool automatically installs language servers as needed.
218
- Supported languages: Go, Python, TypeScript/JavaScript, Rust, Java, C/C++, Ruby, Lua
473
+ Supported languages: C, C++, Go, JavaScript, TypeScript, Rust, Zig, Python
219
474
  """
220
-
475
+
221
476
  def __init__(self):
222
477
  super().__init__()
223
- self.servers: Dict[str, LSPServer] = {}
478
+ self.clients: Dict[str, LSPClient] = {}
224
479
  self.logger = logging.getLogger(__name__)
225
-
226
- def _get_language_from_file(self, file_path: str) -> Optional[str]:
480
+
481
+ def _detect_language(self, file_path: str) -> Optional[str]:
227
482
  """Detect language from file extension."""
228
483
  ext = Path(file_path).suffix.lower()
229
-
230
- for lang, config in LSP_SERVERS.items():
484
+
485
+ for lang, config in ENHANCED_LSP_SERVERS.items():
231
486
  if ext in config["file_extensions"]:
232
487
  return lang
233
-
488
+
234
489
  return None
235
-
490
+
236
491
  def _find_project_root(self, file_path: str, language: str) -> str:
237
- """Find project root based on language markers."""
238
- markers = LSP_SERVERS[language]["root_markers"]
492
+ """Find project root based on markers."""
493
+ markers = ENHANCED_LSP_SERVERS[language]["root_markers"]
239
494
  path = Path(file_path).resolve()
240
-
495
+
241
496
  for parent in path.parents:
242
497
  for marker in markers:
243
498
  if (parent / marker).exists():
244
499
  return str(parent)
245
-
500
+
246
501
  return str(path.parent)
247
-
248
- async def _check_lsp_installed(self, language: str) -> bool:
249
- """Check if LSP server is installed."""
250
- config = LSP_SERVERS.get(language)
251
- if not config:
252
- return False
253
-
254
- try:
255
- result = await asyncio.create_subprocess_exec(
256
- *config["check_cmd"],
257
- stdout=asyncio.subprocess.PIPE,
258
- stderr=asyncio.subprocess.PIPE,
259
- )
260
- await result.communicate()
261
- return result.returncode == 0
262
- except (FileNotFoundError, OSError):
263
- return False
264
-
265
- async def _install_lsp(self, language: str) -> bool:
266
- """Install LSP server for language."""
267
- config = LSP_SERVERS.get(language)
268
- if not config:
269
- return False
270
-
271
- self.logger.info(f"Installing {config['name']} for {language}")
272
-
502
+
503
+ async def _get_client(self, language: str, root_uri: str) -> Optional[LSPClient]:
504
+ """Get or create LSP client."""
505
+ key = f"{language}:{root_uri}"
506
+
507
+ if key not in self.clients:
508
+ client = LSPClient(language, root_uri)
509
+ if await client.start():
510
+ self.clients[key] = client
511
+ else:
512
+ return None
513
+
514
+ return self.clients[key]
515
+
516
+ async def _compile_code(self, file_path: str, language: str) -> Dict[str, Any]:
517
+ """Compile source code."""
518
+ config = ENHANCED_LSP_SERVERS[language]
519
+
520
+ if "compile_cmd" not in config:
521
+ return {"error": f"Compilation not supported for {language}"}
522
+
523
+ # Prepare compile command
524
+ input_file = Path(file_path)
525
+ output_file = input_file.stem
526
+
527
+ compile_cmd = [
528
+ part.replace("{input}", str(input_file)).replace("{output}", output_file)
529
+ for part in config["compile_cmd"]
530
+ ]
531
+
273
532
  try:
274
- # Check if installer is available
275
- installer = config["install_cmd"][0]
276
- if not shutil.which(installer):
277
- self.logger.error(f"Installer {installer} not found")
278
- return False
279
-
280
- # Run installation command
533
+ # Run compilation
281
534
  result = await asyncio.create_subprocess_exec(
282
- *config["install_cmd"],
535
+ *compile_cmd,
283
536
  stdout=asyncio.subprocess.PIPE,
284
537
  stderr=asyncio.subprocess.PIPE,
538
+ cwd=input_file.parent,
285
539
  )
286
-
540
+
287
541
  stdout, stderr = await result.communicate()
288
-
289
- if result.returncode != 0:
290
- self.logger.error(f"Installation failed: {stderr.decode()}")
291
- return False
292
-
293
- self.logger.info(f"Successfully installed {config['name']}")
294
- return True
295
-
542
+
543
+ if result.returncode == 0:
544
+ return {
545
+ "success": True,
546
+ "output_file": output_file,
547
+ "stdout": stdout.decode(),
548
+ "stderr": stderr.decode(),
549
+ }
550
+ else:
551
+ return {
552
+ "success": False,
553
+ "error": stderr.decode(),
554
+ "stdout": stdout.decode(),
555
+ }
556
+
296
557
  except Exception as e:
297
- self.logger.error(f"Installation error: {e}")
298
- return False
299
-
300
- async def _ensure_lsp_running(
301
- self, language: str, root_uri: str
302
- ) -> Optional[LSPServer]:
303
- """Ensure LSP server is running for language."""
304
- # Check if already running
305
- server_key = f"{language}:{root_uri}"
306
- if server_key in self.servers:
307
- server = self.servers[server_key]
308
- if server.process and server.process.returncode is None:
309
- return server
310
-
311
- # Check if installed
312
- if not await self._check_lsp_installed(language):
313
- # Try to install
314
- if not await self._install_lsp(language):
315
- return None
316
-
317
- # Start LSP server
318
- config = LSP_SERVERS[language]
319
-
558
+ return {"success": False, "error": str(e)}
559
+
560
+ async def _run_code(self, file_path: str, language: str) -> Dict[str, Any]:
561
+ """Compile and run code."""
562
+ config = ENHANCED_LSP_SERVERS[language]
563
+
564
+ # Compile if needed
565
+ if "compile_cmd" in config:
566
+ compile_result = await self._compile_code(file_path, language)
567
+ if not compile_result.get("success"):
568
+ return compile_result
569
+
570
+ run_file = compile_result["output_file"]
571
+ else:
572
+ run_file = file_path
573
+
574
+ # Run the code
575
+ run_cmd = [
576
+ part.replace("{input}", run_file).replace("{output}", run_file)
577
+ for part in config["run_cmd"]
578
+ ]
579
+
320
580
  try:
321
- process = await asyncio.create_subprocess_exec(
322
- *config["start_cmd"],
323
- stdin=asyncio.subprocess.PIPE,
581
+ result = await asyncio.create_subprocess_exec(
582
+ *run_cmd,
324
583
  stdout=asyncio.subprocess.PIPE,
325
584
  stderr=asyncio.subprocess.PIPE,
326
- cwd=root_uri,
327
- )
328
-
329
- server = LSPServer(
330
- language=language, process=process, config=config, root_uri=root_uri
585
+ cwd=Path(file_path).parent,
331
586
  )
332
-
333
- # Initialize LSP
334
- await self._initialize_lsp(server)
335
-
336
- self.servers[server_key] = server
337
- return server
338
-
339
- except Exception as e:
340
- self.logger.error(f"Failed to start LSP: {e}")
341
- return None
342
-
343
- async def _initialize_lsp(self, server: LSPServer):
344
- """Send initialize request to LSP server."""
345
- # This is a simplified initialization
346
- # In a real implementation, you'd use the full LSP protocol
347
- init_params = {
348
- "processId": os.getpid(),
349
- "rootUri": f"file://{server.root_uri}",
350
- "capabilities": {
351
- "textDocument": {
352
- "definition": {"dynamicRegistration": True},
353
- "references": {"dynamicRegistration": True},
354
- "rename": {"dynamicRegistration": True},
355
- }
356
- },
357
- }
358
-
359
- # Send initialize request
360
- request = {
361
- "jsonrpc": "2.0",
362
- "id": 1,
363
- "method": "initialize",
364
- "params": init_params,
365
- }
366
-
367
- await self._send_request(server, request)
368
- server.initialized = True
369
-
370
- async def _send_request(
371
- self, server: LSPServer, request: Dict[str, Any]
372
- ) -> Optional[Dict[str, Any]]:
373
- """Send JSON-RPC request to LSP server."""
374
- if not server.process or server.process.returncode is not None:
375
- return None
376
-
377
- try:
378
- # Serialize request
379
- request_str = json.dumps(request)
380
- content_length = len(request_str.encode("utf-8"))
381
-
382
- # Send LSP message
383
- message = f"Content-Length: {content_length}\r\n\r\n{request_str}"
384
- server.process.stdin.write(message.encode("utf-8"))
385
- await server.process.stdin.drain()
386
-
387
- # Read response (simplified - real implementation needs proper parsing)
388
- # This is a placeholder - actual LSP requires parsing Content-Length headers
389
- response_data = await server.process.stdout.readline()
390
-
391
- if response_data:
392
- return json.loads(response_data.decode("utf-8"))
393
-
587
+
588
+ stdout, stderr = await result.communicate()
589
+
590
+ return {
591
+ "success": result.returncode == 0,
592
+ "stdout": stdout.decode(),
593
+ "stderr": stderr.decode(),
594
+ "exit_code": result.returncode,
595
+ }
596
+
394
597
  except Exception as e:
395
- self.logger.error(f"LSP communication error: {e}")
396
-
397
- return None
398
-
598
+ return {"success": False, "error": str(e)}
599
+
399
600
  async def run(
400
601
  self,
401
602
  action: str,
@@ -405,193 +606,132 @@ class LSPTool(BaseTool):
405
606
  new_name: Optional[str] = None,
406
607
  **kwargs,
407
608
  ) -> MCPResourceDocument:
408
- """Execute LSP action.
409
-
410
- Args:
411
- action: LSP action (definition, references, rename, diagnostics, hover, completion, status)
412
- file: File path to analyze
413
- line: Line number (1-indexed)
414
- character: Character position in line (0-indexed)
415
- new_name: New name for rename action
416
- """
417
-
418
- # Validate action
419
- valid_actions = [
420
- "definition",
421
- "references",
422
- "rename",
423
- "diagnostics",
424
- "hover",
425
- "completion",
426
- "status",
427
- ]
428
- if action not in valid_actions:
429
- return MCPResourceDocument(
430
- data={
431
- "error": f"Invalid action. Must be one of: {', '.join(valid_actions)}"
432
- }
433
- )
434
-
435
- # Get language from file
436
- language = self._get_language_from_file(file)
609
+ """Execute LSP action."""
610
+
611
+ # Detect language
612
+ language = self._detect_language(file)
437
613
  if not language:
438
614
  return MCPResourceDocument(
439
615
  data={
440
616
  "error": f"Unsupported file type: {file}",
441
- "supported_languages": list(LSP_SERVERS.keys()),
617
+ "supported_languages": list(ENHANCED_LSP_SERVERS.keys()),
442
618
  }
443
619
  )
444
-
445
- # Check LSP capabilities
446
- capabilities = LSP_SERVERS[language]["capabilities"]
447
- if action not in capabilities and action != "status":
448
- return MCPResourceDocument(
449
- data={
450
- "error": f"Action '{action}' not supported for {language}",
451
- "supported_actions": capabilities,
452
- }
453
- )
454
-
455
- # Status check
456
- if action == "status":
457
- installed = await self._check_lsp_installed(language)
458
- return MCPResourceDocument(
459
- data={
460
- "language": language,
461
- "lsp_server": LSP_SERVERS[language]["name"],
462
- "installed": installed,
463
- "capabilities": capabilities,
464
- }
465
- )
466
-
467
- # Find project root
620
+
621
+ # Handle compile and run actions
622
+ if action == "compile":
623
+ result = await self._compile_code(file, language)
624
+ return MCPResourceDocument(data=result)
625
+
626
+ elif action == "run":
627
+ result = await self._run_code(file, language)
628
+ return MCPResourceDocument(data=result)
629
+
630
+ # Get LSP client
468
631
  root_uri = self._find_project_root(file, language)
469
-
470
- # Ensure LSP is running
471
- server = await self._ensure_lsp_running(language, root_uri)
472
- if not server:
632
+ client = await self._get_client(language, root_uri)
633
+
634
+ if not client:
473
635
  return MCPResourceDocument(
474
- data={
475
- "error": f"Failed to start LSP server for {language}",
476
- "install_command": " ".join(LSP_SERVERS[language]["install_cmd"]),
477
- }
636
+ data={"error": f"Failed to start LSP server for {language}"}
478
637
  )
479
-
480
- # Execute action
481
- result = await self._execute_lsp_action(
482
- server, action, file, line, character, new_name
638
+
639
+ # Open the file
640
+ file_uri = f"file://{Path(file).resolve()}"
641
+ with open(file, "r") as f:
642
+ content = f.read()
643
+
644
+ await client.send_notification(
645
+ "textDocument/didOpen",
646
+ {
647
+ "textDocument": {
648
+ "uri": file_uri,
649
+ "languageId": language,
650
+ "version": 1,
651
+ "text": content,
652
+ }
653
+ },
483
654
  )
484
-
485
- return MCPResourceDocument(data=result)
486
-
487
- async def call(self, **kwargs) -> str:
488
- """Tool interface for MCP - converts result to JSON string."""
489
- result = await self.run(**kwargs)
490
- return result.to_json_string()
491
-
492
- def register(self, mcp_server) -> None:
493
- """Register tool with MCP server."""
494
-
495
- @mcp_server.tool(name=self.name, description=self.description)
496
- async def lsp_handler(
497
- action: str,
498
- file: str,
499
- line: Optional[int] = None,
500
- character: Optional[int] = None,
501
- new_name: Optional[str] = None,
502
- ) -> str:
503
- """Execute LSP action."""
504
- return await self.call(
505
- action=action,
506
- file=file,
507
- line=line,
508
- character=character,
509
- new_name=new_name,
510
- )
511
-
512
- async def _execute_lsp_action(
513
- self,
514
- server: LSPServer,
515
- action: str,
516
- file: str,
517
- line: Optional[int],
518
- character: Optional[int],
519
- new_name: Optional[str],
520
- ) -> Dict[str, Any]:
521
- """Execute specific LSP action."""
522
-
523
- # This is a simplified implementation
524
- # Real implementation would use proper LSP protocol
525
-
655
+
656
+ # Execute LSP action
526
657
  if action == "definition":
527
- # textDocument/definition request
528
- return {
529
- "action": "definition",
530
- "file": file,
531
- "position": {"line": line, "character": character},
532
- "note": "LSP integration pending full implementation",
533
- "fallback": "Use mcp__lsp__find_definition tool for now",
534
- }
535
-
658
+ result = await client.send_request(
659
+ "textDocument/definition",
660
+ {
661
+ "textDocument": {"uri": file_uri},
662
+ "position": {"line": line - 1, "character": character},
663
+ },
664
+ )
665
+
536
666
  elif action == "references":
537
- # textDocument/references request
538
- return {
539
- "action": "references",
540
- "file": file,
541
- "position": {"line": line, "character": character},
542
- "note": "LSP integration pending full implementation",
543
- "fallback": "Use mcp__lsp__find_references tool for now",
544
- }
545
-
667
+ result = await client.send_request(
668
+ "textDocument/references",
669
+ {
670
+ "textDocument": {"uri": file_uri},
671
+ "position": {"line": line - 1, "character": character},
672
+ "context": {"includeDeclaration": True},
673
+ },
674
+ )
675
+
546
676
  elif action == "rename":
547
- # textDocument/rename request
548
- return {
549
- "action": "rename",
550
- "file": file,
551
- "position": {"line": line, "character": character},
552
- "new_name": new_name,
553
- "note": "LSP integration pending full implementation",
554
- "fallback": "Use mcp__lsp__rename_symbol tool for now",
555
- }
556
-
677
+ result = await client.send_request(
678
+ "textDocument/rename",
679
+ {
680
+ "textDocument": {"uri": file_uri},
681
+ "position": {"line": line - 1, "character": character},
682
+ "newName": new_name,
683
+ },
684
+ )
685
+
557
686
  elif action == "diagnostics":
558
- # textDocument/diagnostic request
559
- return {
560
- "action": "diagnostics",
561
- "file": file,
562
- "note": "LSP integration pending full implementation",
563
- "fallback": "Use mcp__lsp__get_diagnostics tool for now",
564
- }
565
-
687
+ result = await client.send_request(
688
+ "textDocument/diagnostic",
689
+ {
690
+ "textDocument": {"uri": file_uri},
691
+ },
692
+ )
693
+
566
694
  elif action == "hover":
567
- # textDocument/hover request
568
- return {
569
- "action": "hover",
570
- "file": file,
571
- "position": {"line": line, "character": character},
572
- "note": "LSP integration pending full implementation",
573
- }
574
-
695
+ result = await client.send_request(
696
+ "textDocument/hover",
697
+ {
698
+ "textDocument": {"uri": file_uri},
699
+ "position": {"line": line - 1, "character": character},
700
+ },
701
+ )
702
+
575
703
  elif action == "completion":
576
- # textDocument/completion request
577
- return {
578
- "action": "completion",
579
- "file": file,
580
- "position": {"line": line, "character": character},
581
- "note": "LSP integration pending full implementation",
582
- }
583
-
584
- return {"error": "Unknown action"}
585
-
704
+ result = await client.send_request(
705
+ "textDocument/completion",
706
+ {
707
+ "textDocument": {"uri": file_uri},
708
+ "position": {"line": line - 1, "character": character},
709
+ },
710
+ )
711
+
712
+ else:
713
+ result = {"error": f"Unknown action: {action}"}
714
+
715
+ # Close the file
716
+ await client.send_notification(
717
+ "textDocument/didClose",
718
+ {"textDocument": {"uri": file_uri}},
719
+ )
720
+
721
+ return MCPResourceDocument(data=result)
722
+
723
+ async def call(self, **kwargs) -> str:
724
+ """Tool interface for MCP."""
725
+ result = await self.run(**kwargs)
726
+ return result.to_json_string()
727
+
586
728
  async def cleanup(self):
587
- """Clean up LSP servers."""
588
- for server in self.servers.values():
589
- if server.process and server.process.returncode is None:
590
- server.process.terminate()
591
- await server.process.wait()
729
+ """Clean up all LSP clients."""
730
+ for client in self.clients.values():
731
+ await client.shutdown()
592
732
 
593
733
 
594
- # Tool registration
734
+ # Factory function
595
735
  def create_lsp_tool():
596
- """Factory function to create LSP tool."""
597
- return LSPTool()
736
+ """Create enhanced LSP tool."""
737
+ return LSPTool()