iflow-mcp_developermode-korea_reversecore-mcp 1.0.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.
Files changed (79) hide show
  1. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/METADATA +543 -0
  2. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/RECORD +79 -0
  3. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/top_level.txt +1 -0
  7. reversecore_mcp/__init__.py +9 -0
  8. reversecore_mcp/core/__init__.py +78 -0
  9. reversecore_mcp/core/audit.py +101 -0
  10. reversecore_mcp/core/binary_cache.py +138 -0
  11. reversecore_mcp/core/command_spec.py +357 -0
  12. reversecore_mcp/core/config.py +432 -0
  13. reversecore_mcp/core/container.py +288 -0
  14. reversecore_mcp/core/decorators.py +152 -0
  15. reversecore_mcp/core/error_formatting.py +93 -0
  16. reversecore_mcp/core/error_handling.py +142 -0
  17. reversecore_mcp/core/evidence.py +229 -0
  18. reversecore_mcp/core/exceptions.py +296 -0
  19. reversecore_mcp/core/execution.py +240 -0
  20. reversecore_mcp/core/ghidra.py +642 -0
  21. reversecore_mcp/core/ghidra_helper.py +481 -0
  22. reversecore_mcp/core/ghidra_manager.py +234 -0
  23. reversecore_mcp/core/json_utils.py +131 -0
  24. reversecore_mcp/core/loader.py +73 -0
  25. reversecore_mcp/core/logging_config.py +206 -0
  26. reversecore_mcp/core/memory.py +721 -0
  27. reversecore_mcp/core/metrics.py +198 -0
  28. reversecore_mcp/core/mitre_mapper.py +365 -0
  29. reversecore_mcp/core/plugin.py +45 -0
  30. reversecore_mcp/core/r2_helpers.py +404 -0
  31. reversecore_mcp/core/r2_pool.py +403 -0
  32. reversecore_mcp/core/report_generator.py +268 -0
  33. reversecore_mcp/core/resilience.py +252 -0
  34. reversecore_mcp/core/resource_manager.py +169 -0
  35. reversecore_mcp/core/result.py +132 -0
  36. reversecore_mcp/core/security.py +213 -0
  37. reversecore_mcp/core/validators.py +238 -0
  38. reversecore_mcp/dashboard/__init__.py +221 -0
  39. reversecore_mcp/prompts/__init__.py +56 -0
  40. reversecore_mcp/prompts/common.py +24 -0
  41. reversecore_mcp/prompts/game.py +280 -0
  42. reversecore_mcp/prompts/malware.py +1219 -0
  43. reversecore_mcp/prompts/report.py +150 -0
  44. reversecore_mcp/prompts/security.py +136 -0
  45. reversecore_mcp/resources.py +329 -0
  46. reversecore_mcp/server.py +727 -0
  47. reversecore_mcp/tools/__init__.py +49 -0
  48. reversecore_mcp/tools/analysis/__init__.py +74 -0
  49. reversecore_mcp/tools/analysis/capa_tools.py +215 -0
  50. reversecore_mcp/tools/analysis/die_tools.py +180 -0
  51. reversecore_mcp/tools/analysis/diff_tools.py +643 -0
  52. reversecore_mcp/tools/analysis/lief_tools.py +272 -0
  53. reversecore_mcp/tools/analysis/signature_tools.py +591 -0
  54. reversecore_mcp/tools/analysis/static_analysis.py +479 -0
  55. reversecore_mcp/tools/common/__init__.py +58 -0
  56. reversecore_mcp/tools/common/file_operations.py +352 -0
  57. reversecore_mcp/tools/common/memory_tools.py +516 -0
  58. reversecore_mcp/tools/common/patch_explainer.py +230 -0
  59. reversecore_mcp/tools/common/server_tools.py +115 -0
  60. reversecore_mcp/tools/ghidra/__init__.py +19 -0
  61. reversecore_mcp/tools/ghidra/decompilation.py +975 -0
  62. reversecore_mcp/tools/ghidra/ghidra_tools.py +1052 -0
  63. reversecore_mcp/tools/malware/__init__.py +61 -0
  64. reversecore_mcp/tools/malware/adaptive_vaccine.py +579 -0
  65. reversecore_mcp/tools/malware/dormant_detector.py +756 -0
  66. reversecore_mcp/tools/malware/ioc_tools.py +228 -0
  67. reversecore_mcp/tools/malware/vulnerability_hunter.py +519 -0
  68. reversecore_mcp/tools/malware/yara_tools.py +214 -0
  69. reversecore_mcp/tools/patch_explainer.py +19 -0
  70. reversecore_mcp/tools/radare2/__init__.py +13 -0
  71. reversecore_mcp/tools/radare2/r2_analysis.py +972 -0
  72. reversecore_mcp/tools/radare2/r2_session.py +376 -0
  73. reversecore_mcp/tools/radare2/radare2_mcp_tools.py +1183 -0
  74. reversecore_mcp/tools/report/__init__.py +4 -0
  75. reversecore_mcp/tools/report/email.py +82 -0
  76. reversecore_mcp/tools/report/report_mcp_tools.py +344 -0
  77. reversecore_mcp/tools/report/report_tools.py +1076 -0
  78. reversecore_mcp/tools/report/session.py +194 -0
  79. reversecore_mcp/tools/report_tools.py +11 -0
@@ -0,0 +1,479 @@
1
+ """Static analysis tools for extracting strings, scanning for versions, and detecting embedded content."""
2
+
3
+ import os
4
+ import re
5
+ import tempfile
6
+
7
+ from reversecore_mcp.core.config import get_config
8
+ from reversecore_mcp.core.decorators import log_execution
9
+ from reversecore_mcp.core.error_handling import handle_tool_errors
10
+ from reversecore_mcp.core.execution import execute_subprocess_async
11
+ from reversecore_mcp.core.metrics import track_metrics
12
+ from reversecore_mcp.core.result import ToolResult, success
13
+ from reversecore_mcp.core.security import validate_file_path
14
+ from reversecore_mcp.core.validators import validate_tool_parameters
15
+
16
+ # Load default timeout from configuration
17
+ DEFAULT_TIMEOUT = get_config().default_tool_timeout
18
+
19
+ # Output size limits
20
+ MIN_OUTPUT_SIZE = 1024 * 1024 # 1MB - minimum output size for meaningful analysis
21
+ LLM_SAFE_LIMIT = 50 * 1024 # 50KB - roughly 12-15k tokens, safe for most LLMs
22
+ MAX_EXTRACTED_FILES = 200 # Maximum files to report in extraction results
23
+ MAX_SIGNATURES = 50 # Maximum signatures to report
24
+
25
+ # Pre-compile regex patterns for performance optimization
26
+ _VERSION_PATTERNS = {
27
+ "OpenSSL": re.compile(r"(OpenSSL|openssl)\s+(\d+\.\d+\.\d+[a-z]?)", re.IGNORECASE),
28
+ "GCC": re.compile(r"GCC:\s+\(.*\)\s+(\d+\.\d+\.\d+)"),
29
+ "Python": re.compile(r"(Python|python)\s+([23]\.\d+\.\d+)", re.IGNORECASE),
30
+ "Curl": re.compile(r"curl\s+(\d+\.\d+\.\d+)", re.IGNORECASE),
31
+ "BusyBox": re.compile(r"BusyBox\s+v(\d+\.\d+\.\d+)", re.IGNORECASE),
32
+ "Generic_Version": re.compile(r"[vV]er(?:sion)?\s?[:.]?\s?(\d+\.\d+\.\d+)"),
33
+ "Copyright": re.compile(r"Copyright.*(19|20)\d{2}"),
34
+ }
35
+
36
+ # Pre-compile RTTI detection patterns for performance optimization
37
+ # These patterns are used in extract_rtti_info to identify C++ type information
38
+ _RTTI_MAIN_PATTERN = re.compile(r"(_ZTS|_ZTI|_ZTV|\.?\?A[VUW]|class\s+\w+|struct\s+\w+)")
39
+
40
+ # Patterns for extracting class names from various RTTI formats
41
+ _RTTI_CLASS_PATTERNS = (
42
+ re.compile(r"(?:class|struct)\s+(\w+(?:::\w+)*)"), # class Foo, struct Bar::Baz
43
+ re.compile(r"\.?\?AV(\w+)@@"), # MSVC class: .?AVClassName@@
44
+ re.compile(r"\.?\?AU(\w+)@@"), # MSVC struct: .?AUStructName@@
45
+ re.compile(r"_ZTS(\d+)(\w+)"), # GCC typeinfo: _ZTS4Foo -> Foo (length prefixed)
46
+ re.compile(
47
+ r"(\w{2,}(?:Actor|Component|Manager|Controller|Handler|Service|Factory|Provider|Interface))"
48
+ ), # Common OOP patterns
49
+ re.compile(r"(C[a-z][A-Z]\w{3,})"), # Hungarian notation: CzCharacter, CxMonster
50
+ )
51
+
52
+
53
+ @log_execution(tool_name="run_strings")
54
+ @track_metrics("run_strings")
55
+ @handle_tool_errors
56
+ async def run_strings(
57
+ file_path: str,
58
+ min_length: int = 10, # Increased default from 4 to 10 to reduce noise and memory usage
59
+ max_output_size: int = 2_000_000, # Reduced default to 2MB for safety
60
+ timeout: int = DEFAULT_TIMEOUT,
61
+ ) -> ToolResult:
62
+ """Extract printable strings using the ``strings`` CLI."""
63
+
64
+ validate_tool_parameters(
65
+ "run_strings",
66
+ {"min_length": min_length, "max_output_size": max_output_size},
67
+ )
68
+
69
+ # Enforce strict output limits
70
+ if max_output_size > 10_000_000:
71
+ max_output_size = 10_000_000 # Cap at 10MB hard limit
72
+
73
+ # Enforce a reasonable minimum output size to prevent accidental truncation
74
+ if max_output_size < MIN_OUTPUT_SIZE:
75
+ max_output_size = MIN_OUTPUT_SIZE
76
+
77
+ validated_path = validate_file_path(file_path)
78
+
79
+ # Use -n option to filter short strings at source
80
+ cmd = ["strings", "-n", str(min_length), str(validated_path)]
81
+
82
+ # Use execute_subprocess_async which now has robust streaming and memory limits
83
+ output, bytes_read = await execute_subprocess_async(
84
+ cmd,
85
+ max_output_size=max_output_size,
86
+ timeout=timeout,
87
+ )
88
+
89
+ # Truncate output logic enhanced with file saving
90
+ truncated = False
91
+ output_files = {}
92
+
93
+ # Calculate statistics
94
+ text_output = output
95
+ lines = text_output.splitlines()
96
+ count = len(lines)
97
+
98
+ if len(output) > LLM_SAFE_LIMIT:
99
+ truncated = True
100
+ # Save full output to temp file (NOT source directory)
101
+ # This avoids: read-only mount failures, race conditions, leftover files
102
+ import tempfile
103
+ strings_filename = f"{validated_path.name}_strings.txt"
104
+
105
+ try:
106
+ # Use system temp directory for output files
107
+ with tempfile.NamedTemporaryFile(
108
+ mode="w",
109
+ suffix="_strings.txt",
110
+ prefix=f"{validated_path.stem}_",
111
+ delete=False, # Keep file so user can access it
112
+ encoding="utf-8",
113
+ ) as f:
114
+ f.write(text_output)
115
+ strings_path = f.name
116
+
117
+ output_files["full_output"] = strings_path
118
+
119
+ # Create preview
120
+ preview_limit = min(2000, len(text_output)) # First 2000 chars
121
+ preview_text = text_output[:preview_limit] + f"\n... (truncated, full content in {strings_path})"
122
+
123
+ return success(
124
+ preview_text,
125
+ bytes_read=bytes_read,
126
+ truncated=True,
127
+ string_statistics={
128
+ "count": count,
129
+ "preview": lines[:50], # First 50 lines list
130
+ "file_path": strings_path,
131
+ "full_size": len(text_output)
132
+ }
133
+ )
134
+ except Exception as e:
135
+ # Fallback if file write fails
136
+ truncated_output = output[:LLM_SAFE_LIMIT]
137
+ return success(
138
+ truncated_output + f"\n[Error saving file: {e}]",
139
+ bytes_read=bytes_read,
140
+ truncated=True
141
+ )
142
+
143
+ return success(
144
+ output,
145
+ bytes_read=bytes_read,
146
+ string_statistics={
147
+ "count": count,
148
+ "preview": lines[:50],
149
+ "full_size": len(text_output)
150
+ }
151
+ )
152
+
153
+
154
+ @log_execution(tool_name="run_binwalk")
155
+ @track_metrics("run_binwalk")
156
+ @handle_tool_errors
157
+ async def run_binwalk(
158
+ file_path: str,
159
+ depth: int = 8,
160
+ max_output_size: int = 10_000_000,
161
+ timeout: int = DEFAULT_TIMEOUT,
162
+ ) -> ToolResult:
163
+ """Analyze binaries for embedded content using binwalk."""
164
+
165
+ validated_path = validate_file_path(file_path)
166
+ cmd = ["binwalk", "-A", "-d", str(depth), str(validated_path)]
167
+ output, bytes_read = await execute_subprocess_async(
168
+ cmd,
169
+ max_output_size=max_output_size,
170
+ timeout=timeout,
171
+ )
172
+ return success(output, bytes_read=bytes_read)
173
+
174
+
175
+ @log_execution(tool_name="run_binwalk_extract")
176
+ @track_metrics("run_binwalk_extract")
177
+ @handle_tool_errors
178
+ async def run_binwalk_extract(
179
+ file_path: str,
180
+ output_dir: str = None,
181
+ matryoshka: bool = True,
182
+ depth: int = 8,
183
+ max_output_size: int = 50_000_000,
184
+ timeout: int = 600,
185
+ ) -> ToolResult:
186
+ """
187
+ Extract embedded files and file systems from a binary using binwalk.
188
+
189
+ This tool performs deep extraction of embedded content, including:
190
+ - Compressed archives (gzip, bzip2, lzma, xz)
191
+ - File systems (squashfs, cramfs, jffs2, ubifs)
192
+ - Firmware images and bootloaders
193
+ - Nested/matryoshka content (files within files)
194
+
195
+ **Use Cases:**
196
+ - **Firmware Analysis**: Extract file systems from router/IoT firmware
197
+ - **Malware Unpacking**: Extract payloads from packed/embedded malware
198
+ - **Forensics**: Recover embedded files from disk images
199
+ - **CTF Challenges**: Extract hidden data from challenge files
200
+
201
+ Args:
202
+ file_path: Path to the binary file to extract
203
+ output_dir: Directory to extract files to (default: creates temp dir)
204
+ matryoshka: Enable recursive extraction (files within files)
205
+ depth: Maximum extraction depth for nested content (default: 8)
206
+ max_output_size: Maximum output size in bytes
207
+ timeout: Extraction timeout in seconds (default: 600 for large files)
208
+
209
+ Returns:
210
+ ToolResult with extraction summary including:
211
+ - extracted_files: List of extracted files with paths and types
212
+ - output_directory: Path to extraction output
213
+ - total_size: Total size of extracted content
214
+ - extraction_depth: Maximum depth reached during extraction
215
+
216
+ Example:
217
+ >>> result = await run_binwalk_extract("/path/to/firmware.bin")
218
+ >>> print(result.data["extracted_files"])
219
+ [{"path": "squashfs-root/etc/passwd", "type": "ASCII text", "size": 1234}, ...]
220
+ """
221
+ from pathlib import Path
222
+
223
+ validated_path = validate_file_path(file_path)
224
+
225
+ # Create output directory if not specified
226
+ if output_dir is None:
227
+ # Create temp directory for extraction
228
+ temp_dir = tempfile.mkdtemp(prefix="binwalk_extract_")
229
+ extraction_dir = temp_dir
230
+ else:
231
+ # Resolve output directory path (may not exist yet)
232
+ from pathlib import Path
233
+
234
+ output_path = Path(output_dir).resolve()
235
+ extraction_dir = str(output_path)
236
+ os.makedirs(extraction_dir, exist_ok=True)
237
+
238
+ # Build binwalk extraction command
239
+ cmd = ["binwalk", "-e"] # -e for extraction
240
+
241
+ if matryoshka:
242
+ cmd.append("-M") # Matryoshka/recursive extraction
243
+
244
+ cmd.extend(["-d", str(depth)]) # Extraction depth
245
+ cmd.extend(["-C", str(extraction_dir)]) # Output directory
246
+ cmd.append(str(validated_path))
247
+
248
+ # Run extraction
249
+ output, bytes_read = await execute_subprocess_async(
250
+ cmd,
251
+ max_output_size=max_output_size,
252
+ timeout=timeout,
253
+ )
254
+
255
+ # Gather extraction results
256
+ extracted_files = []
257
+ total_size = 0
258
+ max_depth_found = 0
259
+
260
+ # Walk the extraction directory to catalog results
261
+ extraction_path = Path(extraction_dir)
262
+ if extraction_path.exists():
263
+ for root, _dirs, files in os.walk(extraction_path):
264
+ # Calculate depth from extraction root
265
+ rel_path = Path(root).relative_to(extraction_path)
266
+ current_depth = len(rel_path.parts)
267
+ max_depth_found = max(max_depth_found, current_depth)
268
+
269
+ for filename in files:
270
+ file_full_path = Path(root) / filename
271
+ try:
272
+ file_size = file_full_path.stat().st_size
273
+ total_size += file_size
274
+
275
+ # Try to determine file type
276
+ file_type = "unknown"
277
+ try:
278
+ # Use 'file' command for type detection
279
+ type_cmd = ["file", "-b", str(file_full_path)]
280
+ type_output, _ = await execute_subprocess_async(
281
+ type_cmd, timeout=5, max_output_size=1024
282
+ )
283
+ file_type = type_output.strip()[:100] # Limit type string length
284
+ except (OSError, TimeoutError):
285
+ # file command failed or timed out, use default "unknown"
286
+ file_type = "unknown"
287
+
288
+ extracted_files.append(
289
+ {
290
+ "path": str(file_full_path.relative_to(extraction_path)),
291
+ "type": file_type,
292
+ "size": file_size,
293
+ }
294
+ )
295
+ except (OSError, ValueError):
296
+ continue
297
+
298
+ # Sort by size (largest first) and limit entries
299
+ extracted_files.sort(key=lambda x: x["size"], reverse=True)
300
+ truncated = len(extracted_files) > MAX_EXTRACTED_FILES
301
+ extracted_files = extracted_files[:MAX_EXTRACTED_FILES]
302
+
303
+ # Parse binwalk output for additional info
304
+ signatures_found = []
305
+ for line in output.split("\n"):
306
+ line = line.strip()
307
+ if line and not line.startswith("DECIMAL") and not line.startswith("-"):
308
+ # Extract signature type from binwalk output
309
+ parts = line.split()
310
+ if len(parts) >= 3:
311
+ try:
312
+ offset = int(parts[0])
313
+ sig_type = " ".join(parts[2:])
314
+ signatures_found.append({"offset": offset, "type": sig_type[:100]})
315
+ except (ValueError, IndexError):
316
+ continue
317
+
318
+ return success(
319
+ {
320
+ "output_directory": str(extraction_dir),
321
+ "extracted_files": extracted_files,
322
+ "total_files": len(extracted_files)
323
+ + (100 if truncated else 0), # Estimate if truncated
324
+ "total_size": total_size,
325
+ "total_size_human": _format_size(total_size),
326
+ "extraction_depth": max_depth_found,
327
+ "signatures_found": signatures_found[:MAX_SIGNATURES],
328
+ "binwalk_output": output[:5000] if len(output) > 5000 else output,
329
+ "truncated": truncated,
330
+ },
331
+ bytes_read=bytes_read,
332
+ description=f"Extracted {len(extracted_files)} files ({_format_size(total_size)}) to {extraction_dir}",
333
+ )
334
+
335
+
336
+ def _format_size(size_bytes: int) -> str:
337
+ """Format byte size to human-readable string."""
338
+ for unit in ["B", "KB", "MB", "GB"]:
339
+ if size_bytes < 1024:
340
+ return f"{size_bytes:.1f} {unit}"
341
+ size_bytes /= 1024
342
+ return f"{size_bytes:.1f} TB"
343
+
344
+
345
+ @log_execution(tool_name="scan_for_versions")
346
+ @track_metrics("scan_for_versions")
347
+ @handle_tool_errors
348
+ async def scan_for_versions(
349
+ file_path: str,
350
+ timeout: int = DEFAULT_TIMEOUT,
351
+ ) -> ToolResult:
352
+ """
353
+ Extract library version strings and CVE clues from a binary.
354
+
355
+ This tool acts as a "Version Detective", scanning the binary for strings that
356
+ look like version numbers or library identifiers (e.g., "OpenSSL 1.0.2g",
357
+ "GCC 5.4.0"). It helps identify outdated components and potential CVEs.
358
+
359
+ **Use Cases:**
360
+ - **SCA (Software Composition Analysis)**: Identify open source components
361
+ - **Vulnerability Scanning**: Find outdated libraries (e.g., Heartbleed-vulnerable OpenSSL)
362
+ - **Firmware Analysis**: Determine OS and toolchain versions
363
+
364
+ Args:
365
+ file_path: Path to the binary file
366
+ timeout: Execution timeout in seconds
367
+
368
+ Returns:
369
+ ToolResult with detected libraries and versions.
370
+ """
371
+ validated_path = validate_file_path(file_path)
372
+
373
+ # Run strings command
374
+ cmd = ["strings", str(validated_path)]
375
+ output, bytes_read = await execute_subprocess_async(
376
+ cmd,
377
+ max_output_size=10_000_000,
378
+ timeout=timeout,
379
+ )
380
+
381
+ text = output
382
+
383
+ # Use pre-compiled patterns for better performance
384
+ detected = {}
385
+
386
+ # Process all version patterns
387
+ for name, pattern in _VERSION_PATTERNS.items():
388
+ matches = []
389
+ for match in pattern.finditer(text):
390
+ # Extract version from appropriate group (1 or 2 depending on pattern)
391
+ if name in ["OpenSSL", "Python"]:
392
+ matches.append(match.group(2))
393
+ else:
394
+ matches.append(match.group(1))
395
+ if matches:
396
+ detected[name] = list(set(matches))
397
+
398
+ return success(
399
+ detected,
400
+ bytes_read=bytes_read,
401
+ description=f"Detected {len(detected)} potential library versions",
402
+ )
403
+
404
+
405
+ @log_execution(tool_name="extract_rtti_info")
406
+ @track_metrics("extract_rtti_info")
407
+ @handle_tool_errors
408
+ async def extract_rtti_info(
409
+ file_path: str,
410
+ timeout: int = DEFAULT_TIMEOUT,
411
+ ) -> ToolResult:
412
+ """
413
+ Extract RTTI (Run-Time Type Information) from C++ binaries.
414
+
415
+ RTTI provides class names and inheritance hierarchies in C++ binaries,
416
+ which is invaluable for understanding object-oriented malware and game clients.
417
+
418
+ Args:
419
+ file_path: Path to the binary file
420
+ timeout: Execution timeout in seconds
421
+
422
+ Returns:
423
+ ToolResult with extracted class names and type information
424
+ """
425
+ validated_path = validate_file_path(file_path)
426
+
427
+ # Use strings with C++ demangling to extract RTTI
428
+ # Look for typeinfo names which start with _ZTS (type string)
429
+ cmd = ["strings", str(validated_path)]
430
+ output, bytes_read = await execute_subprocess_async(
431
+ cmd,
432
+ max_output_size=10_000_000,
433
+ timeout=timeout,
434
+ )
435
+
436
+ # Use pre-compiled module-level patterns for better performance
437
+ # These patterns are compiled once at module load time, avoiding the overhead
438
+ # of regex compilation on each function call
439
+
440
+ rtti_strings = []
441
+ class_names = set()
442
+
443
+ for line in output.split("\n"):
444
+ line_stripped = line.strip()
445
+ if _RTTI_MAIN_PATTERN.search(line_stripped):
446
+ rtti_strings.append(line_stripped)
447
+
448
+ # Try all patterns to extract class names
449
+ for pattern in _RTTI_CLASS_PATTERNS:
450
+ matches = pattern.findall(line_stripped)
451
+ for match in matches:
452
+ # Handle tuple results from patterns with groups
453
+ if isinstance(match, tuple):
454
+ class_name = match[-1] # Take the last group (usually the name)
455
+ else:
456
+ class_name = match
457
+
458
+ # Filter out noise (too short, all caps, numbers only)
459
+ if (
460
+ len(class_name) > 2
461
+ and not class_name.isupper()
462
+ and not class_name.isdigit()
463
+ ):
464
+ class_names.add(class_name)
465
+
466
+ return success(
467
+ {
468
+ "rtti_strings": rtti_strings[:200], # Limit to first 200
469
+ "class_names": sorted(class_names), # sorted() accepts any iterable
470
+ "total_rtti_entries": len(rtti_strings),
471
+ "total_classes": len(class_names),
472
+ },
473
+ bytes_read=bytes_read,
474
+ description=f"Extracted {len(class_names)} C++ class names from RTTI",
475
+ )
476
+
477
+
478
+ # Note: StaticAnalysisPlugin has been removed.
479
+ # The static analysis tools are now registered via AnalysisToolsPlugin in analysis/__init__.py.
@@ -0,0 +1,58 @@
1
+ """Common utility tools package.
2
+
3
+ Provides a unified CommonToolsPlugin that registers all common utility tools.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from reversecore_mcp.core.logging_config import get_logger
9
+ from reversecore_mcp.core.plugin import Plugin
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ class CommonToolsPlugin(Plugin):
15
+ """Unified plugin for all common utility tools."""
16
+
17
+ @property
18
+ def name(self) -> str:
19
+ return "common_tools"
20
+
21
+ @property
22
+ def description(self) -> str:
23
+ return "Unified common tools including memory management, server monitoring, file operations, and patch analysis."
24
+
25
+ def register(self, mcp_server: Any) -> None:
26
+ """Register all common tools."""
27
+ # Import and delegate to specialized plugins
28
+ from reversecore_mcp.tools.common.memory_tools import MemoryToolsPlugin
29
+ from reversecore_mcp.tools.common.server_tools import ServerToolsPlugin
30
+ from reversecore_mcp.tools.common.file_operations import (
31
+ run_file,
32
+ copy_to_workspace,
33
+ list_workspace,
34
+ scan_workspace,
35
+ )
36
+ from reversecore_mcp.tools.common.patch_explainer import explain_patch
37
+
38
+ # Register memory tools (plugin handles internal registration)
39
+ memory_plugin = MemoryToolsPlugin()
40
+ memory_plugin.register(mcp_server)
41
+
42
+ # Register server tools (plugin handles internal registration)
43
+ server_plugin = ServerToolsPlugin()
44
+ server_plugin.register(mcp_server)
45
+
46
+ # File operation tools
47
+ mcp_server.tool(run_file)
48
+ mcp_server.tool(copy_to_workspace)
49
+ mcp_server.tool(list_workspace)
50
+ mcp_server.tool(scan_workspace)
51
+
52
+ # Patch explainer
53
+ mcp_server.tool(explain_patch)
54
+
55
+ logger.info(f"Registered {self.name} plugin with common utilities (unified)")
56
+
57
+
58
+ __all__ = ["CommonToolsPlugin"]