hanzo-mcp 0.9.0__py3-none-any.whl → 0.9.2__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.
- hanzo_mcp/__init__.py +1 -1
- hanzo_mcp/analytics/posthog_analytics.py +14 -1
- hanzo_mcp/cli.py +108 -4
- hanzo_mcp/server.py +11 -0
- hanzo_mcp/tools/__init__.py +3 -16
- hanzo_mcp/tools/agent/__init__.py +5 -0
- hanzo_mcp/tools/agent/agent.py +5 -0
- hanzo_mcp/tools/agent/agent_tool.py +3 -17
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +623 -0
- hanzo_mcp/tools/agent/clarification_tool.py +7 -1
- hanzo_mcp/tools/agent/claude_desktop_auth.py +16 -6
- hanzo_mcp/tools/agent/cli_agent_base.py +5 -0
- hanzo_mcp/tools/agent/cli_tools.py +26 -0
- hanzo_mcp/tools/agent/code_auth_tool.py +5 -0
- hanzo_mcp/tools/agent/critic_tool.py +7 -1
- hanzo_mcp/tools/agent/iching_tool.py +5 -0
- hanzo_mcp/tools/agent/network_tool.py +5 -0
- hanzo_mcp/tools/agent/review_tool.py +7 -1
- hanzo_mcp/tools/agent/swarm_alias.py +5 -0
- hanzo_mcp/tools/agent/swarm_tool.py +701 -0
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +554 -0
- hanzo_mcp/tools/agent/unified_cli_tools.py +5 -0
- hanzo_mcp/tools/common/auto_timeout.py +254 -0
- hanzo_mcp/tools/common/base.py +4 -0
- hanzo_mcp/tools/common/batch_tool.py +5 -0
- hanzo_mcp/tools/common/config_tool.py +5 -0
- hanzo_mcp/tools/common/critic_tool.py +5 -0
- hanzo_mcp/tools/common/paginated_base.py +4 -0
- hanzo_mcp/tools/common/permissions.py +38 -12
- hanzo_mcp/tools/common/personality.py +673 -980
- hanzo_mcp/tools/common/stats.py +5 -0
- hanzo_mcp/tools/common/thinking_tool.py +5 -0
- hanzo_mcp/tools/common/timeout_parser.py +103 -0
- hanzo_mcp/tools/common/tool_disable.py +5 -0
- hanzo_mcp/tools/common/tool_enable.py +5 -0
- hanzo_mcp/tools/common/tool_list.py +5 -0
- hanzo_mcp/tools/config/config_tool.py +5 -0
- hanzo_mcp/tools/config/mode_tool.py +5 -0
- hanzo_mcp/tools/database/graph.py +5 -0
- hanzo_mcp/tools/database/graph_add.py +5 -0
- hanzo_mcp/tools/database/graph_query.py +5 -0
- hanzo_mcp/tools/database/graph_remove.py +5 -0
- hanzo_mcp/tools/database/graph_search.py +5 -0
- hanzo_mcp/tools/database/graph_stats.py +5 -0
- hanzo_mcp/tools/database/sql.py +5 -0
- hanzo_mcp/tools/database/sql_query.py +2 -0
- hanzo_mcp/tools/database/sql_search.py +5 -0
- hanzo_mcp/tools/database/sql_stats.py +5 -0
- hanzo_mcp/tools/editor/neovim_command.py +5 -0
- hanzo_mcp/tools/editor/neovim_edit.py +7 -2
- hanzo_mcp/tools/editor/neovim_session.py +5 -0
- hanzo_mcp/tools/filesystem/__init__.py +23 -26
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -4
- hanzo_mcp/tools/filesystem/base.py +2 -18
- hanzo_mcp/tools/filesystem/batch_search.py +825 -0
- hanzo_mcp/tools/filesystem/content_replace.py +5 -3
- hanzo_mcp/tools/filesystem/diff.py +5 -0
- hanzo_mcp/tools/filesystem/directory_tree.py +34 -281
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +345 -0
- hanzo_mcp/tools/filesystem/edit.py +6 -5
- hanzo_mcp/tools/filesystem/find.py +177 -311
- hanzo_mcp/tools/filesystem/find_files.py +370 -0
- hanzo_mcp/tools/filesystem/git_search.py +5 -3
- hanzo_mcp/tools/filesystem/grep.py +454 -0
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -5
- hanzo_mcp/tools/filesystem/read.py +10 -9
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -4
- hanzo_mcp/tools/filesystem/search_tool.py +728 -0
- hanzo_mcp/tools/filesystem/symbols_tool.py +510 -0
- hanzo_mcp/tools/filesystem/tree.py +273 -0
- hanzo_mcp/tools/filesystem/watch.py +6 -1
- hanzo_mcp/tools/filesystem/write.py +13 -7
- hanzo_mcp/tools/jupyter/jupyter.py +30 -2
- hanzo_mcp/tools/jupyter/notebook_edit.py +298 -0
- hanzo_mcp/tools/jupyter/notebook_read.py +148 -0
- hanzo_mcp/tools/llm/consensus_tool.py +8 -6
- hanzo_mcp/tools/llm/llm_manage.py +5 -0
- hanzo_mcp/tools/llm/llm_tool.py +2 -0
- hanzo_mcp/tools/llm/llm_unified.py +5 -0
- hanzo_mcp/tools/llm/provider_tools.py +5 -0
- hanzo_mcp/tools/lsp/lsp_tool.py +475 -622
- hanzo_mcp/tools/mcp/mcp_add.py +7 -2
- hanzo_mcp/tools/mcp/mcp_remove.py +15 -2
- hanzo_mcp/tools/mcp/mcp_stats.py +5 -0
- hanzo_mcp/tools/mcp/mcp_tool.py +5 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +14 -0
- hanzo_mcp/tools/memory/memory_tools.py +17 -0
- hanzo_mcp/tools/search/find_tool.py +5 -3
- hanzo_mcp/tools/search/unified_search.py +3 -1
- hanzo_mcp/tools/shell/__init__.py +2 -14
- hanzo_mcp/tools/shell/base_process.py +4 -2
- hanzo_mcp/tools/shell/bash_tool.py +2 -0
- hanzo_mcp/tools/shell/command_executor.py +7 -7
- hanzo_mcp/tools/shell/logs.py +5 -0
- hanzo_mcp/tools/shell/npx.py +5 -0
- hanzo_mcp/tools/shell/npx_background.py +5 -0
- hanzo_mcp/tools/shell/npx_tool.py +5 -0
- hanzo_mcp/tools/shell/open.py +5 -0
- hanzo_mcp/tools/shell/pkill.py +5 -0
- hanzo_mcp/tools/shell/process_tool.py +5 -0
- hanzo_mcp/tools/shell/processes.py +5 -0
- hanzo_mcp/tools/shell/run_background.py +5 -0
- hanzo_mcp/tools/shell/run_command.py +2 -0
- hanzo_mcp/tools/shell/run_command_windows.py +5 -0
- hanzo_mcp/tools/shell/streaming_command.py +5 -0
- hanzo_mcp/tools/shell/uvx.py +5 -0
- hanzo_mcp/tools/shell/uvx_background.py +5 -0
- hanzo_mcp/tools/shell/uvx_tool.py +5 -0
- hanzo_mcp/tools/shell/zsh_tool.py +3 -0
- hanzo_mcp/tools/todo/todo.py +5 -0
- hanzo_mcp/tools/todo/todo_read.py +142 -0
- hanzo_mcp/tools/todo/todo_write.py +367 -0
- hanzo_mcp/tools/vector/__init__.py +42 -95
- hanzo_mcp/tools/vector/index_tool.py +5 -0
- hanzo_mcp/tools/vector/vector.py +5 -0
- hanzo_mcp/tools/vector/vector_index.py +5 -0
- hanzo_mcp/tools/vector/vector_search.py +5 -0
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/METADATA +1 -1
- hanzo_mcp-0.9.2.dist-info/RECORD +195 -0
- hanzo_mcp/tools/common/path_utils.py +0 -34
- hanzo_mcp/tools/compiler/__init__.py +0 -8
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +0 -681
- hanzo_mcp/tools/environment/__init__.py +0 -8
- hanzo_mcp/tools/environment/environment_detector.py +0 -594
- hanzo_mcp/tools/filesystem/search.py +0 -1160
- hanzo_mcp/tools/framework/__init__.py +0 -8
- hanzo_mcp/tools/framework/framework_modes.py +0 -714
- hanzo_mcp/tools/memory/conversation_memory.py +0 -636
- hanzo_mcp/tools/shell/run_tool.py +0 -56
- hanzo_mcp/tools/vector/node_tool.py +0 -538
- hanzo_mcp/tools/vector/unified_vector.py +0 -384
- hanzo_mcp-0.9.0.dist-info/RECORD +0 -191
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/lsp/lsp_tool.py
CHANGED
|
@@ -1,602 +1,397 @@
|
|
|
1
|
-
"""Enhanced Language Server Protocol (LSP) implementation with full protocol support.
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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",
|
|
120
|
-
"
|
|
121
|
-
"
|
|
33
|
+
"definition",
|
|
34
|
+
"references",
|
|
35
|
+
"rename",
|
|
36
|
+
"diagnostics",
|
|
37
|
+
"hover",
|
|
38
|
+
"completion",
|
|
122
39
|
],
|
|
123
40
|
},
|
|
124
|
-
"
|
|
125
|
-
"name": "
|
|
126
|
-
"install_cmd":
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
"
|
|
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",
|
|
136
|
-
"
|
|
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
|
-
"
|
|
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",
|
|
152
|
-
"
|
|
153
|
-
"
|
|
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",
|
|
169
|
-
"
|
|
170
|
-
"
|
|
87
|
+
"definition",
|
|
88
|
+
"references",
|
|
89
|
+
"rename",
|
|
90
|
+
"diagnostics",
|
|
91
|
+
"hover",
|
|
92
|
+
"completion",
|
|
93
|
+
"inlay_hints",
|
|
171
94
|
],
|
|
172
95
|
},
|
|
173
|
-
"
|
|
174
|
-
"name": "
|
|
175
|
-
"install_cmd":
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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",
|
|
189
|
-
"
|
|
190
|
-
"
|
|
104
|
+
"definition",
|
|
105
|
+
"references",
|
|
106
|
+
"rename",
|
|
107
|
+
"diagnostics",
|
|
108
|
+
"hover",
|
|
109
|
+
"completion",
|
|
191
110
|
],
|
|
192
111
|
},
|
|
193
|
-
"
|
|
194
|
-
"name": "
|
|
195
|
-
"install_cmd":
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
"
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
-
|
|
202
|
-
|
|
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",
|
|
205
|
-
"
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
"""
|
|
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 = """
|
|
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
|
-
|
|
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.
|
|
225
|
+
self.servers: Dict[str, LSPServer] = {}
|
|
479
226
|
self.logger = logging.getLogger(__name__)
|
|
480
|
-
|
|
481
|
-
def
|
|
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
|
|
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 =
|
|
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
|
|
504
|
-
"""
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
591
|
-
"
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
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
|
-
|
|
612
|
-
|
|
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(
|
|
433
|
+
"supported_languages": list(LSP_SERVERS.keys()),
|
|
618
434
|
}
|
|
619
435
|
)
|
|
620
|
-
|
|
621
|
-
#
|
|
622
|
-
|
|
623
|
-
|
|
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={
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
713
|
-
|
|
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
|
|
730
|
-
for
|
|
731
|
-
|
|
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
|
-
#
|
|
587
|
+
# Tool registration
|
|
735
588
|
def create_lsp_tool():
|
|
736
|
-
"""
|
|
737
|
-
return LSPTool()
|
|
589
|
+
"""Factory function to create LSP tool."""
|
|
590
|
+
return LSPTool()
|