@@ -99,38 +99,115 @@ def find_code(
99
99
project_folder : str = Field (description = "The absolute path to the project folder. It must be absolute path." ),
100
100
pattern : str = Field (description = "The ast-grep pattern to search for. Note, the pattern must have valid AST structure." ),
101
101
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 ]]:
103
105
"""
104
106
Find code in a project folder that matches the given ast-grep pattern.
105
107
Pattern is good for simple and single-AST node result.
106
108
For more complex usage, please use YAML by `find_code_by_rule`.
107
109
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
109
119
"""
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 ]
111
124
if language :
112
125
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
116
154
117
155
@mcp .tool ()
118
156
def find_code_by_rule (
119
157
project_folder : str = Field (description = "The absolute path to the project folder. It must be absolute path." ),
120
158
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 ]]:
122
162
"""
123
163
Find code using ast-grep's YAML rule in a project folder.
124
164
YAML rule is more powerful than simple pattern and can perform complex search like find AST inside/having another AST.
125
165
It is a more advanced search tool than the simple `find_code`.
126
166
127
167
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
130
178
"""
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
134
211
135
212
def run_command (args : List [str ], input_text : Optional [str ] = None ) -> subprocess .CompletedProcess :
136
213
try :
0 commit comments