hanzo-mcp 0.9.0__py3-none-any.whl → 0.9.1__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 (135) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/analytics/posthog_analytics.py +14 -1
  3. hanzo_mcp/cli.py +108 -4
  4. hanzo_mcp/server.py +11 -0
  5. hanzo_mcp/tools/__init__.py +3 -16
  6. hanzo_mcp/tools/agent/__init__.py +5 -0
  7. hanzo_mcp/tools/agent/agent.py +5 -0
  8. hanzo_mcp/tools/agent/agent_tool.py +3 -17
  9. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +623 -0
  10. hanzo_mcp/tools/agent/clarification_tool.py +7 -1
  11. hanzo_mcp/tools/agent/claude_desktop_auth.py +16 -6
  12. hanzo_mcp/tools/agent/cli_agent_base.py +5 -0
  13. hanzo_mcp/tools/agent/cli_tools.py +26 -0
  14. hanzo_mcp/tools/agent/code_auth_tool.py +5 -0
  15. hanzo_mcp/tools/agent/critic_tool.py +7 -1
  16. hanzo_mcp/tools/agent/iching_tool.py +5 -0
  17. hanzo_mcp/tools/agent/network_tool.py +5 -0
  18. hanzo_mcp/tools/agent/review_tool.py +7 -1
  19. hanzo_mcp/tools/agent/swarm_alias.py +5 -0
  20. hanzo_mcp/tools/agent/swarm_tool.py +701 -0
  21. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +554 -0
  22. hanzo_mcp/tools/agent/unified_cli_tools.py +5 -0
  23. hanzo_mcp/tools/common/auto_timeout.py +234 -0
  24. hanzo_mcp/tools/common/base.py +4 -0
  25. hanzo_mcp/tools/common/batch_tool.py +5 -0
  26. hanzo_mcp/tools/common/config_tool.py +5 -0
  27. hanzo_mcp/tools/common/critic_tool.py +5 -0
  28. hanzo_mcp/tools/common/paginated_base.py +4 -0
  29. hanzo_mcp/tools/common/permissions.py +38 -12
  30. hanzo_mcp/tools/common/personality.py +673 -980
  31. hanzo_mcp/tools/common/stats.py +5 -0
  32. hanzo_mcp/tools/common/thinking_tool.py +5 -0
  33. hanzo_mcp/tools/common/timeout_parser.py +103 -0
  34. hanzo_mcp/tools/common/tool_disable.py +5 -0
  35. hanzo_mcp/tools/common/tool_enable.py +5 -0
  36. hanzo_mcp/tools/common/tool_list.py +5 -0
  37. hanzo_mcp/tools/config/config_tool.py +5 -0
  38. hanzo_mcp/tools/config/mode_tool.py +5 -0
  39. hanzo_mcp/tools/database/graph.py +5 -0
  40. hanzo_mcp/tools/database/graph_add.py +5 -0
  41. hanzo_mcp/tools/database/graph_query.py +5 -0
  42. hanzo_mcp/tools/database/graph_remove.py +5 -0
  43. hanzo_mcp/tools/database/graph_search.py +5 -0
  44. hanzo_mcp/tools/database/graph_stats.py +5 -0
  45. hanzo_mcp/tools/database/sql.py +5 -0
  46. hanzo_mcp/tools/database/sql_query.py +2 -0
  47. hanzo_mcp/tools/database/sql_search.py +5 -0
  48. hanzo_mcp/tools/database/sql_stats.py +5 -0
  49. hanzo_mcp/tools/editor/neovim_command.py +5 -0
  50. hanzo_mcp/tools/editor/neovim_edit.py +7 -2
  51. hanzo_mcp/tools/editor/neovim_session.py +5 -0
  52. hanzo_mcp/tools/filesystem/__init__.py +23 -26
  53. hanzo_mcp/tools/filesystem/ast_tool.py +2 -3
  54. hanzo_mcp/tools/filesystem/base.py +0 -16
  55. hanzo_mcp/tools/filesystem/batch_search.py +825 -0
  56. hanzo_mcp/tools/filesystem/content_replace.py +5 -3
  57. hanzo_mcp/tools/filesystem/diff.py +5 -0
  58. hanzo_mcp/tools/filesystem/directory_tree.py +34 -281
  59. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +345 -0
  60. hanzo_mcp/tools/filesystem/edit.py +5 -4
  61. hanzo_mcp/tools/filesystem/find.py +177 -311
  62. hanzo_mcp/tools/filesystem/find_files.py +370 -0
  63. hanzo_mcp/tools/filesystem/git_search.py +5 -3
  64. hanzo_mcp/tools/filesystem/grep.py +454 -0
  65. hanzo_mcp/tools/filesystem/multi_edit.py +5 -4
  66. hanzo_mcp/tools/filesystem/read.py +11 -8
  67. hanzo_mcp/tools/filesystem/rules_tool.py +5 -3
  68. hanzo_mcp/tools/filesystem/search_tool.py +728 -0
  69. hanzo_mcp/tools/filesystem/symbols_tool.py +510 -0
  70. hanzo_mcp/tools/filesystem/tree.py +273 -0
  71. hanzo_mcp/tools/filesystem/watch.py +6 -1
  72. hanzo_mcp/tools/filesystem/write.py +12 -6
  73. hanzo_mcp/tools/jupyter/jupyter.py +30 -2
  74. hanzo_mcp/tools/jupyter/notebook_edit.py +298 -0
  75. hanzo_mcp/tools/jupyter/notebook_read.py +148 -0
  76. hanzo_mcp/tools/llm/consensus_tool.py +8 -6
  77. hanzo_mcp/tools/llm/llm_manage.py +5 -0
  78. hanzo_mcp/tools/llm/llm_tool.py +2 -0
  79. hanzo_mcp/tools/llm/llm_unified.py +5 -0
  80. hanzo_mcp/tools/llm/provider_tools.py +5 -0
  81. hanzo_mcp/tools/lsp/lsp_tool.py +475 -622
  82. hanzo_mcp/tools/mcp/mcp_add.py +7 -2
  83. hanzo_mcp/tools/mcp/mcp_remove.py +15 -2
  84. hanzo_mcp/tools/mcp/mcp_stats.py +5 -0
  85. hanzo_mcp/tools/mcp/mcp_tool.py +5 -0
  86. hanzo_mcp/tools/memory/knowledge_tools.py +14 -0
  87. hanzo_mcp/tools/memory/memory_tools.py +17 -0
  88. hanzo_mcp/tools/search/find_tool.py +5 -3
  89. hanzo_mcp/tools/search/unified_search.py +3 -1
  90. hanzo_mcp/tools/shell/__init__.py +2 -14
  91. hanzo_mcp/tools/shell/base_process.py +4 -2
  92. hanzo_mcp/tools/shell/bash_tool.py +2 -0
  93. hanzo_mcp/tools/shell/command_executor.py +7 -7
  94. hanzo_mcp/tools/shell/logs.py +5 -0
  95. hanzo_mcp/tools/shell/npx.py +5 -0
  96. hanzo_mcp/tools/shell/npx_background.py +5 -0
  97. hanzo_mcp/tools/shell/npx_tool.py +5 -0
  98. hanzo_mcp/tools/shell/open.py +5 -0
  99. hanzo_mcp/tools/shell/pkill.py +5 -0
  100. hanzo_mcp/tools/shell/process_tool.py +5 -0
  101. hanzo_mcp/tools/shell/processes.py +5 -0
  102. hanzo_mcp/tools/shell/run_background.py +5 -0
  103. hanzo_mcp/tools/shell/run_command.py +2 -0
  104. hanzo_mcp/tools/shell/run_command_windows.py +5 -0
  105. hanzo_mcp/tools/shell/streaming_command.py +5 -0
  106. hanzo_mcp/tools/shell/uvx.py +5 -0
  107. hanzo_mcp/tools/shell/uvx_background.py +5 -0
  108. hanzo_mcp/tools/shell/uvx_tool.py +5 -0
  109. hanzo_mcp/tools/shell/zsh_tool.py +3 -0
  110. hanzo_mcp/tools/todo/todo.py +5 -0
  111. hanzo_mcp/tools/todo/todo_read.py +142 -0
  112. hanzo_mcp/tools/todo/todo_write.py +367 -0
  113. hanzo_mcp/tools/vector/__init__.py +42 -95
  114. hanzo_mcp/tools/vector/index_tool.py +5 -0
  115. hanzo_mcp/tools/vector/vector.py +5 -0
  116. hanzo_mcp/tools/vector/vector_index.py +5 -0
  117. hanzo_mcp/tools/vector/vector_search.py +5 -0
  118. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.1.dist-info}/METADATA +1 -1
  119. hanzo_mcp-0.9.1.dist-info/RECORD +195 -0
  120. hanzo_mcp/tools/common/path_utils.py +0 -34
  121. hanzo_mcp/tools/compiler/__init__.py +0 -8
  122. hanzo_mcp/tools/compiler/sandboxed_compiler.py +0 -681
  123. hanzo_mcp/tools/environment/__init__.py +0 -8
  124. hanzo_mcp/tools/environment/environment_detector.py +0 -594
  125. hanzo_mcp/tools/filesystem/search.py +0 -1160
  126. hanzo_mcp/tools/framework/__init__.py +0 -8
  127. hanzo_mcp/tools/framework/framework_modes.py +0 -714
  128. hanzo_mcp/tools/memory/conversation_memory.py +0 -636
  129. hanzo_mcp/tools/shell/run_tool.py +0 -56
  130. hanzo_mcp/tools/vector/node_tool.py +0 -538
  131. hanzo_mcp/tools/vector/unified_vector.py +0 -384
  132. hanzo_mcp-0.9.0.dist-info/RECORD +0 -191
  133. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.1.dist-info}/WHEEL +0 -0
  134. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.1.dist-info}/entry_points.txt +0 -0
  135. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.1.dist-info}/top_level.txt +0 -0
@@ -1,602 +1,397 @@
1
- """Enhanced Language Server Protocol (LSP) implementation with full protocol support.
2
1
 
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.
2
+ from hanzo_mcp.tools.common.auto_timeout import auto_timeout
3
+ """Language Server Protocol (LSP) tool for code intelligence.
4
+
5
+ This tool provides on-demand LSP configuration and installation for various
6
+ programming languages. It automatically installs language servers as needed
7
+ and provides code intelligence features like go-to-definition, find references,
8
+ rename symbol, and diagnostics.
6
9
  """
7
10
 
8
11
  import os
9
- import sys
10
12
  import json
13
+ import shutil
11
14
  import asyncio
12
15
  import logging
13
- import subprocess
14
- from typing import Any, Dict, List, Optional, Tuple
16
+ from typing import Any, Dict, Optional
15
17
  from pathlib import Path
16
- from dataclasses import dataclass, field
17
- from enum import Enum
18
+ from dataclasses import dataclass
18
19
 
19
20
  from hanzo_mcp.types import MCPResourceDocument
20
21
  from hanzo_mcp.tools.common.base import BaseTool
21
22
 
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
+ # LSP server configurations
24
+ LSP_SERVERS = {
107
25
  "go": {
108
26
  "name": "gopls",
109
- "install_cmd": {
110
- "all": ["go", "install", "golang.org/x/tools/gopls@latest"],
111
- },
27
+ "install_cmd": ["go", "install", "golang.org/x/tools/gopls@latest"],
112
28
  "check_cmd": ["gopls", "version"],
113
29
  "start_cmd": ["gopls", "serve"],
114
30
  "root_markers": ["go.mod", "go.sum"],
115
31
  "file_extensions": [".go"],
116
- "compile_cmd": ["go", "build", "-o", "{output}", "{input}"],
117
- "run_cmd": ["./{output}"],
118
32
  "capabilities": [
119
- "definition", "references", "rename", "diagnostics",
120
- "hover", "completion", "formatting", "code_actions",
121
- "implementation", "type_definition"
33
+ "definition",
34
+ "references",
35
+ "rename",
36
+ "diagnostics",
37
+ "hover",
38
+ "completion",
122
39
  ],
123
40
  },
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}"],
41
+ "python": {
42
+ "name": "pylsp",
43
+ "install_cmd": ["pip", "install", "python-lsp-server[all]"],
44
+ "check_cmd": ["pylsp", "--version"],
45
+ "start_cmd": ["pylsp"],
46
+ "root_markers": ["pyproject.toml", "setup.py", "requirements.txt"],
47
+ "file_extensions": [".py"],
134
48
  "capabilities": [
135
- "definition", "references", "rename", "diagnostics",
136
- "hover", "completion", "formatting", "code_actions"
49
+ "definition",
50
+ "references",
51
+ "rename",
52
+ "diagnostics",
53
+ "hover",
54
+ "completion",
137
55
  ],
138
56
  },
139
57
  "typescript": {
140
58
  "name": "typescript-language-server",
141
- "install_cmd": {
142
- "all": ["npm", "install", "-g", "typescript", "typescript-language-server"],
143
- },
59
+ "install_cmd": [
60
+ "npm",
61
+ "install",
62
+ "-g",
63
+ "typescript",
64
+ "typescript-language-server",
65
+ ],
144
66
  "check_cmd": ["typescript-language-server", "--version"],
145
67
  "start_cmd": ["typescript-language-server", "--stdio"],
146
68
  "root_markers": ["tsconfig.json", "package.json"],
147
- "file_extensions": [".ts", ".tsx"],
148
- "compile_cmd": ["tsc", "{input}", "--outFile", "{output}.js"],
149
- "run_cmd": ["node", "{output}.js"],
69
+ "file_extensions": [".ts", ".tsx", ".js", ".jsx"],
150
70
  "capabilities": [
151
- "definition", "references", "rename", "diagnostics",
152
- "hover", "completion", "formatting", "code_actions",
153
- "implementation", "type_definition"
71
+ "definition",
72
+ "references",
73
+ "rename",
74
+ "diagnostics",
75
+ "hover",
76
+ "completion",
154
77
  ],
155
78
  },
156
79
  "rust": {
157
80
  "name": "rust-analyzer",
158
- "install_cmd": {
159
- "all": ["rustup", "component", "add", "rust-analyzer"],
160
- },
81
+ "install_cmd": ["rustup", "component", "add", "rust-analyzer"],
161
82
  "check_cmd": ["rust-analyzer", "--version"],
162
83
  "start_cmd": ["rust-analyzer"],
163
84
  "root_markers": ["Cargo.toml"],
164
85
  "file_extensions": [".rs"],
165
- "compile_cmd": ["rustc", "-o", "{output}", "{input}"],
166
- "run_cmd": ["./{output}"],
167
86
  "capabilities": [
168
- "definition", "references", "rename", "diagnostics",
169
- "hover", "completion", "formatting", "code_actions",
170
- "inlay_hints", "implementation", "type_definition"
87
+ "definition",
88
+ "references",
89
+ "rename",
90
+ "diagnostics",
91
+ "hover",
92
+ "completion",
93
+ "inlay_hints",
171
94
  ],
172
95
  },
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}"],
96
+ "java": {
97
+ "name": "jdtls",
98
+ "install_cmd": ["brew", "install", "jdtls"], # Or manual download
99
+ "check_cmd": ["jdtls", "--version"],
100
+ "start_cmd": ["jdtls"],
101
+ "root_markers": ["pom.xml", "build.gradle", "build.gradle.kts"],
102
+ "file_extensions": [".java"],
187
103
  "capabilities": [
188
- "definition", "references", "rename", "diagnostics",
189
- "hover", "completion", "formatting", "code_actions",
190
- "semantic_tokens"
104
+ "definition",
105
+ "references",
106
+ "rename",
107
+ "diagnostics",
108
+ "hover",
109
+ "completion",
191
110
  ],
192
111
  },
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}"],
112
+ "cpp": {
113
+ "name": "clangd",
114
+ "install_cmd": ["brew", "install", "llvm"], # Or apt-get install clangd
115
+ "check_cmd": ["clangd", "--version"],
116
+ "start_cmd": ["clangd"],
117
+ "root_markers": ["compile_commands.json", "CMakeLists.txt"],
118
+ "file_extensions": [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp"],
119
+ "capabilities": [
120
+ "definition",
121
+ "references",
122
+ "rename",
123
+ "diagnostics",
124
+ "hover",
125
+ "completion",
126
+ ],
127
+ },
128
+ "ruby": {
129
+ "name": "solargraph",
130
+ "install_cmd": ["gem", "install", "solargraph"],
131
+ "check_cmd": ["solargraph", "--version"],
132
+ "start_cmd": ["solargraph", "stdio"],
133
+ "root_markers": ["Gemfile", ".solargraph.yml"],
134
+ "file_extensions": [".rb"],
203
135
  "capabilities": [
204
- "definition", "references", "rename", "diagnostics",
205
- "hover", "completion", "formatting", "code_actions"
136
+ "definition",
137
+ "references",
138
+ "diagnostics",
139
+ "hover",
140
+ "completion",
141
+ ],
142
+ },
143
+ "lua": {
144
+ "name": "lua-language-server",
145
+ "install_cmd": ["brew", "install", "lua-language-server"],
146
+ "check_cmd": ["lua-language-server", "--version"],
147
+ "start_cmd": ["lua-language-server"],
148
+ "root_markers": [".luarc.json"],
149
+ "file_extensions": [".lua"],
150
+ "capabilities": [
151
+ "definition",
152
+ "references",
153
+ "rename",
154
+ "diagnostics",
155
+ "hover",
156
+ "completion",
206
157
  ],
207
158
  },
208
159
  }
209
160
 
210
161
 
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()
162
+ @dataclass
163
+ class LSPServer:
164
+ """Represents an LSP server instance."""
165
+
166
+ language: str
167
+ process: Optional[asyncio.subprocess.Process]
168
+ config: Dict[str, Any]
169
+ root_uri: str
170
+ initialized: bool = False
454
171
 
455
172
 
456
173
  class LSPTool(BaseTool):
457
- """Enhanced LSP tool with full protocol support and compilation capabilities."""
458
-
174
+ """Language Server Protocol tool for code intelligence.
175
+
176
+ This tool automatically configures and manages LSP servers for various
177
+ programming languages. It installs language servers on-demand and provides
178
+ code intelligence features.
179
+
180
+ Features:
181
+ - Auto-installation of language servers
182
+ - Go-to-definition
183
+ - Find references
184
+ - Rename symbol
185
+ - Get diagnostics
186
+ - Hover information
187
+ - Code completion
188
+
189
+ Example usage:
190
+
191
+ 1. Find definition of a Go function:
192
+ lsp("definition", file="main.go", line=10, character=15)
193
+
194
+ 2. Find all references to a Python class:
195
+ lsp("references", file="models.py", line=25, character=10)
196
+
197
+ 3. Rename a TypeScript variable:
198
+ lsp("rename", file="app.ts", line=30, character=20, new_name="newVarName")
199
+
200
+ 4. Get diagnostics for a Rust file:
201
+ lsp("diagnostics", file="lib.rs")
202
+
203
+ The tool automatically detects the language based on file extension and
204
+ installs the appropriate language server if not already available.
205
+ """
206
+
459
207
  name = "lsp"
460
- description = """Enhanced Language Server Protocol tool with compilation support.
208
+ description = """Language Server Protocol tool for code intelligence.
461
209
 
462
210
  Actions:
463
- - definition: Go to definition of symbol
464
- - references: Find all references
211
+ - definition: Go to definition of symbol at position
212
+ - references: Find all references to symbol
465
213
  - rename: Rename symbol across codebase
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
214
+ - diagnostics: Get errors and warnings for file
215
+ - hover: Get hover information at position
216
+ - completion: Get code completions at position
471
217
  - status: Check LSP server status
472
218
 
473
- Supported languages: C, C++, Go, JavaScript, TypeScript, Rust, Zig, Python
219
+ The tool automatically installs language servers as needed.
220
+ Supported languages: Go, Python, TypeScript/JavaScript, Rust, Java, C/C++, Ruby, Lua
474
221
  """
475
-
222
+
476
223
  def __init__(self):
477
224
  super().__init__()
478
- self.clients: Dict[str, LSPClient] = {}
225
+ self.servers: Dict[str, LSPServer] = {}
479
226
  self.logger = logging.getLogger(__name__)
480
-
481
- def _detect_language(self, file_path: str) -> Optional[str]:
227
+
228
+ def _get_language_from_file(self, file_path: str) -> Optional[str]:
482
229
  """Detect language from file extension."""
483
230
  ext = Path(file_path).suffix.lower()
484
-
485
- for lang, config in ENHANCED_LSP_SERVERS.items():
231
+
232
+ for lang, config in LSP_SERVERS.items():
486
233
  if ext in config["file_extensions"]:
487
234
  return lang
488
-
235
+
489
236
  return None
490
-
237
+
491
238
  def _find_project_root(self, file_path: str, language: str) -> str:
492
- """Find project root based on markers."""
493
- markers = ENHANCED_LSP_SERVERS[language]["root_markers"]
239
+ """Find project root based on language markers."""
240
+ markers = LSP_SERVERS[language]["root_markers"]
494
241
  path = Path(file_path).resolve()
495
-
242
+
496
243
  for parent in path.parents:
497
244
  for marker in markers:
498
245
  if (parent / marker).exists():
499
246
  return str(parent)
500
-
247
+
501
248
  return str(path.parent)
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
-
249
+
250
+ async def _check_lsp_installed(self, language: str) -> bool:
251
+ """Check if LSP server is installed."""
252
+ config = LSP_SERVERS.get(language)
253
+ if not config:
254
+ return False
255
+
532
256
  try:
533
- # Run compilation
534
257
  result = await asyncio.create_subprocess_exec(
535
- *compile_cmd,
258
+ *config["check_cmd"],
536
259
  stdout=asyncio.subprocess.PIPE,
537
260
  stderr=asyncio.subprocess.PIPE,
538
- cwd=input_file.parent,
539
261
  )
540
-
541
- stdout, stderr = await result.communicate()
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
-
557
- except Exception as e:
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
-
262
+ await result.communicate()
263
+ return result.returncode == 0
264
+ except (FileNotFoundError, OSError):
265
+ return False
266
+
267
+ async def _install_lsp(self, language: str) -> bool:
268
+ """Install LSP server for language."""
269
+ config = LSP_SERVERS.get(language)
270
+ if not config:
271
+ return False
272
+
273
+ self.logger.info(f"Installing {config['name']} for {language}")
274
+
580
275
  try:
276
+ # Check if installer is available
277
+ installer = config["install_cmd"][0]
278
+ if not shutil.which(installer):
279
+ self.logger.error(f"Installer {installer} not found")
280
+ return False
281
+
282
+ # Run installation command
581
283
  result = await asyncio.create_subprocess_exec(
582
- *run_cmd,
284
+ *config["install_cmd"],
583
285
  stdout=asyncio.subprocess.PIPE,
584
286
  stderr=asyncio.subprocess.PIPE,
585
- cwd=Path(file_path).parent,
586
287
  )
587
-
288
+
588
289
  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
-
290
+
291
+ if result.returncode != 0:
292
+ self.logger.error(f"Installation failed: {stderr.decode()}")
293
+ return False
294
+
295
+ self.logger.info(f"Successfully installed {config['name']}")
296
+ return True
297
+
597
298
  except Exception as e:
598
- return {"success": False, "error": str(e)}
599
-
299
+ self.logger.error(f"Installation error: {e}")
300
+ return False
301
+
302
+ async def _ensure_lsp_running(self, language: str, root_uri: str) -> 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
+
320
+ try:
321
+ process = await asyncio.create_subprocess_exec(
322
+ *config["start_cmd"],
323
+ stdin=asyncio.subprocess.PIPE,
324
+ stdout=asyncio.subprocess.PIPE,
325
+ stderr=asyncio.subprocess.PIPE,
326
+ cwd=root_uri,
327
+ )
328
+
329
+ server = LSPServer(language=language, process=process, config=config, root_uri=root_uri)
330
+
331
+ # Initialize LSP
332
+ await self._initialize_lsp(server)
333
+
334
+ self.servers[server_key] = server
335
+ return server
336
+
337
+ except Exception as e:
338
+ self.logger.error(f"Failed to start LSP: {e}")
339
+ return None
340
+
341
+ async def _initialize_lsp(self, server: LSPServer):
342
+ """Send initialize request to LSP server."""
343
+ # This is a simplified initialization
344
+ # In a real implementation, you'd use the full LSP protocol
345
+ init_params = {
346
+ "processId": os.getpid(),
347
+ "rootUri": f"file://{server.root_uri}",
348
+ "capabilities": {
349
+ "textDocument": {
350
+ "definition": {"dynamicRegistration": True},
351
+ "references": {"dynamicRegistration": True},
352
+ "rename": {"dynamicRegistration": True},
353
+ }
354
+ },
355
+ }
356
+
357
+ # Send initialize request
358
+ request = {
359
+ "jsonrpc": "2.0",
360
+ "id": 1,
361
+ "method": "initialize",
362
+ "params": init_params,
363
+ }
364
+
365
+ await self._send_request(server, request)
366
+ server.initialized = True
367
+
368
+ async def _send_request(self, server: LSPServer, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
369
+ """Send JSON-RPC request to LSP server."""
370
+ if not server.process or server.process.returncode is not None:
371
+ return None
372
+
373
+ try:
374
+ # Serialize request
375
+ request_str = json.dumps(request)
376
+ content_length = len(request_str.encode("utf-8"))
377
+
378
+ # Send LSP message
379
+ message = f"Content-Length: {content_length}\r\n\r\n{request_str}"
380
+ server.process.stdin.write(message.encode("utf-8"))
381
+ await server.process.stdin.drain()
382
+
383
+ # Read response (simplified - real implementation needs proper parsing)
384
+ # This is a placeholder - actual LSP requires parsing Content-Length headers
385
+ response_data = await server.process.stdout.readline()
386
+
387
+ if response_data:
388
+ return json.loads(response_data.decode("utf-8"))
389
+
390
+ except Exception as e:
391
+ self.logger.error(f"LSP communication error: {e}")
392
+
393
+ return None
394
+
600
395
  async def run(
601
396
  self,
602
397
  action: str,
@@ -606,132 +401,190 @@ class LSPTool(BaseTool):
606
401
  new_name: Optional[str] = None,
607
402
  **kwargs,
608
403
  ) -> MCPResourceDocument:
609
- """Execute LSP action."""
610
-
611
- # Detect language
612
- language = self._detect_language(file)
404
+ """Execute LSP action.
405
+
406
+ Args:
407
+ action: LSP action (definition, references, rename, diagnostics, hover, completion, status)
408
+ file: File path to analyze
409
+ line: Line number (1-indexed)
410
+ character: Character position in line (0-indexed)
411
+ new_name: New name for rename action
412
+ """
413
+
414
+ # Validate action
415
+ valid_actions = [
416
+ "definition",
417
+ "references",
418
+ "rename",
419
+ "diagnostics",
420
+ "hover",
421
+ "completion",
422
+ "status",
423
+ ]
424
+ if action not in valid_actions:
425
+ return MCPResourceDocument(data={"error": f"Invalid action. Must be one of: {', '.join(valid_actions)}"})
426
+
427
+ # Get language from file
428
+ language = self._get_language_from_file(file)
613
429
  if not language:
614
430
  return MCPResourceDocument(
615
431
  data={
616
432
  "error": f"Unsupported file type: {file}",
617
- "supported_languages": list(ENHANCED_LSP_SERVERS.keys()),
433
+ "supported_languages": list(LSP_SERVERS.keys()),
618
434
  }
619
435
  )
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
631
- root_uri = self._find_project_root(file, language)
632
- client = await self._get_client(language, root_uri)
633
-
634
- if not client:
436
+
437
+ # Check LSP capabilities
438
+ capabilities = LSP_SERVERS[language]["capabilities"]
439
+ if action not in capabilities and action != "status":
635
440
  return MCPResourceDocument(
636
- data={"error": f"Failed to start LSP server for {language}"}
637
- )
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,
441
+ data={
442
+ "error": f"Action '{action}' not supported for {language}",
443
+ "supported_actions": capabilities,
652
444
  }
653
- },
654
- )
655
-
656
- # Execute LSP action
657
- if action == "definition":
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
-
666
- elif action == "references":
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
-
676
- elif action == "rename":
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
445
  )
685
-
686
- elif action == "diagnostics":
687
- result = await client.send_request(
688
- "textDocument/diagnostic",
689
- {
690
- "textDocument": {"uri": file_uri},
691
- },
692
- )
693
-
694
- elif action == "hover":
695
- result = await client.send_request(
696
- "textDocument/hover",
697
- {
698
- "textDocument": {"uri": file_uri},
699
- "position": {"line": line - 1, "character": character},
700
- },
446
+
447
+ # Status check
448
+ if action == "status":
449
+ installed = await self._check_lsp_installed(language)
450
+ return MCPResourceDocument(
451
+ data={
452
+ "language": language,
453
+ "lsp_server": LSP_SERVERS[language]["name"],
454
+ "installed": installed,
455
+ "capabilities": capabilities,
456
+ }
701
457
  )
702
-
703
- elif action == "completion":
704
- result = await client.send_request(
705
- "textDocument/completion",
706
- {
707
- "textDocument": {"uri": file_uri},
708
- "position": {"line": line - 1, "character": character},
709
- },
458
+
459
+ # Find project root
460
+ root_uri = self._find_project_root(file, language)
461
+
462
+ # Ensure LSP is running
463
+ server = await self._ensure_lsp_running(language, root_uri)
464
+ if not server:
465
+ return MCPResourceDocument(
466
+ data={
467
+ "error": f"Failed to start LSP server for {language}",
468
+ "install_command": " ".join(LSP_SERVERS[language]["install_cmd"]),
469
+ }
710
470
  )
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
-
471
+
472
+ # Execute action
473
+ result = await self._execute_lsp_action(server, action, file, line, character, new_name)
474
+
721
475
  return MCPResourceDocument(data=result)
722
-
476
+
477
+ @auto_timeout("lsp")
478
+
479
+
723
480
  async def call(self, **kwargs) -> str:
724
- """Tool interface for MCP."""
481
+ """Tool interface for MCP - converts result to JSON string."""
725
482
  result = await self.run(**kwargs)
726
483
  return result.to_json_string()
727
-
484
+
485
+ def register(self, mcp_server) -> None:
486
+ """Register tool with MCP server."""
487
+
488
+ @mcp_server.tool(name=self.name, description=self.description)
489
+ async def lsp_handler(
490
+ action: str,
491
+ file: str,
492
+ line: Optional[int] = None,
493
+ character: Optional[int] = None,
494
+ new_name: Optional[str] = None,
495
+ ) -> str:
496
+ """Execute LSP action."""
497
+ return await self.call(
498
+ action=action,
499
+ file=file,
500
+ line=line,
501
+ character=character,
502
+ new_name=new_name,
503
+ )
504
+
505
+ async def _execute_lsp_action(
506
+ self,
507
+ server: LSPServer,
508
+ action: str,
509
+ file: str,
510
+ line: Optional[int],
511
+ character: Optional[int],
512
+ new_name: Optional[str],
513
+ ) -> Dict[str, Any]:
514
+ """Execute specific LSP action."""
515
+
516
+ # This is a simplified implementation
517
+ # Real implementation would use proper LSP protocol
518
+
519
+ if action == "definition":
520
+ # textDocument/definition request
521
+ return {
522
+ "action": "definition",
523
+ "file": file,
524
+ "position": {"line": line, "character": character},
525
+ "note": "LSP integration pending full implementation",
526
+ "fallback": "Use mcp__lsp__find_definition tool for now",
527
+ }
528
+
529
+ elif action == "references":
530
+ # textDocument/references request
531
+ return {
532
+ "action": "references",
533
+ "file": file,
534
+ "position": {"line": line, "character": character},
535
+ "note": "LSP integration pending full implementation",
536
+ "fallback": "Use mcp__lsp__find_references tool for now",
537
+ }
538
+
539
+ elif action == "rename":
540
+ # textDocument/rename request
541
+ return {
542
+ "action": "rename",
543
+ "file": file,
544
+ "position": {"line": line, "character": character},
545
+ "new_name": new_name,
546
+ "note": "LSP integration pending full implementation",
547
+ "fallback": "Use mcp__lsp__rename_symbol tool for now",
548
+ }
549
+
550
+ elif action == "diagnostics":
551
+ # textDocument/diagnostic request
552
+ return {
553
+ "action": "diagnostics",
554
+ "file": file,
555
+ "note": "LSP integration pending full implementation",
556
+ "fallback": "Use mcp__lsp__get_diagnostics tool for now",
557
+ }
558
+
559
+ elif action == "hover":
560
+ # textDocument/hover request
561
+ return {
562
+ "action": "hover",
563
+ "file": file,
564
+ "position": {"line": line, "character": character},
565
+ "note": "LSP integration pending full implementation",
566
+ }
567
+
568
+ elif action == "completion":
569
+ # textDocument/completion request
570
+ return {
571
+ "action": "completion",
572
+ "file": file,
573
+ "position": {"line": line, "character": character},
574
+ "note": "LSP integration pending full implementation",
575
+ }
576
+
577
+ return {"error": "Unknown action"}
578
+
728
579
  async def cleanup(self):
729
- """Clean up all LSP clients."""
730
- for client in self.clients.values():
731
- await client.shutdown()
580
+ """Clean up LSP servers."""
581
+ for server in self.servers.values():
582
+ if server.process and server.process.returncode is None:
583
+ server.process.terminate()
584
+ await server.process.wait()
732
585
 
733
586
 
734
- # Factory function
587
+ # Tool registration
735
588
  def create_lsp_tool():
736
- """Create enhanced LSP tool."""
737
- return LSPTool()
589
+ """Factory function to create LSP tool."""
590
+ return LSPTool()