Skip to content

Commit 4ed55d9

Browse files
author
Mark Solly
committed
Limit response line length in search_code_advanced
1 parent a6a4e4a commit 4ed55d9

File tree

8 files changed

+52
-22
lines changed

8 files changed

+52
-22
lines changed

src/code_index_mcp/search/ag.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def search(
2727
context_lines: int = 0,
2828
file_pattern: Optional[str] = None,
2929
fuzzy: bool = False,
30-
regex: bool = False
30+
regex: bool = False,
31+
max_line_length: Optional[int] = None
3132
) -> Dict[str, List[Tuple[int, str]]]:
3233
"""
3334
Execute a search using The Silver Searcher (ag).
@@ -40,6 +41,7 @@ def search(
4041
file_pattern: File pattern to filter
4142
fuzzy: Enable word boundary matching (not true fuzzy search)
4243
regex: Enable regex pattern matching
44+
max_line_length: Optional. Limit the length of lines when context_lines is used
4345
"""
4446
# ag prints line numbers and groups by file by default, which is good.
4547
# --noheading is used to be consistent with other tools' output format.
@@ -116,10 +118,10 @@ def search(
116118
if process.returncode > 1:
117119
raise RuntimeError(f"ag failed with exit code {process.returncode}: {process.stderr}")
118120

119-
return parse_search_output(process.stdout, base_path)
121+
return parse_search_output(process.stdout, base_path, max_line_length)
120122

121123
except FileNotFoundError:
122124
raise RuntimeError("'ag' (The Silver Searcher) not found. Please install it and ensure it's in your PATH.")
123125
except Exception as e:
124126
# Re-raise other potential exceptions like permission errors
125-
raise RuntimeError(f"An error occurred while running ag: {e}")
127+
raise RuntimeError(f"An error occurred while running ag: {e}")

src/code_index_mcp/search/base.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414

1515
from ..indexing.qualified_names import normalize_file_path
1616

17-
def parse_search_output(output: str, base_path: str) -> Dict[str, List[Tuple[int, str]]]:
17+
def parse_search_output(
18+
output: str,
19+
base_path: str,
20+
max_line_length: Optional[int] = None
21+
) -> Dict[str, List[Tuple[int, str]]]:
1822
"""
1923
Parse the output of command-line search tools (grep, ag, rg).
2024
2125
Args:
2226
output: The raw output from the command-line tool.
2327
base_path: The base path of the project to make file paths relative.
28+
max_line_length: Optional maximum line length to truncate long lines.
2429
2530
Returns:
2631
A dictionary where keys are file paths and values are lists of (line_number, line_content) tuples.
@@ -53,6 +58,10 @@ def parse_search_output(output: str, base_path: str) -> Dict[str, List[Tuple[int
5358
# Normalize path separators for consistency
5459
relative_path = normalize_file_path(relative_path)
5560

61+
# Truncate content if it exceeds max_line_length
62+
if max_line_length and len(content) > max_line_length:
63+
content = content[:max_line_length] + '... (truncated)'
64+
5665
if relative_path not in results:
5766
results[relative_path] = []
5867
results[relative_path].append((line_number, content))
@@ -175,7 +184,8 @@ def search(
175184
context_lines: int = 0,
176185
file_pattern: Optional[str] = None,
177186
fuzzy: bool = False,
178-
regex: bool = False
187+
regex: bool = False,
188+
max_line_length: Optional[int] = None
179189
) -> Dict[str, List[Tuple[int, str]]]:
180190
"""
181191
Execute a search using the specific strategy.
@@ -193,4 +203,3 @@ def search(
193203
A dictionary mapping filenames to lists of (line_number, line_content) tuples.
194204
"""
195205
pass
196-

src/code_index_mcp/search/basic.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def search(
4646
context_lines: int = 0,
4747
file_pattern: Optional[str] = None,
4848
fuzzy: bool = False,
49-
regex: bool = False
49+
regex: bool = False,
50+
max_line_length: Optional[int] = None
5051
) -> Dict[str, List[Tuple[int, str]]]:
5152
"""
5253
Execute a basic, line-by-line search.
@@ -60,6 +61,7 @@ def search(
6061
file_pattern: File pattern to filter
6162
fuzzy: Enable word boundary matching
6263
regex: Enable regex pattern matching
64+
max_line_length: Optional. Limit the length of lines when context_lines is used
6365
"""
6466
results: Dict[str, List[Tuple[int, str]]] = {}
6567

@@ -94,15 +96,20 @@ def search(
9496
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
9597
for line_num, line in enumerate(f, 1):
9698
if search_regex.search(line):
99+
content = line.rstrip('\n')
100+
# Truncate content if it exceeds max_line_length
101+
if max_line_length and len(content) > max_line_length:
102+
content = content[:max_line_length] + '... (truncated)'
103+
97104
if rel_path not in results:
98105
results[rel_path] = []
99106
# Strip newline for consistent output
100-
results[rel_path].append((line_num, line.rstrip('\n')))
107+
results[rel_path].append((line_num, content))
101108
except (UnicodeDecodeError, PermissionError, OSError):
102109
# Ignore files that can't be opened or read due to encoding/permission issues
103110
continue
104111
except Exception:
105112
# Ignore any other unexpected exceptions to maintain robustness
106113
continue
107114

108-
return results
115+
return results

src/code_index_mcp/search/grep.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def search(
3232
context_lines: int = 0,
3333
file_pattern: Optional[str] = None,
3434
fuzzy: bool = False,
35-
regex: bool = False
35+
regex: bool = False,
36+
max_line_length: Optional[int] = None
3637
) -> Dict[str, List[Tuple[int, str]]]:
3738
"""
3839
Execute a search using standard grep.
@@ -45,6 +46,7 @@ def search(
4546
file_pattern: File pattern to filter
4647
fuzzy: Enable word boundary matching
4748
regex: Enable regex pattern matching
49+
max_line_length: Optional. Limit the length of lines when context_lines is used
4850
"""
4951
# -r: recursive, -n: line number
5052
cmd = ['grep', '-r', '-n']
@@ -102,9 +104,9 @@ def search(
102104
if process.returncode > 1:
103105
raise RuntimeError(f"grep failed with exit code {process.returncode}: {process.stderr}")
104106

105-
return parse_search_output(process.stdout, base_path)
107+
return parse_search_output(process.stdout, base_path, max_line_length)
106108

107109
except FileNotFoundError:
108110
raise RuntimeError("'grep' not found. Please install it and ensure it's in your PATH.")
109111
except Exception as e:
110-
raise RuntimeError(f"An error occurred while running grep: {e}")
112+
raise RuntimeError(f"An error occurred while running grep: {e}")

src/code_index_mcp/search/ripgrep.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def search(
2727
context_lines: int = 0,
2828
file_pattern: Optional[str] = None,
2929
fuzzy: bool = False,
30-
regex: bool = False
30+
regex: bool = False,
31+
max_line_length: Optional[int] = None
3132
) -> Dict[str, List[Tuple[int, str]]]:
3233
"""
3334
Execute a search using ripgrep.
@@ -40,6 +41,7 @@ def search(
4041
file_pattern: File pattern to filter
4142
fuzzy: Enable word boundary matching (not true fuzzy search)
4243
regex: Enable regex pattern matching
44+
max_line_length: Optional. Limit the length of lines when context_lines is used
4345
"""
4446
cmd = ['rg', '--line-number', '--no-heading', '--color=never', '--no-ignore']
4547

@@ -87,10 +89,10 @@ def search(
8789
if process.returncode > 1:
8890
raise RuntimeError(f"ripgrep failed with exit code {process.returncode}: {process.stderr}")
8991

90-
return parse_search_output(process.stdout, base_path)
92+
return parse_search_output(process.stdout, base_path, max_line_length)
9193

9294
except FileNotFoundError:
9395
raise RuntimeError("ripgrep (rg) not found. Please install it and ensure it's in your PATH.")
9496
except Exception as e:
9597
# Re-raise other potential exceptions like permission errors
96-
raise RuntimeError(f"An error occurred while running ripgrep: {e}")
98+
raise RuntimeError(f"An error occurred while running ripgrep: {e}")

src/code_index_mcp/search/ugrep.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def search(
2727
context_lines: int = 0,
2828
file_pattern: Optional[str] = None,
2929
fuzzy: bool = False,
30-
regex: bool = False
30+
regex: bool = False,
31+
max_line_length: Optional[int] = None
3132
) -> Dict[str, List[Tuple[int, str]]]:
3233
"""
3334
Execute a search using the 'ug' command-line tool.
@@ -40,6 +41,7 @@ def search(
4041
file_pattern: File pattern to filter
4142
fuzzy: Enable true fuzzy search (ugrep native support)
4243
regex: Enable regex pattern matching
44+
max_line_length: Optional. Limit the length of lines when context_lines is used
4345
"""
4446
if not self.is_available():
4547
return {"error": "ugrep (ug) command not found."}
@@ -89,7 +91,7 @@ def search(
8991
error_output = process.stderr.strip()
9092
return {"error": f"ugrep execution failed with code {process.returncode}", "details": error_output}
9193

92-
return parse_search_output(process.stdout, base_path)
94+
return parse_search_output(process.stdout, base_path, max_line_length)
9395

9496
except FileNotFoundError:
9597
return {"error": "ugrep (ug) command not found. Please ensure it's installed and in your PATH."}

src/code_index_mcp/server.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ def search_code_advanced(
122122
context_lines: int = 0,
123123
file_pattern: str = None,
124124
fuzzy: bool = False,
125-
regex: bool = None
125+
regex: bool = None,
126+
max_line_length: int = 200
126127
) -> Dict[str, Any]:
127128
"""
128129
Search for a code pattern in the project using an advanced, fast tool.
@@ -136,6 +137,7 @@ def search_code_advanced(
136137
context_lines: Number of lines to show before and after the match.
137138
file_pattern: A glob pattern to filter files to search in
138139
(e.g., "*.py", "*.js", "test_*.py").
140+
max_line_length: Optional. Default 200. Limits the length of lines when context_lines is used.
139141
All search tools now handle glob patterns consistently:
140142
- ugrep: Uses glob patterns (*.py, *.{js,ts})
141143
- ripgrep: Uses glob patterns (*.py, *.{js,ts})
@@ -164,7 +166,8 @@ def search_code_advanced(
164166
context_lines=context_lines,
165167
file_pattern=file_pattern,
166168
fuzzy=fuzzy,
167-
regex=regex
169+
regex=regex,
170+
max_line_length=max_line_length
168171
)
169172

170173
@mcp.tool()

src/code_index_mcp/services/search_service.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def search_code( # pylint: disable=too-many-arguments
3131
context_lines: int = 0,
3232
file_pattern: Optional[str] = None,
3333
fuzzy: bool = False,
34-
regex: Optional[bool] = None
34+
regex: Optional[bool] = None,
35+
max_line_length: Optional[int] = 200
3536
) -> Dict[str, Any]:
3637
"""
3738
Search for code patterns in the project.
@@ -45,6 +46,7 @@ def search_code( # pylint: disable=too-many-arguments
4546
file_pattern: Glob pattern to filter files
4647
fuzzy: Whether to enable fuzzy matching
4748
regex: Regex mode - True/False to force, None for auto-detection
49+
max_line_length: Optional. Default 200. Limits the length of lines when context_lines is used.
4850
4951
Returns:
5052
Dictionary with search results or error information
@@ -89,7 +91,8 @@ def search_code( # pylint: disable=too-many-arguments
8991
context_lines=context_lines,
9092
file_pattern=file_pattern,
9193
fuzzy=fuzzy,
92-
regex=regex
94+
regex=regex,
95+
max_line_length=max_line_length
9396
)
9497
return ResponseFormatter.search_results_response(results)
9598
except Exception as e:
@@ -141,4 +144,4 @@ def get_search_capabilities(self) -> Dict[str, Any]:
141144
"supports_file_patterns": True
142145
}
143146

144-
return capabilities
147+
return capabilities

0 commit comments

Comments
 (0)