tree-sitter-analyzer 0.7.0__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of 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 +160 -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 +81 -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 +297 -297
- tree_sitter_analyzer/core/__init__.py +15 -15
- tree_sitter_analyzer/core/analysis_engine.py +555 -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 +346 -333
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -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 +300 -300
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -362
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -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/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 +224 -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.0.dist-info}/METADATA +4 -1
- tree_sitter_analyzer-0.8.0.dist-info/RECORD +76 -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.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,333 +1,346 @@
|
|
|
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 asyncio
|
|
10
|
-
import json
|
|
11
|
-
import sys
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from mcp.server import Server
|
|
16
|
-
from mcp.server.models import InitializationOptions
|
|
17
|
-
from mcp.server.stdio import stdio_server
|
|
18
|
-
from mcp.types import Resource, TextContent, Tool
|
|
19
|
-
|
|
20
|
-
MCP_AVAILABLE = True
|
|
21
|
-
except ImportError:
|
|
22
|
-
MCP_AVAILABLE = False
|
|
23
|
-
|
|
24
|
-
# Fallback types for development without MCP
|
|
25
|
-
class
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
class
|
|
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
|
-
self.
|
|
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
|
-
"type": "
|
|
121
|
-
"description": "
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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 asyncio
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from mcp.server import Server
|
|
16
|
+
from mcp.server.models import InitializationOptions
|
|
17
|
+
from mcp.server.stdio import stdio_server
|
|
18
|
+
from mcp.types import Resource, TextContent, Tool
|
|
19
|
+
|
|
20
|
+
MCP_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
MCP_AVAILABLE = False
|
|
23
|
+
|
|
24
|
+
# Fallback types for development without MCP
|
|
25
|
+
class Server:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
class InitializationOptions:
|
|
29
|
+
def __init__(self, **kwargs):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
class Tool:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
class Resource:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
class TextContent:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def stdio_server():
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
from ..core.analysis_engine import get_analysis_engine
|
|
46
|
+
from ..utils import setup_logger
|
|
47
|
+
from . import MCP_INFO
|
|
48
|
+
from .resources import CodeFileResource, ProjectStatsResource
|
|
49
|
+
from .tools.base_tool import MCPTool
|
|
50
|
+
from .tools.read_partial_tool import ReadPartialTool
|
|
51
|
+
from .tools.table_format_tool import TableFormatTool
|
|
52
|
+
from .tools.universal_analyze_tool import UniversalAnalyzeTool
|
|
53
|
+
from .utils.error_handler import handle_mcp_errors
|
|
54
|
+
|
|
55
|
+
# Set up logging
|
|
56
|
+
logger = setup_logger(__name__)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TreeSitterAnalyzerMCPServer:
|
|
60
|
+
"""
|
|
61
|
+
MCP Server for Tree-sitter Analyzer
|
|
62
|
+
|
|
63
|
+
Provides code analysis capabilities through the Model Context Protocol,
|
|
64
|
+
integrating with existing analyzer components.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self) -> None:
|
|
68
|
+
"""Initialize the MCP server with analyzer components."""
|
|
69
|
+
self.server: Server | None = None
|
|
70
|
+
self.analysis_engine = get_analysis_engine()
|
|
71
|
+
# Use unified analysis engine instead of deprecated AdvancedAnalyzer
|
|
72
|
+
|
|
73
|
+
# Initialize MCP tools
|
|
74
|
+
self.read_partial_tool: MCPTool = ReadPartialTool()
|
|
75
|
+
self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool()
|
|
76
|
+
self.table_format_tool: MCPTool = TableFormatTool()
|
|
77
|
+
|
|
78
|
+
# Initialize MCP resources
|
|
79
|
+
self.code_file_resource = CodeFileResource()
|
|
80
|
+
self.project_stats_resource = ProjectStatsResource()
|
|
81
|
+
|
|
82
|
+
# Server metadata
|
|
83
|
+
self.name = MCP_INFO["name"]
|
|
84
|
+
self.version = MCP_INFO["version"]
|
|
85
|
+
|
|
86
|
+
logger.info(f"Initializing {self.name} v{self.version}")
|
|
87
|
+
|
|
88
|
+
@handle_mcp_errors("analyze_code_scale")
|
|
89
|
+
async def _analyze_code_scale(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
Analyze code scale and complexity metrics by delegating to the universal_analyze_tool.
|
|
92
|
+
"""
|
|
93
|
+
# Delegate the execution to the already initialized tool
|
|
94
|
+
return await self.universal_analyze_tool.execute(arguments)
|
|
95
|
+
|
|
96
|
+
def create_server(self) -> Server:
|
|
97
|
+
"""
|
|
98
|
+
Create and configure the MCP server.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Configured MCP Server instance
|
|
102
|
+
"""
|
|
103
|
+
if not MCP_AVAILABLE:
|
|
104
|
+
raise RuntimeError("MCP library not available. Please install mcp package.")
|
|
105
|
+
|
|
106
|
+
server: Server = Server(self.name)
|
|
107
|
+
|
|
108
|
+
# Register tools
|
|
109
|
+
@server.list_tools() # type: ignore
|
|
110
|
+
async def handle_list_tools() -> list[Tool]:
|
|
111
|
+
"""List available tools."""
|
|
112
|
+
tools = [
|
|
113
|
+
Tool(
|
|
114
|
+
name="analyze_code_scale",
|
|
115
|
+
description="Analyze code scale, complexity, and structure metrics",
|
|
116
|
+
inputSchema={
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"file_path": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Path to the code file to analyze",
|
|
122
|
+
},
|
|
123
|
+
"language": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "Programming language (optional, auto-detected if not specified)",
|
|
126
|
+
},
|
|
127
|
+
"include_complexity": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"description": "Include complexity metrics in the analysis",
|
|
130
|
+
"default": True,
|
|
131
|
+
},
|
|
132
|
+
"include_details": {
|
|
133
|
+
"type": "boolean",
|
|
134
|
+
"description": "Include detailed element information",
|
|
135
|
+
"default": False,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
"required": ["file_path"],
|
|
139
|
+
"additionalProperties": False,
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# Add tools from tool classes - FIXED VERSION
|
|
145
|
+
for tool_instance in [
|
|
146
|
+
self.read_partial_tool,
|
|
147
|
+
self.table_format_tool,
|
|
148
|
+
self.universal_analyze_tool,
|
|
149
|
+
]:
|
|
150
|
+
tool_def = tool_instance.get_tool_definition()
|
|
151
|
+
if isinstance(tool_def, dict):
|
|
152
|
+
# Convert dict to Tool object
|
|
153
|
+
tools.append(Tool(**tool_def))
|
|
154
|
+
else:
|
|
155
|
+
# Already a Tool object
|
|
156
|
+
tools.append(tool_def)
|
|
157
|
+
|
|
158
|
+
return tools
|
|
159
|
+
|
|
160
|
+
@server.call_tool() # type: ignore
|
|
161
|
+
async def handle_call_tool(
|
|
162
|
+
name: str, arguments: dict[str, Any]
|
|
163
|
+
) -> list[TextContent]:
|
|
164
|
+
"""Handle tool calls."""
|
|
165
|
+
try:
|
|
166
|
+
if name == "analyze_code_scale":
|
|
167
|
+
result = await self._analyze_code_scale(arguments)
|
|
168
|
+
return [
|
|
169
|
+
TextContent(
|
|
170
|
+
type="text",
|
|
171
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
172
|
+
)
|
|
173
|
+
]
|
|
174
|
+
elif name == "read_code_partial":
|
|
175
|
+
result = await self.read_partial_tool.execute(arguments)
|
|
176
|
+
return [
|
|
177
|
+
TextContent(
|
|
178
|
+
type="text",
|
|
179
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
180
|
+
)
|
|
181
|
+
]
|
|
182
|
+
elif name == "format_table":
|
|
183
|
+
result = await self.table_format_tool.execute(arguments)
|
|
184
|
+
return [
|
|
185
|
+
TextContent(
|
|
186
|
+
type="text",
|
|
187
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
188
|
+
)
|
|
189
|
+
]
|
|
190
|
+
elif name == "analyze_code_universal":
|
|
191
|
+
result = await self.universal_analyze_tool.execute(arguments)
|
|
192
|
+
return [
|
|
193
|
+
TextContent(
|
|
194
|
+
type="text",
|
|
195
|
+
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
196
|
+
)
|
|
197
|
+
]
|
|
198
|
+
else:
|
|
199
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
try:
|
|
203
|
+
logger.error(f"Tool call error for {name}: {e}")
|
|
204
|
+
except (ValueError, OSError):
|
|
205
|
+
pass # Silently ignore logging errors during shutdown
|
|
206
|
+
return [
|
|
207
|
+
TextContent(
|
|
208
|
+
type="text",
|
|
209
|
+
text=json.dumps(
|
|
210
|
+
{"error": str(e), "tool": name, "arguments": arguments},
|
|
211
|
+
indent=2,
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
# Register resources
|
|
217
|
+
@server.list_resources() # type: ignore
|
|
218
|
+
async def handle_list_resources() -> list[Resource]:
|
|
219
|
+
"""List available resources."""
|
|
220
|
+
return [
|
|
221
|
+
Resource(
|
|
222
|
+
uri=self.code_file_resource.get_resource_info()["uri_template"],
|
|
223
|
+
name=self.code_file_resource.get_resource_info()["name"],
|
|
224
|
+
description=self.code_file_resource.get_resource_info()[
|
|
225
|
+
"description"
|
|
226
|
+
],
|
|
227
|
+
mimeType=self.code_file_resource.get_resource_info()["mime_type"],
|
|
228
|
+
),
|
|
229
|
+
Resource(
|
|
230
|
+
uri=self.project_stats_resource.get_resource_info()["uri_template"],
|
|
231
|
+
name=self.project_stats_resource.get_resource_info()["name"],
|
|
232
|
+
description=self.project_stats_resource.get_resource_info()[
|
|
233
|
+
"description"
|
|
234
|
+
],
|
|
235
|
+
mimeType=self.project_stats_resource.get_resource_info()[
|
|
236
|
+
"mime_type"
|
|
237
|
+
],
|
|
238
|
+
),
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
@server.read_resource() # type: ignore
|
|
242
|
+
async def handle_read_resource(uri: str) -> str:
|
|
243
|
+
"""Read resource content."""
|
|
244
|
+
try:
|
|
245
|
+
# Check which resource matches the URI
|
|
246
|
+
if self.code_file_resource.matches_uri(uri):
|
|
247
|
+
return await self.code_file_resource.read_resource(uri)
|
|
248
|
+
elif self.project_stats_resource.matches_uri(uri):
|
|
249
|
+
return await self.project_stats_resource.read_resource(uri)
|
|
250
|
+
else:
|
|
251
|
+
raise ValueError(f"Resource not found: {uri}")
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
try:
|
|
255
|
+
logger.error(f"Resource read error for {uri}: {e}")
|
|
256
|
+
except (ValueError, OSError):
|
|
257
|
+
pass # Silently ignore logging errors during shutdown
|
|
258
|
+
raise
|
|
259
|
+
|
|
260
|
+
self.server = server
|
|
261
|
+
try:
|
|
262
|
+
logger.info("MCP server created successfully")
|
|
263
|
+
except (ValueError, OSError):
|
|
264
|
+
pass # Silently ignore logging errors during shutdown
|
|
265
|
+
return server
|
|
266
|
+
|
|
267
|
+
def set_project_path(self, project_path: str) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Set the project path for statistics resource
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
project_path: Path to the project directory
|
|
273
|
+
"""
|
|
274
|
+
self.project_stats_resource.set_project_path(project_path)
|
|
275
|
+
try:
|
|
276
|
+
logger.info(f"Set project path to: {project_path}")
|
|
277
|
+
except (ValueError, OSError):
|
|
278
|
+
pass # Silently ignore logging errors during shutdown
|
|
279
|
+
|
|
280
|
+
async def run(self) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Run the MCP server.
|
|
283
|
+
|
|
284
|
+
This method starts the server and handles stdio communication.
|
|
285
|
+
"""
|
|
286
|
+
if not MCP_AVAILABLE:
|
|
287
|
+
raise RuntimeError("MCP library not available. Please install mcp package.")
|
|
288
|
+
|
|
289
|
+
server = self.create_server()
|
|
290
|
+
|
|
291
|
+
# Initialize server options
|
|
292
|
+
options = InitializationOptions(
|
|
293
|
+
server_name=self.name,
|
|
294
|
+
server_version=self.version,
|
|
295
|
+
capabilities=MCP_INFO["capabilities"],
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
logger.info(f"Starting MCP server: {self.name} v{self.version}")
|
|
300
|
+
except (ValueError, OSError):
|
|
301
|
+
pass # Silently ignore logging errors during shutdown
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
305
|
+
await server.run(read_stream, write_stream, options)
|
|
306
|
+
except Exception as e:
|
|
307
|
+
# Use safe logging to avoid I/O errors during shutdown
|
|
308
|
+
try:
|
|
309
|
+
logger.error(f"Server error: {e}")
|
|
310
|
+
except (ValueError, OSError):
|
|
311
|
+
pass # Silently ignore logging errors during shutdown
|
|
312
|
+
raise
|
|
313
|
+
finally:
|
|
314
|
+
# Safe cleanup
|
|
315
|
+
try:
|
|
316
|
+
logger.info("MCP server shutting down")
|
|
317
|
+
except (ValueError, OSError):
|
|
318
|
+
pass # Silently ignore logging errors during shutdown
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
async def main() -> None:
|
|
322
|
+
"""Main entry point for the MCP server."""
|
|
323
|
+
try:
|
|
324
|
+
server = TreeSitterAnalyzerMCPServer()
|
|
325
|
+
await server.run()
|
|
326
|
+
except KeyboardInterrupt:
|
|
327
|
+
try:
|
|
328
|
+
logger.info("Server stopped by user")
|
|
329
|
+
except (ValueError, OSError):
|
|
330
|
+
pass # Silently ignore logging errors during shutdown
|
|
331
|
+
except Exception as e:
|
|
332
|
+
try:
|
|
333
|
+
logger.error(f"Server failed: {e}")
|
|
334
|
+
except (ValueError, OSError):
|
|
335
|
+
pass # Silently ignore logging errors during shutdown
|
|
336
|
+
sys.exit(1)
|
|
337
|
+
finally:
|
|
338
|
+
# Ensure clean shutdown
|
|
339
|
+
try:
|
|
340
|
+
logger.info("MCP server shutdown complete")
|
|
341
|
+
except (ValueError, OSError):
|
|
342
|
+
pass # Silently ignore logging errors during shutdown
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
asyncio.run(main())
|