tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.3.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.

Files changed (78) hide show
  1. tree_sitter_analyzer/__init__.py +133 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +531 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +232 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +277 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +591 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +557 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +494 -502
  23. tree_sitter_analyzer/encoding_utils.py +458 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +217 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +287 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +255 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +322 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +180 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +218 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1129 -1113
  41. tree_sitter_analyzer/languages/python_plugin.py +737 -712
  42. tree_sitter_analyzer/mcp/__init__.py +31 -32
  43. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +212 -213
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +560 -550
  46. tree_sitter_analyzer/mcp/server.py +333 -345
  47. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +621 -557
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +242 -245
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  51. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  52. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  53. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  54. tree_sitter_analyzer/mcp/utils/__init__.py +105 -106
  55. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  56. tree_sitter_analyzer/models.py +470 -481
  57. tree_sitter_analyzer/output_manager.py +261 -264
  58. tree_sitter_analyzer/plugins/__init__.py +333 -334
  59. tree_sitter_analyzer/plugins/base.py +477 -446
  60. tree_sitter_analyzer/plugins/java_plugin.py +608 -625
  61. tree_sitter_analyzer/plugins/javascript_plugin.py +446 -439
  62. tree_sitter_analyzer/plugins/manager.py +362 -355
  63. tree_sitter_analyzer/plugins/plugin_loader.py +85 -83
  64. tree_sitter_analyzer/plugins/python_plugin.py +606 -598
  65. tree_sitter_analyzer/plugins/registry.py +374 -366
  66. tree_sitter_analyzer/queries/__init__.py +26 -27
  67. tree_sitter_analyzer/queries/java.py +391 -394
  68. tree_sitter_analyzer/queries/javascript.py +148 -149
  69. tree_sitter_analyzer/queries/python.py +285 -286
  70. tree_sitter_analyzer/queries/typescript.py +229 -230
  71. tree_sitter_analyzer/query_loader.py +254 -260
  72. tree_sitter_analyzer/table_formatter.py +468 -448
  73. tree_sitter_analyzer/utils.py +277 -277
  74. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/METADATA +21 -6
  75. tree_sitter_analyzer-0.3.0.dist-info/RECORD +77 -0
  76. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  77. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/WHEEL +0 -0
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,345 +1,333 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- MCP Server implementation for Tree-sitter Analyzer (Fixed Version)
5
-
6
- This module provides the main MCP server that exposes tree-sitter analyzer
7
- functionality through the Model Context Protocol.
8
- """
9
-
10
- import asyncio
11
- import json
12
- import logging
13
- import sys
14
- from pathlib import Path
15
- from typing import Any, Dict, List, Optional, Sequence, Union
16
-
17
- try:
18
- from mcp.server import Server
19
- from mcp.server.models import InitializationOptions
20
- from mcp.server.stdio import stdio_server
21
- from mcp.types import (
22
- EmbeddedResource,
23
- ImageContent,
24
- Resource,
25
- TextContent,
26
- Tool,
27
- )
28
-
29
- MCP_AVAILABLE = True
30
- except ImportError:
31
- MCP_AVAILABLE = False
32
-
33
- # Fallback types for development without MCP
34
- class FallbackServer:
35
- pass
36
-
37
- class FallbackInitializationOptions:
38
- pass
39
-
40
-
41
- from tree_sitter_analyzer.core.analysis_engine import get_analysis_engine
42
- from ..core.analysis_engine import get_analysis_engine, AnalysisRequest
43
- from ..language_detector import detect_language_from_file
44
- from ..utils import setup_logger
45
- from . import MCP_INFO
46
- from .resources import CodeFileResource, ProjectStatsResource
47
- from .tools.base_tool import MCPTool
48
- from .tools.read_partial_tool import ReadPartialTool
49
- from .tools.table_format_tool import TableFormatTool
50
- from .tools.universal_analyze_tool import UniversalAnalyzeTool
51
- from .utils.error_handler import handle_mcp_errors
52
- from .utils import get_performance_monitor
53
-
54
- # Set up logging
55
- logger = setup_logger(__name__)
56
-
57
-
58
- class TreeSitterAnalyzerMCPServer:
59
- """
60
- MCP Server for Tree-sitter Analyzer
61
-
62
- Provides code analysis capabilities through the Model Context Protocol,
63
- integrating with existing analyzer components.
64
- """
65
-
66
- def __init__(self) -> None:
67
- """Initialize the MCP server with analyzer components."""
68
- self.server: Optional[Server] = None
69
- self.analysis_engine = get_analysis_engine()
70
- # Use unified analysis engine instead of deprecated AdvancedAnalyzer
71
-
72
- # Initialize MCP tools
73
- self.read_partial_tool: MCPTool = ReadPartialTool()
74
- self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool()
75
- self.table_format_tool: MCPTool = TableFormatTool()
76
-
77
- # Initialize MCP resources
78
- self.code_file_resource = CodeFileResource()
79
- self.project_stats_resource = ProjectStatsResource()
80
-
81
- # Server metadata
82
- self.name = MCP_INFO["name"]
83
- self.version = MCP_INFO["version"]
84
-
85
- logger.info(f"Initializing {self.name} v{self.version}")
86
-
87
- @handle_mcp_errors("analyze_code_scale")
88
- async def _analyze_code_scale(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
89
- """
90
- Analyze code scale and complexity metrics by delegating to the universal_analyze_tool.
91
- """
92
- # Delegate the execution to the already initialized tool
93
- return await self.universal_analyze_tool.execute(arguments)
94
-
95
- def create_server(self) -> Server:
96
- """
97
- Create and configure the MCP server.
98
-
99
- Returns:
100
- Configured MCP Server instance
101
- """
102
- if not MCP_AVAILABLE:
103
- raise RuntimeError("MCP library not available. Please install mcp package.")
104
-
105
- server: Server = Server(self.name)
106
-
107
- # Register tools
108
- @server.list_tools()
109
- async def handle_list_tools() -> List[Tool]:
110
- """List available tools."""
111
- tools = [
112
- Tool(
113
- name="analyze_code_scale",
114
- description="Analyze code scale, complexity, and structure metrics",
115
- inputSchema={
116
- "type": "object",
117
- "properties": {
118
- "file_path": {
119
- "type": "string",
120
- "description": "Path to the code file to analyze",
121
- },
122
- "language": {
123
- "type": "string",
124
- "description": "Programming language (optional, auto-detected if not specified)",
125
- },
126
- "include_complexity": {
127
- "type": "boolean",
128
- "description": "Include complexity metrics in the analysis",
129
- "default": True,
130
- },
131
- "include_details": {
132
- "type": "boolean",
133
- "description": "Include detailed element information",
134
- "default": False,
135
- },
136
- },
137
- "required": ["file_path"],
138
- "additionalProperties": False,
139
- },
140
- )
141
- ]
142
-
143
- # Add tools from tool classes - FIXED VERSION
144
- for tool_instance in [
145
- self.read_partial_tool,
146
- self.table_format_tool,
147
- self.universal_analyze_tool,
148
- ]:
149
- tool_def = tool_instance.get_tool_definition()
150
- if isinstance(tool_def, dict):
151
- # Convert dict to Tool object
152
- tools.append(Tool(**tool_def))
153
- else:
154
- # Already a Tool object
155
- tools.append(tool_def)
156
-
157
- return tools
158
-
159
- @server.call_tool()
160
- async def handle_call_tool(
161
- name: str, arguments: Dict[str, Any]
162
- ) -> List[TextContent]:
163
- """Handle tool calls."""
164
- try:
165
- if name == "analyze_code_scale":
166
- result = await self._analyze_code_scale(arguments)
167
- return [
168
- TextContent(
169
- type="text",
170
- text=json.dumps(result, indent=2, ensure_ascii=False),
171
- )
172
- ]
173
- elif name == "read_code_partial":
174
- result = await self.read_partial_tool.execute(arguments)
175
- return [
176
- TextContent(
177
- type="text",
178
- text=json.dumps(result, indent=2, ensure_ascii=False),
179
- )
180
- ]
181
- elif name == "format_table":
182
- result = await self.table_format_tool.execute(arguments)
183
- return [
184
- TextContent(
185
- type="text",
186
- text=json.dumps(result, indent=2, ensure_ascii=False),
187
- )
188
- ]
189
- elif name == "analyze_code_universal":
190
- result = await self.universal_analyze_tool.execute(arguments)
191
- return [
192
- TextContent(
193
- type="text",
194
- text=json.dumps(result, indent=2, ensure_ascii=False),
195
- )
196
- ]
197
- else:
198
- raise ValueError(f"Unknown tool: {name}")
199
-
200
- except Exception as e:
201
- try:
202
- logger.error(f"Tool call error for {name}: {e}")
203
- except (ValueError, OSError):
204
- pass # Silently ignore logging errors during shutdown
205
- return [
206
- TextContent(
207
- type="text",
208
- text=json.dumps(
209
- {"error": str(e), "tool": name, "arguments": arguments},
210
- indent=2,
211
- ),
212
- )
213
- ]
214
-
215
- # Register resources
216
- @server.list_resources()
217
- async def handle_list_resources() -> List[Resource]:
218
- """List available resources."""
219
- return [
220
- Resource(
221
- uri=self.code_file_resource.get_resource_info()["uri_template"],
222
- name=self.code_file_resource.get_resource_info()["name"],
223
- description=self.code_file_resource.get_resource_info()[
224
- "description"
225
- ],
226
- mimeType=self.code_file_resource.get_resource_info()["mime_type"],
227
- ),
228
- Resource(
229
- uri=self.project_stats_resource.get_resource_info()["uri_template"],
230
- name=self.project_stats_resource.get_resource_info()["name"],
231
- description=self.project_stats_resource.get_resource_info()[
232
- "description"
233
- ],
234
- mimeType=self.project_stats_resource.get_resource_info()[
235
- "mime_type"
236
- ],
237
- ),
238
- ]
239
-
240
- @server.read_resource()
241
- async def handle_read_resource(uri: str) -> str:
242
- """Read resource content."""
243
- try:
244
- # Check which resource matches the URI
245
- if self.code_file_resource.matches_uri(uri):
246
- return await self.code_file_resource.read_resource(uri)
247
- elif self.project_stats_resource.matches_uri(uri):
248
- return await self.project_stats_resource.read_resource(uri)
249
- else:
250
- raise ValueError(f"Resource not found: {uri}")
251
-
252
- except Exception as e:
253
- try:
254
- logger.error(f"Resource read error for {uri}: {e}")
255
- except (ValueError, OSError):
256
- pass # Silently ignore logging errors during shutdown
257
- raise
258
-
259
- self.server = server
260
- try:
261
- logger.info("MCP server created successfully")
262
- except (ValueError, OSError):
263
- pass # Silently ignore logging errors during shutdown
264
- return server
265
-
266
- def set_project_path(self, project_path: str) -> None:
267
- """
268
- Set the project path for statistics resource
269
-
270
- Args:
271
- project_path: Path to the project directory
272
- """
273
- self.project_stats_resource.set_project_path(project_path)
274
- try:
275
- logger.info(f"Set project path to: {project_path}")
276
- except (ValueError, OSError):
277
- pass # Silently ignore logging errors during shutdown
278
-
279
- async def run(self) -> None:
280
- """
281
- Run the MCP server.
282
-
283
- This method starts the server and handles stdio communication.
284
- """
285
- if not MCP_AVAILABLE:
286
- raise RuntimeError("MCP library not available. Please install mcp package.")
287
-
288
- server = self.create_server()
289
-
290
- # Initialize server options
291
- options = InitializationOptions(
292
- server_name=self.name,
293
- server_version=self.version,
294
- capabilities=MCP_INFO["capabilities"],
295
- )
296
-
297
- try:
298
- logger.info(f"Starting MCP server: {self.name} v{self.version}")
299
- except (ValueError, OSError):
300
- pass # Silently ignore logging errors during shutdown
301
-
302
- try:
303
- async with stdio_server() as (read_stream, write_stream):
304
- await server.run(read_stream, write_stream, options)
305
- except Exception as e:
306
- # Use safe logging to avoid I/O errors during shutdown
307
- try:
308
- logger.error(f"Server error: {e}")
309
- except (ValueError, OSError):
310
- pass # Silently ignore logging errors during shutdown
311
- raise
312
- finally:
313
- # Safe cleanup
314
- try:
315
- logger.info("MCP server shutting down")
316
- except (ValueError, OSError):
317
- pass # Silently ignore logging errors during shutdown
318
-
319
-
320
- async def main() -> None:
321
- """Main entry point for the MCP server."""
322
- try:
323
- server = TreeSitterAnalyzerMCPServer()
324
- await server.run()
325
- except KeyboardInterrupt:
326
- try:
327
- logger.info("Server stopped by user")
328
- except (ValueError, OSError):
329
- pass # Silently ignore logging errors during shutdown
330
- except Exception as e:
331
- try:
332
- logger.error(f"Server failed: {e}")
333
- except (ValueError, OSError):
334
- pass # Silently ignore logging errors during shutdown
335
- sys.exit(1)
336
- finally:
337
- # Ensure clean shutdown
338
- try:
339
- logger.info("MCP server shutdown complete")
340
- except (ValueError, OSError):
341
- pass # Silently ignore logging errors during shutdown
342
-
343
-
344
- if __name__ == "__main__":
345
- asyncio.run(main())
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 FallbackServer:
26
+ pass
27
+
28
+ class FallbackInitializationOptions:
29
+ pass
30
+
31
+
32
+ from ..core.analysis_engine import get_analysis_engine
33
+ from ..utils import setup_logger
34
+ from . import MCP_INFO
35
+ from .resources import CodeFileResource, ProjectStatsResource
36
+ from .tools.base_tool import MCPTool
37
+ from .tools.read_partial_tool import ReadPartialTool
38
+ from .tools.table_format_tool import TableFormatTool
39
+ from .tools.universal_analyze_tool import UniversalAnalyzeTool
40
+ from .utils.error_handler import handle_mcp_errors
41
+
42
+ # Set up logging
43
+ logger = setup_logger(__name__)
44
+
45
+
46
+ class TreeSitterAnalyzerMCPServer:
47
+ """
48
+ MCP Server for Tree-sitter Analyzer
49
+
50
+ Provides code analysis capabilities through the Model Context Protocol,
51
+ integrating with existing analyzer components.
52
+ """
53
+
54
+ def __init__(self) -> None:
55
+ """Initialize the MCP server with analyzer components."""
56
+ self.server: Server | None = None
57
+ self.analysis_engine = get_analysis_engine()
58
+ # Use unified analysis engine instead of deprecated AdvancedAnalyzer
59
+
60
+ # Initialize MCP tools
61
+ self.read_partial_tool: MCPTool = ReadPartialTool()
62
+ self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool()
63
+ self.table_format_tool: MCPTool = TableFormatTool()
64
+
65
+ # Initialize MCP resources
66
+ self.code_file_resource = CodeFileResource()
67
+ self.project_stats_resource = ProjectStatsResource()
68
+
69
+ # Server metadata
70
+ self.name = MCP_INFO["name"]
71
+ self.version = MCP_INFO["version"]
72
+
73
+ logger.info(f"Initializing {self.name} v{self.version}")
74
+
75
+ @handle_mcp_errors("analyze_code_scale")
76
+ async def _analyze_code_scale(self, arguments: dict[str, Any]) -> dict[str, Any]:
77
+ """
78
+ Analyze code scale and complexity metrics by delegating to the universal_analyze_tool.
79
+ """
80
+ # Delegate the execution to the already initialized tool
81
+ return await self.universal_analyze_tool.execute(arguments)
82
+
83
+ def create_server(self) -> Server:
84
+ """
85
+ Create and configure the MCP server.
86
+
87
+ Returns:
88
+ Configured MCP Server instance
89
+ """
90
+ if not MCP_AVAILABLE:
91
+ raise RuntimeError("MCP library not available. Please install mcp package.")
92
+
93
+ server: Server = Server(self.name)
94
+
95
+ # Register tools
96
+ @server.list_tools()
97
+ async def handle_list_tools() -> list[Tool]:
98
+ """List available tools."""
99
+ tools = [
100
+ Tool(
101
+ name="analyze_code_scale",
102
+ description="Analyze code scale, complexity, and structure metrics",
103
+ inputSchema={
104
+ "type": "object",
105
+ "properties": {
106
+ "file_path": {
107
+ "type": "string",
108
+ "description": "Path to the code file to analyze",
109
+ },
110
+ "language": {
111
+ "type": "string",
112
+ "description": "Programming language (optional, auto-detected if not specified)",
113
+ },
114
+ "include_complexity": {
115
+ "type": "boolean",
116
+ "description": "Include complexity metrics in the analysis",
117
+ "default": True,
118
+ },
119
+ "include_details": {
120
+ "type": "boolean",
121
+ "description": "Include detailed element information",
122
+ "default": False,
123
+ },
124
+ },
125
+ "required": ["file_path"],
126
+ "additionalProperties": False,
127
+ },
128
+ )
129
+ ]
130
+
131
+ # Add tools from tool classes - FIXED VERSION
132
+ for tool_instance in [
133
+ self.read_partial_tool,
134
+ self.table_format_tool,
135
+ self.universal_analyze_tool,
136
+ ]:
137
+ tool_def = tool_instance.get_tool_definition()
138
+ if isinstance(tool_def, dict):
139
+ # Convert dict to Tool object
140
+ tools.append(Tool(**tool_def))
141
+ else:
142
+ # Already a Tool object
143
+ tools.append(tool_def)
144
+
145
+ return tools
146
+
147
+ @server.call_tool()
148
+ async def handle_call_tool(
149
+ name: str, arguments: dict[str, Any]
150
+ ) -> list[TextContent]:
151
+ """Handle tool calls."""
152
+ try:
153
+ if name == "analyze_code_scale":
154
+ result = await self._analyze_code_scale(arguments)
155
+ return [
156
+ TextContent(
157
+ type="text",
158
+ text=json.dumps(result, indent=2, ensure_ascii=False),
159
+ )
160
+ ]
161
+ elif name == "read_code_partial":
162
+ result = await self.read_partial_tool.execute(arguments)
163
+ return [
164
+ TextContent(
165
+ type="text",
166
+ text=json.dumps(result, indent=2, ensure_ascii=False),
167
+ )
168
+ ]
169
+ elif name == "format_table":
170
+ result = await self.table_format_tool.execute(arguments)
171
+ return [
172
+ TextContent(
173
+ type="text",
174
+ text=json.dumps(result, indent=2, ensure_ascii=False),
175
+ )
176
+ ]
177
+ elif name == "analyze_code_universal":
178
+ result = await self.universal_analyze_tool.execute(arguments)
179
+ return [
180
+ TextContent(
181
+ type="text",
182
+ text=json.dumps(result, indent=2, ensure_ascii=False),
183
+ )
184
+ ]
185
+ else:
186
+ raise ValueError(f"Unknown tool: {name}")
187
+
188
+ except Exception as e:
189
+ try:
190
+ logger.error(f"Tool call error for {name}: {e}")
191
+ except (ValueError, OSError):
192
+ pass # Silently ignore logging errors during shutdown
193
+ return [
194
+ TextContent(
195
+ type="text",
196
+ text=json.dumps(
197
+ {"error": str(e), "tool": name, "arguments": arguments},
198
+ indent=2,
199
+ ),
200
+ )
201
+ ]
202
+
203
+ # Register resources
204
+ @server.list_resources()
205
+ async def handle_list_resources() -> list[Resource]:
206
+ """List available resources."""
207
+ return [
208
+ Resource(
209
+ uri=self.code_file_resource.get_resource_info()["uri_template"],
210
+ name=self.code_file_resource.get_resource_info()["name"],
211
+ description=self.code_file_resource.get_resource_info()[
212
+ "description"
213
+ ],
214
+ mimeType=self.code_file_resource.get_resource_info()["mime_type"],
215
+ ),
216
+ Resource(
217
+ uri=self.project_stats_resource.get_resource_info()["uri_template"],
218
+ name=self.project_stats_resource.get_resource_info()["name"],
219
+ description=self.project_stats_resource.get_resource_info()[
220
+ "description"
221
+ ],
222
+ mimeType=self.project_stats_resource.get_resource_info()[
223
+ "mime_type"
224
+ ],
225
+ ),
226
+ ]
227
+
228
+ @server.read_resource()
229
+ async def handle_read_resource(uri: str) -> str:
230
+ """Read resource content."""
231
+ try:
232
+ # Check which resource matches the URI
233
+ if self.code_file_resource.matches_uri(uri):
234
+ return await self.code_file_resource.read_resource(uri)
235
+ elif self.project_stats_resource.matches_uri(uri):
236
+ return await self.project_stats_resource.read_resource(uri)
237
+ else:
238
+ raise ValueError(f"Resource not found: {uri}")
239
+
240
+ except Exception as e:
241
+ try:
242
+ logger.error(f"Resource read error for {uri}: {e}")
243
+ except (ValueError, OSError):
244
+ pass # Silently ignore logging errors during shutdown
245
+ raise
246
+
247
+ self.server = server
248
+ try:
249
+ logger.info("MCP server created successfully")
250
+ except (ValueError, OSError):
251
+ pass # Silently ignore logging errors during shutdown
252
+ return server
253
+
254
+ def set_project_path(self, project_path: str) -> None:
255
+ """
256
+ Set the project path for statistics resource
257
+
258
+ Args:
259
+ project_path: Path to the project directory
260
+ """
261
+ self.project_stats_resource.set_project_path(project_path)
262
+ try:
263
+ logger.info(f"Set project path to: {project_path}")
264
+ except (ValueError, OSError):
265
+ pass # Silently ignore logging errors during shutdown
266
+
267
+ async def run(self) -> None:
268
+ """
269
+ Run the MCP server.
270
+
271
+ This method starts the server and handles stdio communication.
272
+ """
273
+ if not MCP_AVAILABLE:
274
+ raise RuntimeError("MCP library not available. Please install mcp package.")
275
+
276
+ server = self.create_server()
277
+
278
+ # Initialize server options
279
+ options = InitializationOptions(
280
+ server_name=self.name,
281
+ server_version=self.version,
282
+ capabilities=MCP_INFO["capabilities"],
283
+ )
284
+
285
+ try:
286
+ logger.info(f"Starting MCP server: {self.name} v{self.version}")
287
+ except (ValueError, OSError):
288
+ pass # Silently ignore logging errors during shutdown
289
+
290
+ try:
291
+ async with stdio_server() as (read_stream, write_stream):
292
+ await server.run(read_stream, write_stream, options)
293
+ except Exception as e:
294
+ # Use safe logging to avoid I/O errors during shutdown
295
+ try:
296
+ logger.error(f"Server error: {e}")
297
+ except (ValueError, OSError):
298
+ pass # Silently ignore logging errors during shutdown
299
+ raise
300
+ finally:
301
+ # Safe cleanup
302
+ try:
303
+ logger.info("MCP server shutting down")
304
+ except (ValueError, OSError):
305
+ pass # Silently ignore logging errors during shutdown
306
+
307
+
308
+ async def main() -> None:
309
+ """Main entry point for the MCP server."""
310
+ try:
311
+ server = TreeSitterAnalyzerMCPServer()
312
+ await server.run()
313
+ except KeyboardInterrupt:
314
+ try:
315
+ logger.info("Server stopped by user")
316
+ except (ValueError, OSError):
317
+ pass # Silently ignore logging errors during shutdown
318
+ except Exception as e:
319
+ try:
320
+ logger.error(f"Server failed: {e}")
321
+ except (ValueError, OSError):
322
+ pass # Silently ignore logging errors during shutdown
323
+ sys.exit(1)
324
+ finally:
325
+ # Ensure clean shutdown
326
+ try:
327
+ logger.info("MCP server shutdown complete")
328
+ except (ValueError, OSError):
329
+ pass # Silently ignore logging errors during shutdown
330
+
331
+
332
+ if __name__ == "__main__":
333
+ asyncio.run(main())