tree-sitter-analyzer 0.9.3__py3-none-any.whl → 0.9.4__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 (32) hide show
  1. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  2. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -141
  3. tree_sitter_analyzer/cli/commands/query_command.py +92 -88
  4. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  5. tree_sitter_analyzer/cli/info_commands.py +121 -121
  6. tree_sitter_analyzer/cli_main.py +307 -307
  7. tree_sitter_analyzer/core/analysis_engine.py +584 -584
  8. tree_sitter_analyzer/core/cache_service.py +5 -4
  9. tree_sitter_analyzer/core/query.py +502 -502
  10. tree_sitter_analyzer/encoding_utils.py +6 -2
  11. tree_sitter_analyzer/exceptions.py +400 -406
  12. tree_sitter_analyzer/formatters/java_formatter.py +291 -291
  13. tree_sitter_analyzer/formatters/python_formatter.py +259 -259
  14. tree_sitter_analyzer/interfaces/mcp_server.py +426 -425
  15. tree_sitter_analyzer/language_detector.py +398 -398
  16. tree_sitter_analyzer/language_loader.py +224 -224
  17. tree_sitter_analyzer/languages/java_plugin.py +1202 -1202
  18. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +559 -555
  19. tree_sitter_analyzer/mcp/server.py +30 -9
  20. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +21 -4
  21. tree_sitter_analyzer/mcp/tools/table_format_tool.py +22 -4
  22. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -567
  23. tree_sitter_analyzer/models.py +470 -470
  24. tree_sitter_analyzer/security/__init__.py +22 -22
  25. tree_sitter_analyzer/security/boundary_manager.py +243 -243
  26. tree_sitter_analyzer/security/regex_checker.py +297 -292
  27. tree_sitter_analyzer/table_formatter.py +703 -652
  28. tree_sitter_analyzer/utils.py +50 -19
  29. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/METADATA +1 -1
  30. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/RECORD +32 -32
  31. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/WHEEL +0 -0
  32. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/entry_points.txt +0 -0
@@ -1,425 +1,426 @@
1
- #!/usr/bin/env python3
2
- """
3
- MCP Server Interface
4
-
5
- New MCP server implementation that uses the API facade for all operations.
6
- Provides a clean separation between MCP protocol concerns and core analysis logic.
7
- """
8
-
9
- import asyncio
10
- import json
11
- import logging
12
- import sys
13
- from typing import Any
14
-
15
- try:
16
- from mcp.server import Server
17
- from mcp.server.models import InitializationOptions
18
- from mcp.server.stdio import stdio_server
19
- from mcp.types import Resource, TextContent, Tool
20
-
21
- MCP_AVAILABLE = True
22
- except ImportError:
23
- MCP_AVAILABLE = False
24
-
25
- # Fallback types for development without MCP
26
- class Server:
27
- pass
28
-
29
- class InitializationOptions:
30
- def __init__(self, **kwargs):
31
- pass
32
-
33
- class Tool:
34
- pass
35
-
36
- class Resource:
37
- pass
38
-
39
- class TextContent:
40
- pass
41
-
42
- def stdio_server():
43
- pass
44
-
45
- from .. import api
46
- from ..utils import log_error, log_info
47
-
48
- # Configure logging for MCP
49
- logging.basicConfig(
50
- level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
51
- )
52
- logger = logging.getLogger(__name__)
53
-
54
-
55
- class TreeSitterAnalyzerMCPServer:
56
- """
57
- MCP Server for Tree-sitter Analyzer using the new API facade.
58
-
59
- This server provides code analysis capabilities through the Model Context Protocol,
60
- using the unified API facade for all operations.
61
- """
62
-
63
- def __init__(self) -> None:
64
- """Initialize the MCP server."""
65
- if not MCP_AVAILABLE:
66
- raise ImportError("MCP library not available. Please install mcp package.")
67
-
68
- self.server: Server | None = None
69
- self.name = "tree-sitter-analyzer"
70
- self.version = "2.0.0"
71
-
72
- log_info(f"Initializing {self.name} v{self.version}")
73
-
74
- def create_server(self) -> Server:
75
- """Create and configure the MCP server."""
76
- server: Any = Server(self.name)
77
-
78
- @server.list_tools() # type: ignore
79
- async def handle_list_tools() -> list[Tool]:
80
- """List available tools."""
81
- return [
82
- Tool(
83
- name="analyze_file",
84
- description="Analyze a source code file comprehensively",
85
- inputSchema={
86
- "type": "object",
87
- "properties": {
88
- "file_path": {
89
- "type": "string",
90
- "description": "Path to the source file to analyze",
91
- },
92
- "language": {
93
- "type": "string",
94
- "description": "Programming language (optional, auto-detected if not specified)",
95
- },
96
- "queries": {
97
- "type": "array",
98
- "items": {"type": "string"},
99
- "description": "List of query names to execute (optional)",
100
- },
101
- "include_elements": {
102
- "type": "boolean",
103
- "description": "Whether to extract code elements",
104
- "default": True,
105
- },
106
- "include_queries": {
107
- "type": "boolean",
108
- "description": "Whether to execute queries",
109
- "default": True,
110
- },
111
- },
112
- "required": ["file_path"],
113
- "additionalProperties": False,
114
- },
115
- ),
116
- Tool(
117
- name="analyze_code",
118
- description="Analyze source code directly (without file)",
119
- inputSchema={
120
- "type": "object",
121
- "properties": {
122
- "source_code": {
123
- "type": "string",
124
- "description": "Source code string to analyze",
125
- },
126
- "language": {
127
- "type": "string",
128
- "description": "Programming language",
129
- },
130
- "queries": {
131
- "type": "array",
132
- "items": {"type": "string"},
133
- "description": "List of query names to execute (optional)",
134
- },
135
- "include_elements": {
136
- "type": "boolean",
137
- "description": "Whether to extract code elements",
138
- "default": True,
139
- },
140
- "include_queries": {
141
- "type": "boolean",
142
- "description": "Whether to execute queries",
143
- "default": True,
144
- },
145
- },
146
- "required": ["source_code", "language"],
147
- "additionalProperties": False,
148
- },
149
- ),
150
- Tool(
151
- name="extract_elements",
152
- description="Extract code elements from a file",
153
- inputSchema={
154
- "type": "object",
155
- "properties": {
156
- "file_path": {
157
- "type": "string",
158
- "description": "Path to the source file",
159
- },
160
- "language": {
161
- "type": "string",
162
- "description": "Programming language (optional, auto-detected if not specified)",
163
- },
164
- "element_types": {
165
- "type": "array",
166
- "items": {"type": "string"},
167
- "description": "Types of elements to extract (optional)",
168
- },
169
- },
170
- "required": ["file_path"],
171
- "additionalProperties": False,
172
- },
173
- ),
174
- Tool(
175
- name="execute_query",
176
- description="Execute a specific query on a file",
177
- inputSchema={
178
- "type": "object",
179
- "properties": {
180
- "file_path": {
181
- "type": "string",
182
- "description": "Path to the source file",
183
- },
184
- "query_name": {
185
- "type": "string",
186
- "description": "Name of the query to execute",
187
- },
188
- "language": {
189
- "type": "string",
190
- "description": "Programming language (optional, auto-detected if not specified)",
191
- },
192
- },
193
- "required": ["file_path", "query_name"],
194
- "additionalProperties": False,
195
- },
196
- ),
197
- Tool(
198
- name="validate_file",
199
- description="Validate a source code file",
200
- inputSchema={
201
- "type": "object",
202
- "properties": {
203
- "file_path": {
204
- "type": "string",
205
- "description": "Path to the source file to validate",
206
- }
207
- },
208
- "required": ["file_path"],
209
- "additionalProperties": False,
210
- },
211
- ),
212
- Tool(
213
- name="get_supported_languages",
214
- description="Get list of supported programming languages",
215
- inputSchema={
216
- "type": "object",
217
- "properties": {},
218
- "additionalProperties": False,
219
- },
220
- ),
221
- Tool(
222
- name="get_available_queries",
223
- description="Get available queries for a specific language",
224
- inputSchema={
225
- "type": "object",
226
- "properties": {
227
- "language": {
228
- "type": "string",
229
- "description": "Programming language name",
230
- }
231
- },
232
- "required": ["language"],
233
- "additionalProperties": False,
234
- },
235
- ),
236
- Tool(
237
- name="get_framework_info",
238
- description="Get information about the analyzer framework",
239
- inputSchema={
240
- "type": "object",
241
- "properties": {},
242
- "additionalProperties": False,
243
- },
244
- ),
245
- ]
246
-
247
- @server.call_tool() # type: ignore
248
- async def handle_call_tool(
249
- name: str, arguments: dict[str, Any]
250
- ) -> list[TextContent]:
251
- """Handle tool calls."""
252
- try:
253
- result = None
254
-
255
- if name == "analyze_file":
256
- result = api.analyze_file(
257
- file_path=arguments["file_path"],
258
- language=arguments.get("language"),
259
- queries=arguments.get("queries"),
260
- include_elements=arguments.get("include_elements", True),
261
- include_queries=arguments.get("include_queries", True),
262
- )
263
-
264
- elif name == "analyze_code":
265
- result = api.analyze_code(
266
- source_code=arguments["source_code"],
267
- language=arguments["language"],
268
- queries=arguments.get("queries"),
269
- include_elements=arguments.get("include_elements", True),
270
- include_queries=arguments.get("include_queries", True),
271
- )
272
-
273
- elif name == "extract_elements":
274
- result = api.extract_elements(
275
- file_path=arguments["file_path"],
276
- language=arguments.get("language"),
277
- element_types=arguments.get("element_types"),
278
- )
279
-
280
- elif name == "execute_query":
281
- result = api.execute_query(
282
- file_path=arguments["file_path"],
283
- query_name=arguments["query_name"],
284
- language=arguments.get("language"),
285
- )
286
-
287
- elif name == "validate_file":
288
- result = api.validate_file(arguments["file_path"])
289
-
290
- elif name == "get_supported_languages":
291
- result = {
292
- "languages": api.get_supported_languages(),
293
- "total": len(api.get_supported_languages()),
294
- }
295
-
296
- elif name == "get_available_queries":
297
- queries = api.get_available_queries(arguments["language"])
298
- result = {
299
- "language": arguments["language"],
300
- "queries": queries,
301
- "total": len(queries),
302
- }
303
-
304
- elif name == "get_framework_info":
305
- result = api.get_framework_info()
306
-
307
- else:
308
- raise ValueError(f"Unknown tool: {name}")
309
-
310
- return [
311
- TextContent(
312
- type="text",
313
- text=json.dumps(result, indent=2, ensure_ascii=False),
314
- )
315
- ]
316
-
317
- except Exception as e:
318
- log_error(f"Tool call error for {name}: {e}")
319
- error_result = {
320
- "error": str(e),
321
- "tool": name,
322
- "arguments": arguments,
323
- "success": False,
324
- }
325
- return [
326
- TextContent(
327
- type="text",
328
- text=json.dumps(error_result, indent=2, ensure_ascii=False),
329
- )
330
- ]
331
-
332
- @server.list_resources() # type: ignore
333
- async def handle_list_resources() -> list[Resource]:
334
- """List available resources."""
335
- return [
336
- Resource(
337
- uri="code://file/{file_path}", # type: ignore
338
- name="Code File Analysis",
339
- description="Access to code file content and analysis",
340
- mimeType="application/json",
341
- ),
342
- Resource(
343
- uri="code://stats/{stats_type}", # type: ignore
344
- name="Project Statistics",
345
- description="Access to project statistics and analysis data",
346
- mimeType="application/json",
347
- ),
348
- ]
349
-
350
- @server.read_resource() # type: ignore
351
- async def handle_read_resource(uri: str) -> str:
352
- """Read resource content."""
353
- try:
354
- if uri.startswith("code://file/"):
355
- # Extract file path from URI
356
- file_path = uri[len("code://file/") :]
357
-
358
- # Analyze the file
359
- result = api.analyze_file(file_path)
360
- return json.dumps(result, indent=2, ensure_ascii=False)
361
-
362
- elif uri.startswith("code://stats/"):
363
- # Extract stats type from URI
364
- stats_type = uri[len("code://stats/") :]
365
-
366
- # Get framework info as basic stats
367
- if stats_type == "framework":
368
- result = api.get_framework_info()
369
- elif stats_type == "languages":
370
- result = {
371
- "supported_languages": api.get_supported_languages(),
372
- "total_languages": len(api.get_supported_languages()),
373
- }
374
- else:
375
- raise ValueError(f"Unknown stats type: {stats_type}")
376
-
377
- return json.dumps(result, indent=2, ensure_ascii=False)
378
-
379
- else:
380
- raise ValueError(f"Resource not found: {uri}")
381
-
382
- except Exception as e:
383
- log_error(f"Resource read error for {uri}: {e}")
384
- error_result = {"error": str(e), "uri": uri, "success": False}
385
- return json.dumps(error_result, indent=2, ensure_ascii=False)
386
-
387
- self.server = server
388
- log_info("MCP server created successfully")
389
- return server # type: ignore
390
-
391
- async def run(self) -> None:
392
- """Run the MCP server."""
393
- server = self.create_server()
394
-
395
- # Initialize server options
396
- options = InitializationOptions(
397
- server_name=self.name,
398
- server_version=self.version,
399
- capabilities={"tools": {}, "resources": {}}, # type: ignore
400
- )
401
-
402
- log_info(f"Starting MCP server: {self.name} v{self.version}")
403
-
404
- try:
405
- async with stdio_server() as (read_stream, write_stream):
406
- await server.run(read_stream, write_stream, options)
407
- except Exception as e:
408
- log_error(f"Server error: {e}")
409
- raise
410
-
411
-
412
- async def main() -> None:
413
- """Main entry point for the MCP server."""
414
- try:
415
- server = TreeSitterAnalyzerMCPServer()
416
- await server.run()
417
- except KeyboardInterrupt:
418
- log_info("Server stopped by user")
419
- except Exception as e:
420
- log_error(f"Server failed: {e}")
421
- sys.exit(1)
422
-
423
-
424
- if __name__ == "__main__":
425
- asyncio.run(main())
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Server Interface
4
+
5
+ New MCP server implementation that uses the API facade for all operations.
6
+ Provides a clean separation between MCP protocol concerns and core analysis logic.
7
+ """
8
+
9
+ import asyncio
10
+ import json
11
+ import logging
12
+ import sys
13
+ from typing import Any
14
+
15
+ try:
16
+ from mcp.server import Server
17
+ from mcp.server.models import InitializationOptions
18
+ from mcp.server.stdio import stdio_server
19
+ from mcp.types import Resource, TextContent, Tool
20
+
21
+ MCP_AVAILABLE = True
22
+ except ImportError:
23
+ MCP_AVAILABLE = False
24
+
25
+ # Fallback types for development without MCP
26
+ class Server:
27
+ pass
28
+
29
+ class InitializationOptions:
30
+ def __init__(self, **kwargs):
31
+ pass
32
+
33
+ class Tool:
34
+ pass
35
+
36
+ class Resource:
37
+ pass
38
+
39
+ class TextContent:
40
+ pass
41
+
42
+ def stdio_server():
43
+ pass
44
+
45
+
46
+ from .. import api
47
+ from ..utils import log_error, log_info
48
+
49
+ # Configure logging for MCP
50
+ logging.basicConfig(
51
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
52
+ )
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ class TreeSitterAnalyzerMCPServer:
57
+ """
58
+ MCP Server for Tree-sitter Analyzer using the new API facade.
59
+
60
+ This server provides code analysis capabilities through the Model Context Protocol,
61
+ using the unified API facade for all operations.
62
+ """
63
+
64
+ def __init__(self) -> None:
65
+ """Initialize the MCP server."""
66
+ if not MCP_AVAILABLE:
67
+ raise ImportError("MCP library not available. Please install mcp package.")
68
+
69
+ self.server: Server | None = None
70
+ self.name = "tree-sitter-analyzer"
71
+ self.version = "2.0.0"
72
+
73
+ log_info(f"Initializing {self.name} v{self.version}")
74
+
75
+ def create_server(self) -> Server:
76
+ """Create and configure the MCP server."""
77
+ server: Any = Server(self.name)
78
+
79
+ @server.list_tools() # type: ignore
80
+ async def handle_list_tools() -> list[Tool]:
81
+ """List available tools."""
82
+ return [
83
+ Tool(
84
+ name="analyze_file",
85
+ description="Analyze a source code file comprehensively",
86
+ inputSchema={
87
+ "type": "object",
88
+ "properties": {
89
+ "file_path": {
90
+ "type": "string",
91
+ "description": "Path to the source file to analyze",
92
+ },
93
+ "language": {
94
+ "type": "string",
95
+ "description": "Programming language (optional, auto-detected if not specified)",
96
+ },
97
+ "queries": {
98
+ "type": "array",
99
+ "items": {"type": "string"},
100
+ "description": "List of query names to execute (optional)",
101
+ },
102
+ "include_elements": {
103
+ "type": "boolean",
104
+ "description": "Whether to extract code elements",
105
+ "default": True,
106
+ },
107
+ "include_queries": {
108
+ "type": "boolean",
109
+ "description": "Whether to execute queries",
110
+ "default": True,
111
+ },
112
+ },
113
+ "required": ["file_path"],
114
+ "additionalProperties": False,
115
+ },
116
+ ),
117
+ Tool(
118
+ name="analyze_code",
119
+ description="Analyze source code directly (without file)",
120
+ inputSchema={
121
+ "type": "object",
122
+ "properties": {
123
+ "source_code": {
124
+ "type": "string",
125
+ "description": "Source code string to analyze",
126
+ },
127
+ "language": {
128
+ "type": "string",
129
+ "description": "Programming language",
130
+ },
131
+ "queries": {
132
+ "type": "array",
133
+ "items": {"type": "string"},
134
+ "description": "List of query names to execute (optional)",
135
+ },
136
+ "include_elements": {
137
+ "type": "boolean",
138
+ "description": "Whether to extract code elements",
139
+ "default": True,
140
+ },
141
+ "include_queries": {
142
+ "type": "boolean",
143
+ "description": "Whether to execute queries",
144
+ "default": True,
145
+ },
146
+ },
147
+ "required": ["source_code", "language"],
148
+ "additionalProperties": False,
149
+ },
150
+ ),
151
+ Tool(
152
+ name="extract_elements",
153
+ description="Extract code elements from a file",
154
+ inputSchema={
155
+ "type": "object",
156
+ "properties": {
157
+ "file_path": {
158
+ "type": "string",
159
+ "description": "Path to the source file",
160
+ },
161
+ "language": {
162
+ "type": "string",
163
+ "description": "Programming language (optional, auto-detected if not specified)",
164
+ },
165
+ "element_types": {
166
+ "type": "array",
167
+ "items": {"type": "string"},
168
+ "description": "Types of elements to extract (optional)",
169
+ },
170
+ },
171
+ "required": ["file_path"],
172
+ "additionalProperties": False,
173
+ },
174
+ ),
175
+ Tool(
176
+ name="execute_query",
177
+ description="Execute a specific query on a file",
178
+ inputSchema={
179
+ "type": "object",
180
+ "properties": {
181
+ "file_path": {
182
+ "type": "string",
183
+ "description": "Path to the source file",
184
+ },
185
+ "query_name": {
186
+ "type": "string",
187
+ "description": "Name of the query to execute",
188
+ },
189
+ "language": {
190
+ "type": "string",
191
+ "description": "Programming language (optional, auto-detected if not specified)",
192
+ },
193
+ },
194
+ "required": ["file_path", "query_name"],
195
+ "additionalProperties": False,
196
+ },
197
+ ),
198
+ Tool(
199
+ name="validate_file",
200
+ description="Validate a source code file",
201
+ inputSchema={
202
+ "type": "object",
203
+ "properties": {
204
+ "file_path": {
205
+ "type": "string",
206
+ "description": "Path to the source file to validate",
207
+ }
208
+ },
209
+ "required": ["file_path"],
210
+ "additionalProperties": False,
211
+ },
212
+ ),
213
+ Tool(
214
+ name="get_supported_languages",
215
+ description="Get list of supported programming languages",
216
+ inputSchema={
217
+ "type": "object",
218
+ "properties": {},
219
+ "additionalProperties": False,
220
+ },
221
+ ),
222
+ Tool(
223
+ name="get_available_queries",
224
+ description="Get available queries for a specific language",
225
+ inputSchema={
226
+ "type": "object",
227
+ "properties": {
228
+ "language": {
229
+ "type": "string",
230
+ "description": "Programming language name",
231
+ }
232
+ },
233
+ "required": ["language"],
234
+ "additionalProperties": False,
235
+ },
236
+ ),
237
+ Tool(
238
+ name="get_framework_info",
239
+ description="Get information about the analyzer framework",
240
+ inputSchema={
241
+ "type": "object",
242
+ "properties": {},
243
+ "additionalProperties": False,
244
+ },
245
+ ),
246
+ ]
247
+
248
+ @server.call_tool() # type: ignore
249
+ async def handle_call_tool(
250
+ name: str, arguments: dict[str, Any]
251
+ ) -> list[TextContent]:
252
+ """Handle tool calls."""
253
+ try:
254
+ result = None
255
+
256
+ if name == "analyze_file":
257
+ result = api.analyze_file(
258
+ file_path=arguments["file_path"],
259
+ language=arguments.get("language"),
260
+ queries=arguments.get("queries"),
261
+ include_elements=arguments.get("include_elements", True),
262
+ include_queries=arguments.get("include_queries", True),
263
+ )
264
+
265
+ elif name == "analyze_code":
266
+ result = api.analyze_code(
267
+ source_code=arguments["source_code"],
268
+ language=arguments["language"],
269
+ queries=arguments.get("queries"),
270
+ include_elements=arguments.get("include_elements", True),
271
+ include_queries=arguments.get("include_queries", True),
272
+ )
273
+
274
+ elif name == "extract_elements":
275
+ result = api.extract_elements(
276
+ file_path=arguments["file_path"],
277
+ language=arguments.get("language"),
278
+ element_types=arguments.get("element_types"),
279
+ )
280
+
281
+ elif name == "execute_query":
282
+ result = api.execute_query(
283
+ file_path=arguments["file_path"],
284
+ query_name=arguments["query_name"],
285
+ language=arguments.get("language"),
286
+ )
287
+
288
+ elif name == "validate_file":
289
+ result = api.validate_file(arguments["file_path"])
290
+
291
+ elif name == "get_supported_languages":
292
+ result = {
293
+ "languages": api.get_supported_languages(),
294
+ "total": len(api.get_supported_languages()),
295
+ }
296
+
297
+ elif name == "get_available_queries":
298
+ queries = api.get_available_queries(arguments["language"])
299
+ result = {
300
+ "language": arguments["language"],
301
+ "queries": queries,
302
+ "total": len(queries),
303
+ }
304
+
305
+ elif name == "get_framework_info":
306
+ result = api.get_framework_info()
307
+
308
+ else:
309
+ raise ValueError(f"Unknown tool: {name}")
310
+
311
+ return [
312
+ TextContent(
313
+ type="text",
314
+ text=json.dumps(result, indent=2, ensure_ascii=False),
315
+ )
316
+ ]
317
+
318
+ except Exception as e:
319
+ log_error(f"Tool call error for {name}: {e}")
320
+ error_result = {
321
+ "error": str(e),
322
+ "tool": name,
323
+ "arguments": arguments,
324
+ "success": False,
325
+ }
326
+ return [
327
+ TextContent(
328
+ type="text",
329
+ text=json.dumps(error_result, indent=2, ensure_ascii=False),
330
+ )
331
+ ]
332
+
333
+ @server.list_resources() # type: ignore
334
+ async def handle_list_resources() -> list[Resource]:
335
+ """List available resources."""
336
+ return [
337
+ Resource(
338
+ uri="code://file/{file_path}", # type: ignore
339
+ name="Code File Analysis",
340
+ description="Access to code file content and analysis",
341
+ mimeType="application/json",
342
+ ),
343
+ Resource(
344
+ uri="code://stats/{stats_type}", # type: ignore
345
+ name="Project Statistics",
346
+ description="Access to project statistics and analysis data",
347
+ mimeType="application/json",
348
+ ),
349
+ ]
350
+
351
+ @server.read_resource() # type: ignore
352
+ async def handle_read_resource(uri: str) -> str:
353
+ """Read resource content."""
354
+ try:
355
+ if uri.startswith("code://file/"):
356
+ # Extract file path from URI
357
+ file_path = uri[len("code://file/") :]
358
+
359
+ # Analyze the file
360
+ result = api.analyze_file(file_path)
361
+ return json.dumps(result, indent=2, ensure_ascii=False)
362
+
363
+ elif uri.startswith("code://stats/"):
364
+ # Extract stats type from URI
365
+ stats_type = uri[len("code://stats/") :]
366
+
367
+ # Get framework info as basic stats
368
+ if stats_type == "framework":
369
+ result = api.get_framework_info()
370
+ elif stats_type == "languages":
371
+ result = {
372
+ "supported_languages": api.get_supported_languages(),
373
+ "total_languages": len(api.get_supported_languages()),
374
+ }
375
+ else:
376
+ raise ValueError(f"Unknown stats type: {stats_type}")
377
+
378
+ return json.dumps(result, indent=2, ensure_ascii=False)
379
+
380
+ else:
381
+ raise ValueError(f"Resource not found: {uri}")
382
+
383
+ except Exception as e:
384
+ log_error(f"Resource read error for {uri}: {e}")
385
+ error_result = {"error": str(e), "uri": uri, "success": False}
386
+ return json.dumps(error_result, indent=2, ensure_ascii=False)
387
+
388
+ self.server = server
389
+ log_info("MCP server created successfully")
390
+ return server # type: ignore
391
+
392
+ async def run(self) -> None:
393
+ """Run the MCP server."""
394
+ server = self.create_server()
395
+
396
+ # Initialize server options
397
+ options = InitializationOptions(
398
+ server_name=self.name,
399
+ server_version=self.version,
400
+ capabilities={"tools": {}, "resources": {}}, # type: ignore
401
+ )
402
+
403
+ log_info(f"Starting MCP server: {self.name} v{self.version}")
404
+
405
+ try:
406
+ async with stdio_server() as (read_stream, write_stream):
407
+ await server.run(read_stream, write_stream, options)
408
+ except Exception as e:
409
+ log_error(f"Server error: {e}")
410
+ raise
411
+
412
+
413
+ async def main() -> None:
414
+ """Main entry point for the MCP server."""
415
+ try:
416
+ server = TreeSitterAnalyzerMCPServer()
417
+ await server.run()
418
+ except KeyboardInterrupt:
419
+ log_info("Server stopped by user")
420
+ except Exception as e:
421
+ log_error(f"Server failed: {e}")
422
+ sys.exit(1)
423
+
424
+
425
+ if __name__ == "__main__":
426
+ asyncio.run(main())