Skip to content

Commit b28e40d

Browse files
committed
Add maxResults parameter and return text results by default
To avoid errors like: Error: MCP tool "find_code" response (194537 tokens) exceeds maximum allowed tokens (25000). Please use pagination, filtering, or limit parameters to reduce the response size.
1 parent beeb6a3 commit b28e40d

File tree

2 files changed

+97
-12
lines changed

2 files changed

+97
-12
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ Test ast-grep YAML rules against code snippets before applying them to larger co
131131
### 🎯 `find_code`
132132
Search codebases using simple ast-grep patterns for straightforward structural matches.
133133

134+
**Parameters:**
135+
- `max_results`: Limit number of results (default: unlimited)
136+
- `output_format`: Choose between `"text"` (default, ~75% fewer tokens) or `"json"` (full metadata)
137+
134138
**Use cases:**
135139
- Find function calls with specific patterns
136140
- Locate variable declarations
@@ -139,6 +143,10 @@ Search codebases using simple ast-grep patterns for straightforward structural m
139143
### 🚀 `find_code_by_rule`
140144
Advanced codebase search using complex YAML rules that can express sophisticated matching criteria.
141145

146+
**Parameters:**
147+
- `max_results`: Limit number of results (default: unlimited)
148+
- `output_format`: Choose between `"text"` (default, ~75% fewer tokens) or `"json"` (full metadata)
149+
142150
**Use cases:**
143151
- Find nested code structures
144152
- Search with relational constraints (inside, has, precedes, follows)

main.py

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,38 +99,115 @@ def find_code(
9999
project_folder: str = Field(description="The absolute path to the project folder. It must be absolute path."),
100100
pattern: str = Field(description="The ast-grep pattern to search for. Note, the pattern must have valid AST structure."),
101101
language: str = Field(description="The language of the query", default=""),
102-
) -> List[dict[str, Any]]:
102+
max_results: Optional[int] = Field(default=None, description="Maximum results to return"),
103+
output_format: str = Field(default="text", description="'text' or 'json'"),
104+
) -> str | List[dict[str, Any]]:
103105
"""
104106
Find code in a project folder that matches the given ast-grep pattern.
105107
Pattern is good for simple and single-AST node result.
106108
For more complex usage, please use YAML by `find_code_by_rule`.
107109
108-
Internally calls: ast-grep run --pattern <pattern> --json <project_folder>
110+
Internally calls: ast-grep run --pattern <pattern> [--json] <project_folder>
111+
112+
Output formats:
113+
- text (default): Simple file:line:content format, ~75% fewer tokens
114+
- json: Full match objects with metadata
115+
116+
Example usage:
117+
find_code(pattern="class $NAME", max_results=20) # Returns text format
118+
find_code(pattern="class $NAME", output_format="json") # Returns JSON with metadata
109119
"""
110-
args = ["--pattern", pattern, "--json"]
120+
if output_format not in ["text", "json"]:
121+
raise ValueError(f"Invalid output_format: {output_format}. Must be 'text' or 'json'.")
122+
123+
args = ["--pattern", pattern]
111124
if language:
112125
args.extend(["--lang", language])
113-
args.append(project_folder)
114-
result = run_ast_grep("run", args)
115-
return json.loads(result.stdout)
126+
127+
if output_format == "json":
128+
# JSON format - return structured data
129+
result = run_ast_grep("run", args + ["--json", project_folder])
130+
matches = json.loads(result.stdout.strip() or "[]")
131+
# Limit results if max_results is specified
132+
if max_results is not None and len(matches) > max_results:
133+
matches = matches[:max_results]
134+
return matches
135+
else:
136+
# Text format - return plain text output
137+
result = run_ast_grep("run", args + [project_folder])
138+
output = result.stdout.strip()
139+
if not output:
140+
output = "No matches found"
141+
else:
142+
# Apply max_results limit if specified
143+
lines = output.split('\n')
144+
non_empty_lines = [line for line in lines if line.strip()]
145+
if max_results is not None and len(non_empty_lines) > max_results:
146+
# Limit the results
147+
non_empty_lines = non_empty_lines[:max_results]
148+
output = '\n'.join(non_empty_lines)
149+
header = f"Found {len(non_empty_lines)} matches (limited to {max_results}):\n"
150+
else:
151+
header = f"Found {len(non_empty_lines)} matches:\n"
152+
output = header + output
153+
return output
116154

117155
@mcp.tool()
118156
def find_code_by_rule(
119157
project_folder: str = Field(description="The absolute path to the project folder. It must be absolute path."),
120158
yaml: str = Field(description="The ast-grep YAML rule to search. It must have id, language, rule fields."),
121-
) -> List[dict[str, Any]]:
159+
max_results: Optional[int] = Field(default=None, description="Maximum results to return"),
160+
output_format: str = Field(default="text", description="'text' or 'json'"),
161+
) -> str | List[dict[str, Any]]:
122162
"""
123163
Find code using ast-grep's YAML rule in a project folder.
124164
YAML rule is more powerful than simple pattern and can perform complex search like find AST inside/having another AST.
125165
It is a more advanced search tool than the simple `find_code`.
126166
127167
Tip: When using relational rules (inside/has), add `stopBy: end` to ensure complete traversal.
128-
129-
Internally calls: ast-grep scan --inline-rules <yaml> --json <project_folder>
168+
169+
Internally calls: ast-grep scan --inline-rules <yaml> [--json] <project_folder>
170+
171+
Output formats:
172+
- text (default): Simple file:line:content format, ~75% fewer tokens
173+
- json: Full match objects with metadata
174+
175+
Example usage:
176+
find_code_by_rule(yaml="id: x\\nlanguage: python\\nrule: {pattern: 'class $NAME'}", max_results=20)
177+
find_code_by_rule(yaml="...", output_format="json") # For full metadata
130178
"""
131-
args = ["--inline-rules", yaml, "--json", project_folder]
132-
result = run_ast_grep("scan", args)
133-
return json.loads(result.stdout)
179+
if output_format not in ["text", "json"]:
180+
raise ValueError(f"Invalid output_format: {output_format}. Must be 'text' or 'json'.")
181+
182+
args = ["--inline-rules", yaml]
183+
184+
if output_format == "json":
185+
# JSON format - return structured data
186+
result = run_ast_grep("scan", args + ["--json", project_folder])
187+
matches = json.loads(result.stdout.strip() or "[]")
188+
# Limit results if max_results is specified
189+
if max_results is not None and len(matches) > max_results:
190+
matches = matches[:max_results]
191+
return matches
192+
else:
193+
# Text format - return plain text output
194+
result = run_ast_grep("scan", args + [project_folder])
195+
output = result.stdout.strip()
196+
if not output:
197+
output = "No matches found"
198+
else:
199+
# Apply max_results limit if specified
200+
lines = output.split('\n')
201+
non_empty_lines = [line for line in lines if line.strip()]
202+
if max_results is not None and len(non_empty_lines) > max_results:
203+
# Limit the results
204+
non_empty_lines = non_empty_lines[:max_results]
205+
output = '\n'.join(non_empty_lines)
206+
header = f"Found {len(non_empty_lines)} matches (limited to {max_results}):\n"
207+
else:
208+
header = f"Found {len(non_empty_lines)} matches:\n"
209+
output = header + output
210+
return output
134211

135212
def run_command(args: List[str], input_text: Optional[str] = None) -> subprocess.CompletedProcess:
136213
try:

0 commit comments

Comments
 (0)