tree-sitter-analyzer 0.6.2__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.

Files changed (69) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -160
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -141
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -81
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  15. tree_sitter_analyzer/cli/info_commands.py +121 -121
  16. tree_sitter_analyzer/cli_main.py +297 -297
  17. tree_sitter_analyzer/core/__init__.py +15 -15
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -555
  19. tree_sitter_analyzer/core/cache_service.py +320 -320
  20. tree_sitter_analyzer/core/engine.py +566 -566
  21. tree_sitter_analyzer/core/parser.py +293 -293
  22. tree_sitter_analyzer/encoding_utils.py +459 -459
  23. tree_sitter_analyzer/exceptions.py +406 -337
  24. tree_sitter_analyzer/file_handler.py +210 -210
  25. tree_sitter_analyzer/formatters/__init__.py +1 -1
  26. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  27. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  28. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  29. tree_sitter_analyzer/interfaces/cli.py +528 -528
  30. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -343
  31. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  32. tree_sitter_analyzer/interfaces/mcp_server.py +425 -405
  33. tree_sitter_analyzer/languages/__init__.py +10 -10
  34. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  35. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  36. tree_sitter_analyzer/mcp/__init__.py +31 -31
  37. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  38. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  39. tree_sitter_analyzer/mcp/server.py +346 -333
  40. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  41. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -654
  42. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
  43. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  44. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -300
  45. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -362
  46. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -543
  47. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  48. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  49. tree_sitter_analyzer/output_manager.py +253 -253
  50. tree_sitter_analyzer/plugins/__init__.py +280 -280
  51. tree_sitter_analyzer/plugins/base.py +529 -529
  52. tree_sitter_analyzer/plugins/manager.py +379 -379
  53. tree_sitter_analyzer/queries/__init__.py +26 -26
  54. tree_sitter_analyzer/queries/java.py +391 -391
  55. tree_sitter_analyzer/queries/javascript.py +148 -148
  56. tree_sitter_analyzer/queries/python.py +285 -285
  57. tree_sitter_analyzer/queries/typescript.py +229 -229
  58. tree_sitter_analyzer/query_loader.py +257 -257
  59. tree_sitter_analyzer/security/__init__.py +22 -0
  60. tree_sitter_analyzer/security/boundary_manager.py +237 -0
  61. tree_sitter_analyzer/security/regex_checker.py +292 -0
  62. tree_sitter_analyzer/security/validator.py +224 -0
  63. tree_sitter_analyzer/table_formatter.py +652 -473
  64. tree_sitter_analyzer/utils.py +277 -277
  65. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/METADATA +4 -1
  66. tree_sitter_analyzer-0.8.0.dist-info/RECORD +76 -0
  67. tree_sitter_analyzer-0.6.2.dist-info/RECORD +0 -72
  68. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/WHEEL +0 -0
  69. {tree_sitter_analyzer-0.6.2.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 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() # type: ignore
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() # type: ignore
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() # type: ignore
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() # type: ignore
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())
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())