iflow-mcp_kandrwmrtn-cplusplus_mcp 0.1.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.
- iflow_mcp_kandrwmrtn_cplusplus_mcp-0.1.0.dist-info/METADATA +222 -0
- iflow_mcp_kandrwmrtn_cplusplus_mcp-0.1.0.dist-info/RECORD +14 -0
- iflow_mcp_kandrwmrtn_cplusplus_mcp-0.1.0.dist-info/WHEEL +4 -0
- iflow_mcp_kandrwmrtn_cplusplus_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_kandrwmrtn_cplusplus_mcp-0.1.0.dist-info/licenses/LICENSE +21 -0
- mcp_server/__init__.py +1 -0
- mcp_server/cache_manager.py +212 -0
- mcp_server/call_graph.py +108 -0
- mcp_server/cpp_analyzer.py +1042 -0
- mcp_server/cpp_analyzer_config.py +112 -0
- mcp_server/cpp_mcp_server.py +1675 -0
- mcp_server/file_scanner.py +92 -0
- mcp_server/search_engine.py +131 -0
- mcp_server/symbol_info.py +42 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""File discovery and filtering for C++ projects."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Set
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FileScanner:
|
|
10
|
+
"""Handles file discovery and filtering for C++ projects."""
|
|
11
|
+
|
|
12
|
+
# C++ file extensions
|
|
13
|
+
CPP_EXTENSIONS = {'.cpp', '.cc', '.cxx', '.c++', '.h', '.hpp', '.hxx', '.h++'}
|
|
14
|
+
|
|
15
|
+
# Directories to exclude (set by configuration)
|
|
16
|
+
EXCLUDE_DIRS = set()
|
|
17
|
+
|
|
18
|
+
# Directories that contain dependencies (set by configuration)
|
|
19
|
+
DEPENDENCY_DIRS = set()
|
|
20
|
+
|
|
21
|
+
def __init__(self, project_root: Path, include_dependencies: bool = False):
|
|
22
|
+
self.project_root = project_root
|
|
23
|
+
self.include_dependencies = include_dependencies
|
|
24
|
+
|
|
25
|
+
def should_skip_directory(self, dir_path: str) -> bool:
|
|
26
|
+
"""Check if a directory should be skipped"""
|
|
27
|
+
# Only skip if this directory is directly under the project root
|
|
28
|
+
try:
|
|
29
|
+
rel_path = Path(dir_path).relative_to(self.project_root)
|
|
30
|
+
# If the relative path has no parent, it's a top-level directory
|
|
31
|
+
if len(rel_path.parts) == 1:
|
|
32
|
+
return rel_path.parts[0] in self.EXCLUDE_DIRS
|
|
33
|
+
except ValueError:
|
|
34
|
+
# Directory is outside project root
|
|
35
|
+
pass
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
def should_skip_file(self, file_path: str) -> bool:
|
|
39
|
+
"""Check if a file should be skipped during indexing"""
|
|
40
|
+
# Skip files outside project root (shouldn't happen, but safety check)
|
|
41
|
+
try:
|
|
42
|
+
rel_path = Path(file_path).relative_to(self.project_root)
|
|
43
|
+
except ValueError:
|
|
44
|
+
# File is outside project root
|
|
45
|
+
if not self.include_dependencies:
|
|
46
|
+
return True
|
|
47
|
+
else:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
# Check if file is in a top-level excluded directory
|
|
51
|
+
if len(rel_path.parts) > 0 and rel_path.parts[0] in self.EXCLUDE_DIRS:
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def find_cpp_files(self) -> List[str]:
|
|
57
|
+
"""Find all C++ files in the project"""
|
|
58
|
+
files = []
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
for root, dirs, filenames in os.walk(self.project_root):
|
|
62
|
+
# Filter directories in-place to prevent walking into them
|
|
63
|
+
dirs[:] = [d for d in dirs if not self.should_skip_directory(os.path.join(root, d))]
|
|
64
|
+
|
|
65
|
+
for filename in filenames:
|
|
66
|
+
if any(filename.endswith(ext) for ext in self.CPP_EXTENSIONS):
|
|
67
|
+
file_path = os.path.join(root, filename)
|
|
68
|
+
if not self.should_skip_file(file_path):
|
|
69
|
+
files.append(file_path)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f"Error scanning directory: {e}", file=sys.stderr)
|
|
72
|
+
|
|
73
|
+
return files
|
|
74
|
+
|
|
75
|
+
def is_project_file(self, file_path: str) -> bool:
|
|
76
|
+
"""Check if a file is part of the project (not a dependency)"""
|
|
77
|
+
if not file_path:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
# Check if file is under project root
|
|
81
|
+
try:
|
|
82
|
+
rel_path = Path(file_path).relative_to(self.project_root)
|
|
83
|
+
|
|
84
|
+
# Check if file is in a dependency directory (at any level)
|
|
85
|
+
for part in rel_path.parts:
|
|
86
|
+
if part in self.DEPENDENCY_DIRS:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return True
|
|
90
|
+
except ValueError:
|
|
91
|
+
# File is outside project root - it's a dependency
|
|
92
|
+
return False
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Search functionality for C++ symbols."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict, List, Optional, Any
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from .symbol_info import SymbolInfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SearchEngine:
|
|
10
|
+
"""Handles searching for C++ symbols."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, class_index: Dict[str, List[SymbolInfo]],
|
|
13
|
+
function_index: Dict[str, List[SymbolInfo]],
|
|
14
|
+
file_index: Dict[str, List[SymbolInfo]],
|
|
15
|
+
usr_index: Dict[str, SymbolInfo]):
|
|
16
|
+
self.class_index = class_index
|
|
17
|
+
self.function_index = function_index
|
|
18
|
+
self.file_index = file_index
|
|
19
|
+
self.usr_index = usr_index
|
|
20
|
+
|
|
21
|
+
def search_classes(self, pattern: str, project_only: bool = True) -> List[Dict[str, Any]]:
|
|
22
|
+
"""Search for classes matching a pattern"""
|
|
23
|
+
results = []
|
|
24
|
+
regex = re.compile(pattern, re.IGNORECASE)
|
|
25
|
+
|
|
26
|
+
for name, infos in self.class_index.items():
|
|
27
|
+
if regex.search(name):
|
|
28
|
+
for info in infos:
|
|
29
|
+
if not project_only or info.is_project:
|
|
30
|
+
results.append({
|
|
31
|
+
"name": info.name,
|
|
32
|
+
"kind": info.kind,
|
|
33
|
+
"file": info.file,
|
|
34
|
+
"line": info.line,
|
|
35
|
+
"is_project": info.is_project,
|
|
36
|
+
"base_classes": info.base_classes
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return results
|
|
40
|
+
|
|
41
|
+
def search_functions(self, pattern: str, project_only: bool = True,
|
|
42
|
+
class_name: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
43
|
+
"""Search for functions matching a pattern"""
|
|
44
|
+
results = []
|
|
45
|
+
regex = re.compile(pattern, re.IGNORECASE)
|
|
46
|
+
|
|
47
|
+
for name, infos in self.function_index.items():
|
|
48
|
+
if regex.search(name):
|
|
49
|
+
for info in infos:
|
|
50
|
+
if not project_only or info.is_project:
|
|
51
|
+
# Filter by class name if specified
|
|
52
|
+
if class_name and info.parent_class != class_name:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
results.append({
|
|
56
|
+
"name": info.name,
|
|
57
|
+
"kind": info.kind,
|
|
58
|
+
"file": info.file,
|
|
59
|
+
"line": info.line,
|
|
60
|
+
"signature": info.signature,
|
|
61
|
+
"is_project": info.is_project,
|
|
62
|
+
"parent_class": info.parent_class
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return results
|
|
66
|
+
|
|
67
|
+
def search_symbols(self, pattern: str, project_only: bool = True,
|
|
68
|
+
symbol_types: Optional[List[str]] = None) -> Dict[str, List[Dict[str, Any]]]:
|
|
69
|
+
"""Search for any symbols matching a pattern"""
|
|
70
|
+
results = {"classes": [], "functions": []}
|
|
71
|
+
|
|
72
|
+
# Filter symbol types
|
|
73
|
+
search_classes = not symbol_types or any(t in ["class", "struct"] for t in symbol_types)
|
|
74
|
+
search_functions = not symbol_types or any(t in ["function", "method"] for t in symbol_types)
|
|
75
|
+
|
|
76
|
+
if search_classes:
|
|
77
|
+
results["classes"] = self.search_classes(pattern, project_only)
|
|
78
|
+
|
|
79
|
+
if search_functions:
|
|
80
|
+
results["functions"] = self.search_functions(pattern, project_only)
|
|
81
|
+
|
|
82
|
+
return results
|
|
83
|
+
|
|
84
|
+
def get_symbols_in_file(self, file_path: str) -> List[SymbolInfo]:
|
|
85
|
+
"""Get all symbols in a specific file"""
|
|
86
|
+
return self.file_index.get(file_path, [])
|
|
87
|
+
|
|
88
|
+
def get_class_info(self, class_name: str) -> Optional[Dict[str, Any]]:
|
|
89
|
+
"""Get detailed information about a class"""
|
|
90
|
+
infos = self.class_index.get(class_name, [])
|
|
91
|
+
if not infos:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Return the first match (could be enhanced to handle multiple matches)
|
|
95
|
+
info = infos[0]
|
|
96
|
+
|
|
97
|
+
# Find all methods of this class
|
|
98
|
+
methods = []
|
|
99
|
+
for name, func_infos in self.function_index.items():
|
|
100
|
+
for func_info in func_infos:
|
|
101
|
+
if func_info.parent_class == class_name:
|
|
102
|
+
methods.append({
|
|
103
|
+
"name": func_info.name,
|
|
104
|
+
"signature": func_info.signature,
|
|
105
|
+
"access": func_info.access,
|
|
106
|
+
"line": func_info.line
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"name": info.name,
|
|
111
|
+
"kind": info.kind,
|
|
112
|
+
"file": info.file,
|
|
113
|
+
"line": info.line,
|
|
114
|
+
"base_classes": info.base_classes,
|
|
115
|
+
"methods": sorted(methods, key=lambda x: x["line"]),
|
|
116
|
+
"is_project": info.is_project
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def get_function_signature(self, function_name: str,
|
|
120
|
+
class_name: Optional[str] = None) -> List[str]:
|
|
121
|
+
"""Get function signatures matching the name"""
|
|
122
|
+
signatures = []
|
|
123
|
+
|
|
124
|
+
for info in self.function_index.get(function_name, []):
|
|
125
|
+
if class_name is None or info.parent_class == class_name:
|
|
126
|
+
if info.parent_class:
|
|
127
|
+
signatures.append(f"{info.parent_class}::{info.name}{info.signature}")
|
|
128
|
+
else:
|
|
129
|
+
signatures.append(f"{info.name}{info.signature}")
|
|
130
|
+
|
|
131
|
+
return signatures
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Symbol information data structure for C++ analysis."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SymbolInfo:
|
|
9
|
+
"""Information about a C++ symbol (class, function, etc.)"""
|
|
10
|
+
name: str
|
|
11
|
+
kind: str # "class", "function", "method", etc.
|
|
12
|
+
file: str
|
|
13
|
+
line: int
|
|
14
|
+
column: int
|
|
15
|
+
signature: str = ""
|
|
16
|
+
is_project: bool = True
|
|
17
|
+
namespace: str = ""
|
|
18
|
+
access: str = "public" # public, private, protected
|
|
19
|
+
parent_class: str = "" # For methods, the containing class
|
|
20
|
+
base_classes: List[str] = field(default_factory=list) # For classes
|
|
21
|
+
usr: str = "" # Unified Symbol Resolution - unique identifier
|
|
22
|
+
calls: List[str] = field(default_factory=list) # USRs of functions this function calls
|
|
23
|
+
called_by: List[str] = field(default_factory=list) # USRs of functions that call this
|
|
24
|
+
|
|
25
|
+
def to_dict(self):
|
|
26
|
+
"""Convert to dictionary for JSON serialization"""
|
|
27
|
+
return {
|
|
28
|
+
"name": self.name,
|
|
29
|
+
"kind": self.kind,
|
|
30
|
+
"file": self.file,
|
|
31
|
+
"line": self.line,
|
|
32
|
+
"column": self.column,
|
|
33
|
+
"signature": self.signature,
|
|
34
|
+
"is_project": self.is_project,
|
|
35
|
+
"namespace": self.namespace,
|
|
36
|
+
"access": self.access,
|
|
37
|
+
"parent_class": self.parent_class,
|
|
38
|
+
"base_classes": self.base_classes,
|
|
39
|
+
"usr": self.usr,
|
|
40
|
+
"calls": self.calls,
|
|
41
|
+
"called_by": self.called_by
|
|
42
|
+
}
|