tree-sitter-analyzer 0.7.0__py3-none-any.whl → 0.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +132 -132
- tree_sitter_analyzer/__main__.py +11 -11
- tree_sitter_analyzer/api.py +533 -533
- tree_sitter_analyzer/cli/__init__.py +39 -39
- tree_sitter_analyzer/cli/__main__.py +12 -12
- tree_sitter_analyzer/cli/commands/__init__.py +26 -26
- tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
- tree_sitter_analyzer/cli/commands/base_command.py +178 -160
- tree_sitter_analyzer/cli/commands/default_command.py +18 -18
- tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -141
- tree_sitter_analyzer/cli/commands/query_command.py +88 -81
- tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
- tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
- tree_sitter_analyzer/cli/commands/table_command.py +235 -235
- tree_sitter_analyzer/cli/info_commands.py +121 -121
- tree_sitter_analyzer/cli_main.py +303 -297
- tree_sitter_analyzer/core/__init__.py +15 -15
- tree_sitter_analyzer/core/analysis_engine.py +580 -555
- tree_sitter_analyzer/core/cache_service.py +320 -320
- tree_sitter_analyzer/core/engine.py +566 -566
- tree_sitter_analyzer/core/parser.py +293 -293
- tree_sitter_analyzer/encoding_utils.py +459 -459
- tree_sitter_analyzer/exceptions.py +406 -337
- tree_sitter_analyzer/file_handler.py +210 -210
- tree_sitter_analyzer/formatters/__init__.py +1 -1
- tree_sitter_analyzer/formatters/base_formatter.py +167 -167
- tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
- tree_sitter_analyzer/interfaces/__init__.py +9 -9
- tree_sitter_analyzer/interfaces/cli.py +528 -528
- tree_sitter_analyzer/interfaces/cli_adapter.py +343 -343
- tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
- tree_sitter_analyzer/interfaces/mcp_server.py +425 -405
- tree_sitter_analyzer/languages/__init__.py +10 -10
- tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
- tree_sitter_analyzer/languages/python_plugin.py +755 -755
- tree_sitter_analyzer/mcp/__init__.py +31 -31
- tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
- tree_sitter_analyzer/mcp/server.py +408 -333
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +673 -654
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
- tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +308 -300
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +379 -362
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +559 -543
- tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
- tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
- tree_sitter_analyzer/output_manager.py +253 -253
- tree_sitter_analyzer/plugins/__init__.py +280 -280
- tree_sitter_analyzer/plugins/base.py +529 -529
- tree_sitter_analyzer/plugins/manager.py +379 -379
- tree_sitter_analyzer/project_detector.py +317 -0
- tree_sitter_analyzer/queries/__init__.py +26 -26
- tree_sitter_analyzer/queries/java.py +391 -391
- tree_sitter_analyzer/queries/javascript.py +148 -148
- tree_sitter_analyzer/queries/python.py +285 -285
- tree_sitter_analyzer/queries/typescript.py +229 -229
- tree_sitter_analyzer/query_loader.py +257 -257
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +237 -0
- tree_sitter_analyzer/security/regex_checker.py +292 -0
- tree_sitter_analyzer/security/validator.py +241 -0
- tree_sitter_analyzer/table_formatter.py +652 -589
- tree_sitter_analyzer/utils.py +277 -277
- {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.1.dist-info}/METADATA +27 -1
- tree_sitter_analyzer-0.8.1.dist-info/RECORD +77 -0
- tree_sitter_analyzer-0.7.0.dist-info/RECORD +0 -72
- {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.1.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,333 +1,408 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
MCP Server implementation for Tree-sitter Analyzer (Fixed Version)
|
|
4
|
-
|
|
5
|
-
This module provides the main MCP server that exposes tree-sitter analyzer
|
|
6
|
-
functionality through the Model Context Protocol.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from mcp.server
|
|
18
|
-
from mcp.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
MCP_AVAILABLE =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
try:
|
|
320
|
-
logger.
|
|
321
|
-
except (ValueError, OSError):
|
|
322
|
-
pass # Silently ignore logging errors during shutdown
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server implementation for Tree-sitter Analyzer (Fixed Version)
|
|
4
|
+
|
|
5
|
+
This module provides the main MCP server that exposes tree-sitter analyzer
|
|
6
|
+
functionality through the Model Context Protocol.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from mcp.server import Server
|
|
18
|
+
from mcp.server.models import InitializationOptions
|
|
19
|
+
from mcp.server.stdio import stdio_server
|
|
20
|
+
from mcp.types import Resource, TextContent, Tool
|
|
21
|
+
|
|
22
|
+
MCP_AVAILABLE = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
MCP_AVAILABLE = False
|
|
25
|
+
|
|
26
|
+
# Fallback types for development without MCP
|
|
27
|
+
class Server:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class InitializationOptions:
|
|
31
|
+
def __init__(self, **kwargs):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
class Tool:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
class Resource:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
class TextContent:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def stdio_server():
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
from ..core.analysis_engine import get_analysis_engine
|
|
48
|
+
from ..project_detector import detect_project_root
|
|
49
|
+
from ..security import SecurityValidator
|
|
50
|
+
from ..utils import setup_logger
|
|
51
|
+
from . import MCP_INFO
|
|
52
|
+
from .resources import CodeFileResource, ProjectStatsResource
|
|
53
|
+
from .tools.base_tool import MCPTool
|
|
54
|
+
from .tools.read_partial_tool import ReadPartialTool
|
|
55
|
+
from .tools.table_format_tool import TableFormatTool
|
|
56
|
+
from .tools.universal_analyze_tool import UniversalAnalyzeTool
|
|
57
|
+
from .utils.error_handler import handle_mcp_errors
|
|
58
|
+
|
|
59
|
+
# Set up logging
|
|
60
|
+
logger = setup_logger(__name__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TreeSitterAnalyzerMCPServer:
|
|
64
|
+
"""
|
|
65
|
+
MCP Server for Tree-sitter Analyzer
|
|
66
|
+
|
|
67
|
+
Provides code analysis capabilities through the Model Context Protocol,
|
|
68
|
+
integrating with existing analyzer components.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, project_root: str = None) -> None:
|
|
72
|
+
"""Initialize the MCP server with analyzer components."""
|
|
73
|
+
self.server: Server | None = None
|
|
74
|
+
self.analysis_engine = get_analysis_engine(project_root)
|
|
75
|
+
self.security_validator = SecurityValidator(project_root)
|
|
76
|
+
# Use unified analysis engine instead of deprecated AdvancedAnalyzer
|
|
77
|
+
|
|
78
|
+
# Initialize MCP tools with security validation
|
|
79
|
+
self.read_partial_tool: MCPTool = ReadPartialTool(project_root)
|
|
80
|
+
self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool(project_root)
|
|
81
|
+
self.table_format_tool: MCPTool = TableFormatTool(project_root)
|
|
82
|
+
|
|
83
|
+
# Initialize MCP resources
|
|
84
|
+
self.code_file_resource = CodeFileResource()
|
|
85
|
+
self.project_stats_resource = ProjectStatsResource()
|
|
86
|
+
|
|
87
|
+
# Server metadata
|
|
88
|
+
self.name = MCP_INFO["name"]
|
|
89
|
+
self.version = MCP_INFO["version"]
|
|
90
|
+
|
|
91
|
+
logger.info(f"Initializing {self.name} v{self.version}")
|
|
92
|
+
|
|
93
|
+
@handle_mcp_errors("analyze_code_scale")
|
|
94
|
+
async def _analyze_code_scale(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Analyze code scale and complexity metrics by delegating to the universal_analyze_tool.
|
|
97
|
+
"""
|
|
98
|
+
# Delegate the execution to the already initialized tool
|
|
99
|
+
return await self.universal_analyze_tool.execute(arguments)
|
|
100
|
+
|
|
101
|
+
def create_server(self) -> Server:
|
|
102
|
+
"""
|
|
103
|
+
Create and configure the MCP server.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Configured MCP Server instance
|
|
107
|
+
"""
|
|
108
|
+
if not MCP_AVAILABLE:
|
|
109
|
+
raise RuntimeError("MCP library not available. Please install mcp package.")
|
|
110
|
+
|
|
111
|
+
server: Server = Server(self.name)
|
|
112
|
+
|
|
113
|
+
# Register tools
|
|
114
|
+
@server.list_tools() # type: ignore
|
|
115
|
+
async def handle_list_tools() -> list[Tool]:
|
|
116
|
+
"""List available tools."""
|
|
117
|
+
tools = [
|
|
118
|
+
Tool(
|
|
119
|
+
name="analyze_code_scale",
|
|
120
|
+
description="Analyze code scale, complexity, and structure metrics",
|
|
121
|
+
inputSchema={
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"file_path": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Path to the code file to analyze",
|
|
127
|
+
},
|
|
128
|
+
"language": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"description": "Programming language (optional, auto-detected if not specified)",
|
|
131
|
+
},
|
|
132
|
+
"include_complexity": {
|
|
133
|
+
"type": "boolean",
|
|
134
|
+
"description": "Include complexity metrics in the analysis",
|
|
135
|
+
"default": True,
|
|
136
|
+
},
|
|
137
|
+
"include_details": {
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"description": "Include detailed element information",
|
|
140
|
+
"default": False,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
"required": ["file_path"],
|
|
144
|
+
"additionalProperties": False,
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
# Add tools from tool classes - FIXED VERSION
|
|
150
|
+
for tool_instance in [
|
|
151
|
+
self.read_partial_tool,
|
|
152
|
+
self.table_format_tool,
|
|
153
|
+
self.universal_analyze_tool,
|
|
154
|
+
]:
|
|
155
|
+
tool_def = tool_instance.get_tool_definition()
|
|
156
|
+
if isinstance(tool_def, dict):
|
|
157
|
+
# Convert dict to Tool object
|
|
158
|
+
tools.append(Tool(**tool_def))
|
|
159
|
+
else:
|
|
160
|
+
# Already a Tool object
|
|
161
|
+
tools.append(tool_def)
|
|
162
|
+
|
|
163
|
+
return tools
|
|
164
|
+
|
|
165
|
+
@server.call_tool() # type: ignore
|
|
166
|
+
async def handle_call_tool(
|
|
167
|
+
name: str, arguments: dict[str, Any]
|
|
168
|
+
) -> list[TextContent]:
|
|
169
|
+
"""Handle tool calls with security validation."""
|
|
170
|
+
try:
|
|
171
|
+
# Security validation for tool name
|
|
172
|
+
sanitized_name = self.security_validator.sanitize_input(name, max_length=100)
|
|
173
|
+
|
|
174
|
+
# Log tool call for audit
|
|
175
|
+
logger.info(f"MCP tool call: {sanitized_name} with args: {list(arguments.keys())}")
|
|
176
|
+
|
|
177
|
+
# Validate arguments contain no malicious content
|
|
178
|
+
for key, value in arguments.items():
|
|
179
|
+
if isinstance(value, str):
|
|
180
|
+
# Check for potential injection attempts
|
|
181
|
+
if len(value) > 10000: # Prevent extremely large inputs
|
|
182
|
+
raise ValueError(f"Input too large for parameter {key}")
|
|
183
|
+
|
|
184
|
+
# Basic sanitization for string inputs
|
|
185
|
+
sanitized_value = self.security_validator.sanitize_input(value, max_length=10000)
|
|
186
|
+
arguments[key] = sanitized_value
|
|
187
|
+
if sanitized_name == "analyze_code_scale":
|
|
188
|
+
result = await self._analyze_code_scale(arguments)
|
|
189
|
+
return [
|
|
190
|
+
TextContent(
|
|
191
|
+
type="text",
|
|
192
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
193
|
+
)
|
|
194
|
+
]
|
|
195
|
+
elif sanitized_name == "read_code_partial":
|
|
196
|
+
result = await self.read_partial_tool.execute(arguments)
|
|
197
|
+
return [
|
|
198
|
+
TextContent(
|
|
199
|
+
type="text",
|
|
200
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
elif sanitized_name == "format_table":
|
|
204
|
+
result = await self.table_format_tool.execute(arguments)
|
|
205
|
+
return [
|
|
206
|
+
TextContent(
|
|
207
|
+
type="text",
|
|
208
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
209
|
+
)
|
|
210
|
+
]
|
|
211
|
+
elif sanitized_name == "analyze_code_universal":
|
|
212
|
+
result = await self.universal_analyze_tool.execute(arguments)
|
|
213
|
+
return [
|
|
214
|
+
TextContent(
|
|
215
|
+
type="text",
|
|
216
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
217
|
+
)
|
|
218
|
+
]
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
try:
|
|
224
|
+
logger.error(f"Tool call error for {name}: {e}")
|
|
225
|
+
except (ValueError, OSError):
|
|
226
|
+
pass # Silently ignore logging errors during shutdown
|
|
227
|
+
return [
|
|
228
|
+
TextContent(
|
|
229
|
+
type="text",
|
|
230
|
+
text=json.dumps(
|
|
231
|
+
{"error": str(e), "tool": name, "arguments": arguments},
|
|
232
|
+
indent=2,
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
# Register resources
|
|
238
|
+
@server.list_resources() # type: ignore
|
|
239
|
+
async def handle_list_resources() -> list[Resource]:
|
|
240
|
+
"""List available resources."""
|
|
241
|
+
return [
|
|
242
|
+
Resource(
|
|
243
|
+
uri=self.code_file_resource.get_resource_info()["uri_template"],
|
|
244
|
+
name=self.code_file_resource.get_resource_info()["name"],
|
|
245
|
+
description=self.code_file_resource.get_resource_info()[
|
|
246
|
+
"description"
|
|
247
|
+
],
|
|
248
|
+
mimeType=self.code_file_resource.get_resource_info()["mime_type"],
|
|
249
|
+
),
|
|
250
|
+
Resource(
|
|
251
|
+
uri=self.project_stats_resource.get_resource_info()["uri_template"],
|
|
252
|
+
name=self.project_stats_resource.get_resource_info()["name"],
|
|
253
|
+
description=self.project_stats_resource.get_resource_info()[
|
|
254
|
+
"description"
|
|
255
|
+
],
|
|
256
|
+
mimeType=self.project_stats_resource.get_resource_info()[
|
|
257
|
+
"mime_type"
|
|
258
|
+
],
|
|
259
|
+
),
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
@server.read_resource() # type: ignore
|
|
263
|
+
async def handle_read_resource(uri: str) -> str:
|
|
264
|
+
"""Read resource content."""
|
|
265
|
+
try:
|
|
266
|
+
# Check which resource matches the URI
|
|
267
|
+
if self.code_file_resource.matches_uri(uri):
|
|
268
|
+
return await self.code_file_resource.read_resource(uri)
|
|
269
|
+
elif self.project_stats_resource.matches_uri(uri):
|
|
270
|
+
return await self.project_stats_resource.read_resource(uri)
|
|
271
|
+
else:
|
|
272
|
+
raise ValueError(f"Resource not found: {uri}")
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
try:
|
|
276
|
+
logger.error(f"Resource read error for {uri}: {e}")
|
|
277
|
+
except (ValueError, OSError):
|
|
278
|
+
pass # Silently ignore logging errors during shutdown
|
|
279
|
+
raise
|
|
280
|
+
|
|
281
|
+
self.server = server
|
|
282
|
+
try:
|
|
283
|
+
logger.info("MCP server created successfully")
|
|
284
|
+
except (ValueError, OSError):
|
|
285
|
+
pass # Silently ignore logging errors during shutdown
|
|
286
|
+
return server
|
|
287
|
+
|
|
288
|
+
def set_project_path(self, project_path: str) -> None:
|
|
289
|
+
"""
|
|
290
|
+
Set the project path for statistics resource
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
project_path: Path to the project directory
|
|
294
|
+
"""
|
|
295
|
+
self.project_stats_resource.set_project_path(project_path)
|
|
296
|
+
try:
|
|
297
|
+
logger.info(f"Set project path to: {project_path}")
|
|
298
|
+
except (ValueError, OSError):
|
|
299
|
+
pass # Silently ignore logging errors during shutdown
|
|
300
|
+
|
|
301
|
+
async def run(self) -> None:
|
|
302
|
+
"""
|
|
303
|
+
Run the MCP server.
|
|
304
|
+
|
|
305
|
+
This method starts the server and handles stdio communication.
|
|
306
|
+
"""
|
|
307
|
+
if not MCP_AVAILABLE:
|
|
308
|
+
raise RuntimeError("MCP library not available. Please install mcp package.")
|
|
309
|
+
|
|
310
|
+
server = self.create_server()
|
|
311
|
+
|
|
312
|
+
# Initialize server options
|
|
313
|
+
options = InitializationOptions(
|
|
314
|
+
server_name=self.name,
|
|
315
|
+
server_version=self.version,
|
|
316
|
+
capabilities=MCP_INFO["capabilities"],
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
logger.info(f"Starting MCP server: {self.name} v{self.version}")
|
|
321
|
+
except (ValueError, OSError):
|
|
322
|
+
pass # Silently ignore logging errors during shutdown
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
326
|
+
await server.run(read_stream, write_stream, options)
|
|
327
|
+
except Exception as e:
|
|
328
|
+
# Use safe logging to avoid I/O errors during shutdown
|
|
329
|
+
try:
|
|
330
|
+
logger.error(f"Server error: {e}")
|
|
331
|
+
except (ValueError, OSError):
|
|
332
|
+
pass # Silently ignore logging errors during shutdown
|
|
333
|
+
raise
|
|
334
|
+
finally:
|
|
335
|
+
# Safe cleanup
|
|
336
|
+
try:
|
|
337
|
+
logger.info("MCP server shutting down")
|
|
338
|
+
except (ValueError, OSError):
|
|
339
|
+
pass # Silently ignore logging errors during shutdown
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def parse_mcp_args(args=None) -> argparse.Namespace:
|
|
343
|
+
"""Parse command line arguments for MCP server."""
|
|
344
|
+
parser = argparse.ArgumentParser(
|
|
345
|
+
description="Tree-sitter Analyzer MCP Server",
|
|
346
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
347
|
+
epilog="""
|
|
348
|
+
Environment Variables:
|
|
349
|
+
TREE_SITTER_PROJECT_ROOT Project root directory (alternative to --project-root)
|
|
350
|
+
|
|
351
|
+
Examples:
|
|
352
|
+
python -m tree_sitter_analyzer.mcp.server
|
|
353
|
+
python -m tree_sitter_analyzer.mcp.server --project-root /path/to/project
|
|
354
|
+
"""
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
parser.add_argument(
|
|
358
|
+
"--project-root",
|
|
359
|
+
help="Project root directory for security validation (auto-detected if not specified)"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return parser.parse_args(args)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
async def main() -> None:
|
|
366
|
+
"""Main entry point for the MCP server."""
|
|
367
|
+
try:
|
|
368
|
+
# Parse command line arguments (empty list for testing)
|
|
369
|
+
args = parse_mcp_args([])
|
|
370
|
+
|
|
371
|
+
# Determine project root with priority handling
|
|
372
|
+
project_root = None
|
|
373
|
+
|
|
374
|
+
# Priority 1: Command line argument
|
|
375
|
+
if args.project_root:
|
|
376
|
+
project_root = args.project_root
|
|
377
|
+
# Priority 2: Environment variable
|
|
378
|
+
elif os.getenv('TREE_SITTER_PROJECT_ROOT'):
|
|
379
|
+
project_root = os.getenv('TREE_SITTER_PROJECT_ROOT')
|
|
380
|
+
# Priority 3: Auto-detection from current directory
|
|
381
|
+
else:
|
|
382
|
+
project_root = detect_project_root()
|
|
383
|
+
|
|
384
|
+
logger.info(f"MCP server starting with project root: {project_root}")
|
|
385
|
+
|
|
386
|
+
server = TreeSitterAnalyzerMCPServer(project_root)
|
|
387
|
+
await server.run()
|
|
388
|
+
except KeyboardInterrupt:
|
|
389
|
+
try:
|
|
390
|
+
logger.info("Server stopped by user")
|
|
391
|
+
except (ValueError, OSError):
|
|
392
|
+
pass # Silently ignore logging errors during shutdown
|
|
393
|
+
except Exception as e:
|
|
394
|
+
try:
|
|
395
|
+
logger.error(f"Server failed: {e}")
|
|
396
|
+
except (ValueError, OSError):
|
|
397
|
+
pass # Silently ignore logging errors during shutdown
|
|
398
|
+
sys.exit(1)
|
|
399
|
+
finally:
|
|
400
|
+
# Ensure clean shutdown
|
|
401
|
+
try:
|
|
402
|
+
logger.info("MCP server shutdown complete")
|
|
403
|
+
except (ValueError, OSError):
|
|
404
|
+
pass # Silently ignore logging errors during shutdown
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
if __name__ == "__main__":
|
|
408
|
+
asyncio.run(main())
|