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