tree-sitter-analyzer 1.9.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Read Code Partial MCP Tool
4
+
5
+ This tool provides partial file reading functionality through the MCP protocol,
6
+ allowing selective content extraction with line and column range support.
7
+ """
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from ...file_handler import read_file_partial
14
+ from ...utils import setup_logger
15
+ from ..utils.file_output_manager import FileOutputManager
16
+ from .base_tool import BaseMCPTool
17
+
18
+ # Set up logging
19
+ logger = setup_logger(__name__)
20
+
21
+
22
+ class ReadPartialTool(BaseMCPTool):
23
+ """
24
+ MCP Tool for reading partial content from code files.
25
+
26
+ This tool integrates with existing file_handler functionality to provide
27
+ selective file content reading through the MCP protocol.
28
+ """
29
+
30
+ def __init__(self, project_root: str | None = None) -> None:
31
+ """Initialize the read partial tool."""
32
+ super().__init__(project_root)
33
+ self.file_output_manager = FileOutputManager(project_root)
34
+ logger.info("ReadPartialTool initialized with security validation")
35
+
36
+ def get_tool_schema(self) -> dict[str, Any]:
37
+ """
38
+ Get the MCP tool schema for read_code_partial.
39
+
40
+ Returns:
41
+ Dictionary containing the tool schema
42
+ """
43
+ return {
44
+ "type": "object",
45
+ "properties": {
46
+ "file_path": {
47
+ "type": "string",
48
+ "description": "Path to the code file to read",
49
+ },
50
+ "start_line": {
51
+ "type": "integer",
52
+ "description": "Starting line number (1-based)",
53
+ "minimum": 1,
54
+ },
55
+ "end_line": {
56
+ "type": "integer",
57
+ "description": "Ending line number (1-based, optional - reads to end if not specified)",
58
+ "minimum": 1,
59
+ },
60
+ "start_column": {
61
+ "type": "integer",
62
+ "description": "Starting column number (0-based, optional)",
63
+ "minimum": 0,
64
+ },
65
+ "end_column": {
66
+ "type": "integer",
67
+ "description": "Ending column number (0-based, optional)",
68
+ "minimum": 0,
69
+ },
70
+ "format": {
71
+ "type": "string",
72
+ "description": "Output format for the content",
73
+ "enum": ["text", "json", "raw"],
74
+ "default": "text",
75
+ },
76
+ "output_file": {
77
+ "type": "string",
78
+ "description": "Optional filename to save output to file (extension auto-detected based on content)",
79
+ },
80
+ "suppress_output": {
81
+ "type": "boolean",
82
+ "description": "When true and output_file is specified, suppress partial_content_result in response to save tokens",
83
+ "default": False,
84
+ },
85
+ },
86
+ "required": ["file_path", "start_line"],
87
+ }
88
+
89
+ async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
90
+ """
91
+ Execute the read_code_partial tool.
92
+
93
+ Args:
94
+ arguments: Tool arguments containing file_path, line/column ranges, and format
95
+
96
+ Returns:
97
+ Dictionary containing the partial file content and metadata (CLI --partial-read compatible format)
98
+
99
+ Raises:
100
+ ValueError: If required arguments are missing or invalid
101
+ FileNotFoundError: If the specified file doesn't exist
102
+ """
103
+ # Validate required arguments
104
+ if "file_path" not in arguments:
105
+ raise ValueError("file_path is required")
106
+
107
+ if "start_line" not in arguments:
108
+ raise ValueError("start_line is required")
109
+
110
+ file_path = arguments["file_path"]
111
+ start_line = arguments["start_line"]
112
+ end_line = arguments.get("end_line")
113
+ start_column = arguments.get("start_column")
114
+ end_column = arguments.get("end_column")
115
+ output_file = arguments.get("output_file")
116
+ suppress_output = arguments.get("suppress_output", False)
117
+ output_format = arguments.get("format", "text")
118
+
119
+ # Security validation BEFORE path resolution to catch symlinks
120
+ is_valid, error_msg = self.security_validator.validate_file_path(
121
+ file_path, self.project_root
122
+ )
123
+ if not is_valid:
124
+ logger.warning(
125
+ f"Security validation failed for file path: {file_path} - {error_msg}"
126
+ )
127
+ return {
128
+ "success": False,
129
+ "error": f"Security validation failed: {error_msg}",
130
+ "file_path": file_path,
131
+ }
132
+
133
+ # Resolve file path using common path resolver
134
+ resolved_path = self.path_resolver.resolve(file_path)
135
+
136
+ # Additional security validation on resolved path
137
+ is_valid, error_msg = self.security_validator.validate_file_path(
138
+ resolved_path, self.project_root
139
+ )
140
+ if not is_valid:
141
+ logger.warning(
142
+ f"Security validation failed for resolved path: {resolved_path} - {error_msg}"
143
+ )
144
+ return {
145
+ "success": False,
146
+ "error": f"Security validation failed for resolved path: {error_msg}",
147
+ "file_path": file_path,
148
+ }
149
+
150
+ # Validate file exists
151
+ if not Path(resolved_path).exists():
152
+ return {
153
+ "success": False,
154
+ "error": "Invalid file path: file does not exist",
155
+ "file_path": file_path,
156
+ }
157
+
158
+ # Validate line numbers
159
+ if start_line < 1:
160
+ return {
161
+ "success": False,
162
+ "error": "start_line must be >= 1",
163
+ "file_path": file_path,
164
+ }
165
+
166
+ if end_line is not None and end_line < start_line:
167
+ return {
168
+ "success": False,
169
+ "error": "end_line must be >= start_line",
170
+ "file_path": file_path,
171
+ }
172
+
173
+ # Validate column numbers
174
+ if start_column is not None and start_column < 0:
175
+ return {
176
+ "success": False,
177
+ "error": "start_column must be >= 0",
178
+ "file_path": file_path,
179
+ }
180
+
181
+ if end_column is not None and end_column < 0:
182
+ return {
183
+ "success": False,
184
+ "error": "end_column must be >= 0",
185
+ "file_path": file_path,
186
+ }
187
+
188
+ logger.info(
189
+ f"Reading partial content from {file_path}: lines {start_line}-{end_line or 'end'}"
190
+ )
191
+
192
+ try:
193
+ # Use existing file_handler functionality
194
+ # Use performance monitoring with proper context manager
195
+ from ...mcp.utils import get_performance_monitor
196
+
197
+ with get_performance_monitor().measure_operation("read_code_partial"):
198
+ content = self._read_file_partial(
199
+ resolved_path, start_line, end_line, start_column, end_column
200
+ )
201
+
202
+ if content is None:
203
+ return {
204
+ "success": False,
205
+ "error": f"Failed to read partial content from file: {file_path}",
206
+ "file_path": file_path,
207
+ }
208
+
209
+ # Check if content is empty or invalid range
210
+ if not content or content.strip() == "":
211
+ return {
212
+ "success": False,
213
+ "error": f"Invalid line range or empty content: start_line={start_line}, end_line={end_line}",
214
+ "file_path": file_path,
215
+ }
216
+
217
+ # Build result structure compatible with CLI --partial-read format
218
+ result_data = {
219
+ "file_path": file_path,
220
+ "range": {
221
+ "start_line": start_line,
222
+ "end_line": end_line,
223
+ "start_column": start_column,
224
+ "end_column": end_column,
225
+ },
226
+ "content": content,
227
+ "content_length": len(content),
228
+ }
229
+
230
+ # Format as JSON string like CLI does
231
+ json_output = json.dumps(result_data, indent=2, ensure_ascii=False)
232
+
233
+ # Build range info for header
234
+ range_info = f"Line {start_line}"
235
+ if end_line:
236
+ range_info += f"-{end_line}"
237
+
238
+ # Build CLI-compatible output with header and JSON (without log message)
239
+ cli_output = (
240
+ f"--- Partial Read Result ---\n"
241
+ f"File: {file_path}\n"
242
+ f"Range: {range_info}\n"
243
+ f"Characters read: {len(content)}\n"
244
+ f"{json_output}"
245
+ )
246
+
247
+ logger.info(
248
+ f"Successfully read {len(content)} characters from {file_path}"
249
+ )
250
+
251
+ # Calculate lines extracted
252
+ lines_extracted = len(content.split("\n")) if content else 0
253
+ if end_line:
254
+ lines_extracted = end_line - start_line + 1
255
+
256
+ # Build result - conditionally include partial_content_result based on suppress_output
257
+ result = {
258
+ "success": True,
259
+ "file_path": file_path,
260
+ "range": {
261
+ "start_line": start_line,
262
+ "end_line": end_line,
263
+ "start_column": start_column,
264
+ "end_column": end_column,
265
+ },
266
+ "content_length": len(content),
267
+ "lines_extracted": lines_extracted,
268
+ }
269
+
270
+ # Only include partial_content_result if not suppressed or no output file specified
271
+ if not suppress_output or not output_file:
272
+ if output_format == "json":
273
+ # For JSON format, return structured data with exact line count
274
+ lines = content.split("\n") if content else []
275
+
276
+ # If end_line is specified, ensure we return exactly the requested number of lines
277
+ if end_line and len(lines) > lines_extracted:
278
+ lines = lines[:lines_extracted]
279
+ elif end_line and len(lines) < lines_extracted:
280
+ # Pad with empty lines if needed (shouldn't normally happen)
281
+ lines.extend([""] * (lines_extracted - len(lines)))
282
+
283
+ result["partial_content_result"] = {
284
+ "lines": lines,
285
+ "metadata": {
286
+ "file_path": file_path,
287
+ "range": {
288
+ "start_line": start_line,
289
+ "end_line": end_line,
290
+ "start_column": start_column,
291
+ "end_column": end_column,
292
+ },
293
+ "content_length": len(content),
294
+ "lines_count": len(lines),
295
+ },
296
+ }
297
+ else:
298
+ # For text/raw format, return CLI-compatible string
299
+ result["partial_content_result"] = cli_output
300
+
301
+ # Handle file output if requested
302
+ if output_file:
303
+ try:
304
+ # Generate base name from original file path if not provided
305
+ if not output_file or output_file.strip() == "":
306
+ base_name = Path(file_path).stem + "_extract"
307
+ else:
308
+ base_name = output_file
309
+
310
+ # Determine what content to save based on format preference
311
+ if output_format == "raw":
312
+ # Save only the extracted code content (no metadata)
313
+ content_to_save = content
314
+ elif output_format == "json":
315
+ # Save structured JSON data
316
+ content_to_save = json_output
317
+ else: # format == "text" (default)
318
+ # Save CLI-compatible format with headers
319
+ content_to_save = cli_output
320
+
321
+ # Save to file with automatic extension detection
322
+ saved_file_path = self.file_output_manager.save_to_file(
323
+ content=content_to_save, base_name=base_name
324
+ )
325
+
326
+ result["output_file_path"] = saved_file_path
327
+ result["file_saved"] = True
328
+
329
+ logger.info(f"Extract output saved to: {saved_file_path}")
330
+
331
+ except Exception as e:
332
+ logger.error(f"Failed to save output to file: {e}")
333
+ result["file_save_error"] = str(e)
334
+ result["file_saved"] = False
335
+
336
+ return result
337
+
338
+ except Exception as e:
339
+ logger.error(f"Error reading partial content from {file_path}: {e}")
340
+ return {"success": False, "error": str(e), "file_path": file_path}
341
+
342
+ def _read_file_partial(
343
+ self,
344
+ file_path: str,
345
+ start_line: int,
346
+ end_line: int | None = None,
347
+ start_column: int | None = None,
348
+ end_column: int | None = None,
349
+ ) -> str | None:
350
+ """
351
+ Internal method to read partial file content.
352
+
353
+ This method wraps the existing read_file_partial function from file_handler.
354
+
355
+ Args:
356
+ file_path: Path to the file to read
357
+ start_line: Starting line number (1-based)
358
+ end_line: Ending line number (1-based, optional)
359
+ start_column: Starting column number (0-based, optional)
360
+ end_column: Ending column number (0-based, optional)
361
+
362
+ Returns:
363
+ Partial file content as string, or None if error
364
+ """
365
+ return read_file_partial(
366
+ file_path, start_line, end_line, start_column, end_column
367
+ )
368
+
369
+ def validate_arguments(self, arguments: dict[str, Any]) -> bool:
370
+ """
371
+ Validate tool arguments against the schema.
372
+
373
+ Args:
374
+ arguments: Arguments to validate
375
+
376
+ Returns:
377
+ True if arguments are valid
378
+
379
+ Raises:
380
+ ValueError: If arguments are invalid
381
+ """
382
+ schema = self.get_tool_schema()
383
+ required_fields = schema.get("required", [])
384
+
385
+ # Check required fields
386
+ for field in required_fields:
387
+ if field not in arguments:
388
+ raise ValueError(f"Required field '{field}' is missing")
389
+
390
+ # Validate file_path
391
+ if "file_path" in arguments:
392
+ file_path = arguments["file_path"]
393
+ if not isinstance(file_path, str):
394
+ raise ValueError("file_path must be a string")
395
+ if not file_path.strip():
396
+ raise ValueError("file_path cannot be empty")
397
+
398
+ # Validate start_line
399
+ if "start_line" in arguments:
400
+ start_line = arguments["start_line"]
401
+ if not isinstance(start_line, int):
402
+ raise ValueError("start_line must be an integer")
403
+ if start_line < 1:
404
+ raise ValueError("start_line must be >= 1")
405
+
406
+ # Validate end_line
407
+ if "end_line" in arguments:
408
+ end_line = arguments["end_line"]
409
+ if not isinstance(end_line, int):
410
+ raise ValueError("end_line must be an integer")
411
+ if end_line < 1:
412
+ raise ValueError("end_line must be >= 1")
413
+ if "start_line" in arguments and end_line < arguments["start_line"]:
414
+ raise ValueError("end_line must be >= start_line")
415
+
416
+ # Validate column numbers
417
+ for col_field in ["start_column", "end_column"]:
418
+ if col_field in arguments:
419
+ col_value = arguments[col_field]
420
+ if not isinstance(col_value, int):
421
+ raise ValueError(f"{col_field} must be an integer")
422
+ if col_value < 0:
423
+ raise ValueError(f"{col_field} must be >= 0")
424
+
425
+ # Validate format
426
+ if "format" in arguments:
427
+ format_value = arguments["format"]
428
+ if not isinstance(format_value, str):
429
+ raise ValueError("format must be a string")
430
+ if format_value not in ["text", "json", "raw"]:
431
+ raise ValueError("format must be 'text', 'json', or 'raw'")
432
+
433
+ # Validate output_file if provided
434
+ if "output_file" in arguments:
435
+ output_file = arguments["output_file"]
436
+ if not isinstance(output_file, str):
437
+ raise ValueError("output_file must be a string")
438
+ if not output_file.strip():
439
+ raise ValueError("output_file cannot be empty")
440
+
441
+ # Validate suppress_output if provided
442
+ if "suppress_output" in arguments:
443
+ suppress_output = arguments["suppress_output"]
444
+ if not isinstance(suppress_output, bool):
445
+ raise ValueError("suppress_output must be a boolean")
446
+
447
+ return True
448
+
449
+ def get_tool_definition(self) -> dict[str, Any]:
450
+ """
451
+ Get the MCP tool definition for read_code_partial.
452
+
453
+ Returns:
454
+ Tool definition dictionary compatible with MCP server
455
+ """
456
+ return {
457
+ "name": "extract_code_section",
458
+ "description": "Extract specific code sections by line/column range with multiple output formats (text/json/raw), optionally save to file with token optimization",
459
+ "inputSchema": self.get_tool_schema(),
460
+ }
461
+
462
+
463
+ # Tool instance for easy access
464
+ read_partial_tool = ReadPartialTool()