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