@probelabs/probe 0.6.0-rc121 → 0.6.0-rc123

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.
@@ -8,7 +8,7 @@ import path from 'path';
8
8
  import fs from 'fs-extra';
9
9
  import { fileURLToPath } from 'url';
10
10
  // Import from parent package
11
- import { search, extract } from '../index.js';
11
+ import { search, extract, grep } from '../index.js';
12
12
  // Parse command-line arguments
13
13
  function parseArgs() {
14
14
  const args = process.argv.slice(2);
@@ -39,7 +39,7 @@ Usage:
39
39
 
40
40
  Options:
41
41
  --timeout, -t <seconds> Set timeout for search operations (default: 30)
42
- --format <format> Set output format (json, outline-xml, etc.)
42
+ --format <format> Set output format (default: outline)
43
43
  --help, -h Show this help message
44
44
  `);
45
45
  process.exit(0);
@@ -106,7 +106,7 @@ class ProbeServer {
106
106
  });
107
107
  this.setupToolHandlers();
108
108
  // Error handling
109
- this.server.onerror = (error) => console.error('[MCP Error]', error);
109
+ this.server.onerror = (error) => console.error('[MCP ERROR]', error);
110
110
  process.on('SIGINT', async () => {
111
111
  await this.server.close();
112
112
  process.exit(0);
@@ -118,35 +118,22 @@ class ProbeServer {
118
118
  tools: [
119
119
  {
120
120
  name: 'search_code',
121
- description: "Search code in the repository using ElasticSearch. Use this tool first for any code-related questions.",
121
+ description: "Semantic code search using AST parsing and ElasticSearch-style queries. Use this for finding code, not grep.",
122
122
  inputSchema: {
123
123
  type: 'object',
124
124
  properties: {
125
125
  path: {
126
126
  type: 'string',
127
- description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
127
+ description: 'Absolute path to the directory to search',
128
128
  },
129
129
  query: {
130
130
  type: 'string',
131
- description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. For exact matches of specific identifiers, use quotes: "MyFunction", "SpecificStruct", "exact_variable_name". Examples: "config", "(term1 OR term2) AND term3", "getUserData", "struct Config".',
131
+ description: 'Search query. Use quotes for exact matches: "functionName". Supports AND, OR, NOT operators.',
132
132
  },
133
133
  exact: {
134
134
  type: 'boolean',
135
- description: 'When you exactly know what you are looking for, like known function name, struct name, or variable name, set this flag for precise matching'
136
- },
137
- allowTests: {
138
- type: 'boolean',
139
- description: 'Allow test files and test code blocks in results (enabled by default)',
140
- default: true
141
- },
142
- session: {
143
- type: 'string',
144
- description: 'Session identifier for caching. Set to "new" if unknown, or want to reset cache. Re-use session ID returned from previous searches',
145
- default: "new",
146
- },
147
- noGitignore: {
148
- type: 'boolean',
149
- description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
135
+ description: 'Use when searching for exact function/class/variable names',
136
+ default: false
150
137
  }
151
138
  },
152
139
  required: ['path', 'query']
@@ -154,37 +141,63 @@ class ProbeServer {
154
141
  },
155
142
  {
156
143
  name: 'extract_code',
157
- description: "Extract code blocks from files based on line number, or symbol name. Fetch full file when line number is not provided.",
144
+ description: "Extract code from files. Formats: file.js (whole file), file.js:42 (from line), file.js#functionName (symbol).",
158
145
  inputSchema: {
159
146
  type: 'object',
160
147
  properties: {
161
148
  path: {
162
149
  type: 'string',
163
- description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
150
+ description: 'Absolute path to the project directory',
164
151
  },
165
152
  files: {
166
153
  type: 'array',
167
154
  items: { type: 'string' },
168
- description: 'Files and lines or sybmbols to extract from: /path/to/file.rs:10, /path/to/file.rs#func_name Path should be absolute.',
155
+ description: 'Array of file paths with optional line/symbol: ["file.rs:10", "file.rs#func_name"]',
156
+ }
157
+ },
158
+ required: ['path', 'files'],
159
+ },
160
+ },
161
+ {
162
+ name: 'grep',
163
+ description: "Standard grep-style search for non-code files (logs, config files, text files). Line numbers are shown by default. For code files, use search_code instead.",
164
+ inputSchema: {
165
+ type: 'object',
166
+ properties: {
167
+ pattern: {
168
+ type: 'string',
169
+ description: 'Regular expression pattern to search for',
170
+ },
171
+ paths: {
172
+ oneOf: [
173
+ { type: 'string' },
174
+ { type: 'array', items: { type: 'string' } }
175
+ ],
176
+ description: 'Path or array of paths to search in',
169
177
  },
170
- allowTests: {
178
+ ignoreCase: {
171
179
  type: 'boolean',
172
- description: 'Allow test files and test code blocks in results (enabled by default)',
173
- default: true
180
+ description: 'Case-insensitive search',
181
+ default: false
174
182
  },
175
- noGitignore: {
183
+ count: {
176
184
  type: 'boolean',
177
- description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
185
+ description: 'Only show count of matches per file instead of the matches',
186
+ default: false
187
+ },
188
+ context: {
189
+ type: 'number',
190
+ description: 'Number of lines of context to show before and after each match',
178
191
  }
179
192
  },
180
- required: ['path', 'files'],
193
+ required: ['pattern', 'paths'],
181
194
  },
182
195
  },
183
196
  ],
184
197
  }));
185
198
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
186
199
  if (request.params.name !== 'search_code' && request.params.name !== 'extract_code' &&
187
- request.params.name !== 'probe' && request.params.name !== 'extract') {
200
+ request.params.name !== 'grep' && request.params.name !== 'probe' && request.params.name !== 'extract') {
188
201
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
189
202
  }
190
203
  try {
@@ -208,6 +221,10 @@ class ProbeServer {
208
221
  }
209
222
  result = await this.executeCodeSearch(args);
210
223
  }
224
+ else if (request.params.name === 'grep') {
225
+ const args = request.params.arguments;
226
+ result = await this.executeGrep(args);
227
+ }
211
228
  else { // extract_code or extract
212
229
  const args = request.params.arguments;
213
230
  result = await this.executeCodeExtract(args);
@@ -245,54 +262,27 @@ class ProbeServer {
245
262
  if (!args.query) {
246
263
  throw new Error("Query is required");
247
264
  }
248
- // Log the arguments we received for debugging
249
- console.error(`Received search arguments: path=${args.path}, query=${JSON.stringify(args.query)}`);
250
- // Create a clean options object with only the essential properties first
265
+ // Build options with smart defaults
251
266
  const options = {
252
- path: args.path.trim(), // Ensure path is trimmed
253
- query: args.query
267
+ path: args.path.trim(),
268
+ query: args.query,
269
+ // Smart defaults for MCP usage
270
+ allowTests: true, // Include test files by default
271
+ session: "new", // Fresh session each time
272
+ maxResults: 20, // Reasonable limit for context window
273
+ maxTokens: 8000, // Fits in most AI context windows
254
274
  };
255
- // Add optional parameters only if they exist
275
+ // Only override defaults if user explicitly set them
256
276
  if (args.exact !== undefined)
257
277
  options.exact = args.exact;
258
- if (args.maxResults !== undefined)
259
- options.maxResults = args.maxResults;
260
- if (args.maxTokens !== undefined)
261
- options.maxTokens = args.maxTokens;
262
- // Set allowTests to true by default if not specified
263
- if (args.allowTests !== undefined) {
264
- options.allowTests = args.allowTests;
265
- }
266
- else {
267
- options.allowTests = true;
268
- }
269
- // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
270
- if (args.noGitignore !== undefined) {
271
- options.noGitignore = args.noGitignore;
272
- }
273
- else if (process.env.PROBE_NO_GITIGNORE) {
274
- options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
275
- }
276
- if (args.session !== undefined && args.session.trim() !== '') {
277
- options.session = args.session;
278
- }
279
- else {
280
- options.session = "new";
281
- }
282
- // Handle format options
283
- if (this.defaultFormat === 'outline-xml') {
284
- // For outline-xml format, we pass it as a format flag to the search command
285
- options.format = 'outline-xml';
278
+ // Handle format based on server default
279
+ if (this.defaultFormat === 'outline' || this.defaultFormat === 'outline-xml') {
280
+ options.format = this.defaultFormat;
286
281
  }
287
282
  else if (this.defaultFormat === 'json') {
288
283
  options.json = true;
289
284
  }
290
285
  console.error("Executing search with options:", JSON.stringify(options, null, 2));
291
- // Double-check that path is still in the options object
292
- if (!options.path) {
293
- console.error("Path is missing from options object after construction");
294
- throw new Error("Path is missing from options object");
295
- }
296
286
  try {
297
287
  // Call search with the options object
298
288
  const result = await search(options);
@@ -317,26 +307,13 @@ class ProbeServer {
317
307
  if (!args.files || !Array.isArray(args.files) || args.files.length === 0) {
318
308
  throw new Error("Files array is required and must not be empty");
319
309
  }
320
- // Create a single options object with files and other parameters
310
+ // Build options with smart defaults
321
311
  const options = {
322
312
  files: args.files,
323
313
  path: args.path,
324
- format: 'xml'
314
+ format: 'xml',
315
+ allowTests: true, // Include test files by default
325
316
  };
326
- // Set allowTests to true by default if not specified
327
- if (args.allowTests !== undefined) {
328
- options.allowTests = args.allowTests;
329
- }
330
- else {
331
- options.allowTests = true;
332
- }
333
- // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
334
- if (args.noGitignore !== undefined) {
335
- options.noGitignore = args.noGitignore;
336
- }
337
- else if (process.env.PROBE_NO_GITIGNORE) {
338
- options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
339
- }
340
317
  // Call extract with the complete options object
341
318
  try {
342
319
  // Track request size for token usage
@@ -380,6 +357,47 @@ class ProbeServer {
380
357
  throw new McpError('MethodNotFound', `Error executing code extract: ${error.message || String(error)}`);
381
358
  }
382
359
  }
360
+ async executeGrep(args) {
361
+ try {
362
+ // Validate required parameters
363
+ if (!args.pattern) {
364
+ throw new Error("Pattern is required");
365
+ }
366
+ if (!args.paths) {
367
+ throw new Error("Paths are required");
368
+ }
369
+ // Build options object with good defaults
370
+ const options = {
371
+ pattern: args.pattern,
372
+ paths: args.paths,
373
+ // Default: show line numbers (makes output more useful)
374
+ lineNumbers: true,
375
+ // Default: never use color in MCP context (better for parsing)
376
+ color: 'never'
377
+ };
378
+ // Only add user-specified optional parameters
379
+ if (args.ignoreCase !== undefined)
380
+ options.ignoreCase = args.ignoreCase;
381
+ if (args.count !== undefined)
382
+ options.count = args.count;
383
+ if (args.context !== undefined)
384
+ options.context = args.context;
385
+ console.error("Executing grep with options:", JSON.stringify(options, null, 2));
386
+ try {
387
+ // Call grep with the options object
388
+ const result = await grep(options);
389
+ return result || 'No matches found';
390
+ }
391
+ catch (grepError) {
392
+ console.error("Grep function error:", grepError);
393
+ throw new Error(`Grep function error: ${grepError.message || String(grepError)}`);
394
+ }
395
+ }
396
+ catch (error) {
397
+ console.error('Error executing grep:', error);
398
+ throw new McpError('MethodNotFound', `Error executing grep: ${error.message || String(error)}`);
399
+ }
400
+ }
383
401
  async run() {
384
402
  // The @probelabs/probe package now handles binary path management internally
385
403
  // We don't need to verify or download the binary in the MCP server anymore
@@ -389,5 +407,5 @@ class ProbeServer {
389
407
  console.error('Probe MCP server running on stdio');
390
408
  }
391
409
  }
392
- const server = new ProbeServer(cliConfig.timeout, cliConfig.format || 'outline-xml');
410
+ const server = new ProbeServer(cliConfig.timeout, cliConfig.format || 'outline');
393
411
  server.run().catch(console.error);
@@ -14,7 +14,7 @@ import fs from 'fs-extra';
14
14
  import { fileURLToPath } from 'url';
15
15
 
16
16
  // Import from parent package
17
- import { search, query, extract, getBinaryPath, setBinaryPath } from '../index.js';
17
+ import { search, query, extract, grep, getBinaryPath, setBinaryPath } from '../index.js';
18
18
 
19
19
  // Parse command-line arguments
20
20
  function parseArgs(): { timeout?: number; format?: string } {
@@ -44,7 +44,7 @@ Usage:
44
44
 
45
45
  Options:
46
46
  --timeout, -t <seconds> Set timeout for search operations (default: 30)
47
- --format <format> Set output format (json, outline-xml, etc.)
47
+ --format <format> Set output format (default: outline)
48
48
  --help, -h Show this help message
49
49
  `);
50
50
  process.exit(0);
@@ -113,19 +113,19 @@ interface SearchCodeArgs {
113
113
  path: string;
114
114
  query: string | string[];
115
115
  exact?: boolean;
116
- maxResults?: number;
117
- maxTokens?: number;
118
- allowTests?: boolean;
119
- session?: string;
120
- noGitignore?: boolean;
121
116
  }
122
117
 
123
-
124
118
  interface ExtractCodeArgs {
125
119
  path: string;
126
120
  files: string[];
127
- allowTests?: boolean;
128
- noGitignore?: boolean;
121
+ }
122
+
123
+ interface GrepArgs {
124
+ pattern: string;
125
+ paths: string | string[];
126
+ ignoreCase?: boolean;
127
+ count?: boolean;
128
+ context?: number;
129
129
  }
130
130
 
131
131
  class ProbeServer {
@@ -151,7 +151,7 @@ class ProbeServer {
151
151
  this.setupToolHandlers();
152
152
 
153
153
  // Error handling
154
- this.server.onerror = (error) => console.error('[MCP Error]', error);
154
+ this.server.onerror = (error) => console.error('[MCP ERROR]', error);
155
155
  process.on('SIGINT', async () => {
156
156
  await this.server.close();
157
157
  process.exit(0);
@@ -165,35 +165,22 @@ class ProbeServer {
165
165
  tools: [
166
166
  {
167
167
  name: 'search_code',
168
- description: "Search code in the repository using ElasticSearch. Use this tool first for any code-related questions.",
168
+ description: "Semantic code search using AST parsing and ElasticSearch-style queries. Use this for finding code, not grep.",
169
169
  inputSchema: {
170
170
  type: 'object',
171
171
  properties: {
172
172
  path: {
173
173
  type: 'string',
174
- description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
174
+ description: 'Absolute path to the directory to search',
175
175
  },
176
176
  query: {
177
177
  type: 'string',
178
- description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. For exact matches of specific identifiers, use quotes: "MyFunction", "SpecificStruct", "exact_variable_name". Examples: "config", "(term1 OR term2) AND term3", "getUserData", "struct Config".',
178
+ description: 'Search query. Use quotes for exact matches: "functionName". Supports AND, OR, NOT operators.',
179
179
  },
180
180
  exact: {
181
181
  type: 'boolean',
182
- description: 'When you exactly know what you are looking for, like known function name, struct name, or variable name, set this flag for precise matching'
183
- },
184
- allowTests: {
185
- type: 'boolean',
186
- description: 'Allow test files and test code blocks in results (enabled by default)',
187
- default: true
188
- },
189
- session: {
190
- type: 'string',
191
- description: 'Session identifier for caching. Set to "new" if unknown, or want to reset cache. Re-use session ID returned from previous searches',
192
- default: "new",
193
- },
194
- noGitignore: {
195
- type: 'boolean',
196
- description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
182
+ description: 'Use when searching for exact function/class/variable names',
183
+ default: false
197
184
  }
198
185
  },
199
186
  required: ['path', 'query']
@@ -201,30 +188,56 @@ class ProbeServer {
201
188
  },
202
189
  {
203
190
  name: 'extract_code',
204
- description: "Extract code blocks from files based on line number, or symbol name. Fetch full file when line number is not provided.",
191
+ description: "Extract code from files. Formats: file.js (whole file), file.js:42 (from line), file.js#functionName (symbol).",
205
192
  inputSchema: {
206
193
  type: 'object',
207
194
  properties: {
208
195
  path: {
209
196
  type: 'string',
210
- description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
197
+ description: 'Absolute path to the project directory',
211
198
  },
212
199
  files: {
213
200
  type: 'array',
214
201
  items: { type: 'string' },
215
- description: 'Files and lines or sybmbols to extract from: /path/to/file.rs:10, /path/to/file.rs#func_name Path should be absolute.',
202
+ description: 'Array of file paths with optional line/symbol: ["file.rs:10", "file.rs#func_name"]',
203
+ }
204
+ },
205
+ required: ['path', 'files'],
206
+ },
207
+ },
208
+ {
209
+ name: 'grep',
210
+ description: "Standard grep-style search for non-code files (logs, config files, text files). Line numbers are shown by default. For code files, use search_code instead.",
211
+ inputSchema: {
212
+ type: 'object',
213
+ properties: {
214
+ pattern: {
215
+ type: 'string',
216
+ description: 'Regular expression pattern to search for',
217
+ },
218
+ paths: {
219
+ oneOf: [
220
+ { type: 'string' },
221
+ { type: 'array', items: { type: 'string' } }
222
+ ],
223
+ description: 'Path or array of paths to search in',
216
224
  },
217
- allowTests: {
225
+ ignoreCase: {
218
226
  type: 'boolean',
219
- description: 'Allow test files and test code blocks in results (enabled by default)',
220
- default: true
227
+ description: 'Case-insensitive search',
228
+ default: false
221
229
  },
222
- noGitignore: {
230
+ count: {
223
231
  type: 'boolean',
224
- description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
232
+ description: 'Only show count of matches per file instead of the matches',
233
+ default: false
234
+ },
235
+ context: {
236
+ type: 'number',
237
+ description: 'Number of lines of context to show before and after each match',
225
238
  }
226
239
  },
227
- required: ['path', 'files'],
240
+ required: ['pattern', 'paths'],
228
241
  },
229
242
  },
230
243
  ],
@@ -232,7 +245,7 @@ class ProbeServer {
232
245
 
233
246
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
234
247
  if (request.params.name !== 'search_code' && request.params.name !== 'extract_code' &&
235
- request.params.name !== 'probe' && request.params.name !== 'extract') {
248
+ request.params.name !== 'grep' && request.params.name !== 'probe' && request.params.name !== 'extract') {
236
249
  throw new McpError(
237
250
  ErrorCode.MethodNotFound,
238
251
  `Unknown tool: ${request.params.name}`
@@ -252,9 +265,9 @@ class ProbeServer {
252
265
  if (!request.params.arguments || typeof request.params.arguments !== 'object') {
253
266
  throw new Error("Arguments must be an object");
254
267
  }
255
-
268
+
256
269
  const args = request.params.arguments as unknown as SearchCodeArgs;
257
-
270
+
258
271
  // Validate required fields
259
272
  if (!args.path) {
260
273
  throw new Error("Path is required in arguments");
@@ -262,8 +275,11 @@ class ProbeServer {
262
275
  if (!args.query) {
263
276
  throw new Error("Query is required in arguments");
264
277
  }
265
-
278
+
266
279
  result = await this.executeCodeSearch(args);
280
+ } else if (request.params.name === 'grep') {
281
+ const args = request.params.arguments as unknown as GrepArgs;
282
+ result = await this.executeGrep(args);
267
283
  } else { // extract_code or extract
268
284
  const args = request.params.arguments as unknown as ExtractCodeArgs;
269
285
  result = await this.executeCodeExtract(args);
@@ -304,53 +320,29 @@ class ProbeServer {
304
320
  throw new Error("Query is required");
305
321
  }
306
322
 
307
- // Log the arguments we received for debugging
308
- console.error(`Received search arguments: path=${args.path}, query=${JSON.stringify(args.query)}`);
309
-
310
- // Create a clean options object with only the essential properties first
323
+ // Build options with smart defaults
311
324
  const options: any = {
312
- path: args.path.trim(), // Ensure path is trimmed
313
- query: args.query
325
+ path: args.path.trim(),
326
+ query: args.query,
327
+ // Smart defaults for MCP usage
328
+ allowTests: true, // Include test files by default
329
+ session: "new", // Fresh session each time
330
+ maxResults: 20, // Reasonable limit for context window
331
+ maxTokens: 8000, // Fits in most AI context windows
314
332
  };
315
-
316
- // Add optional parameters only if they exist
333
+
334
+ // Only override defaults if user explicitly set them
317
335
  if (args.exact !== undefined) options.exact = args.exact;
318
- if (args.maxResults !== undefined) options.maxResults = args.maxResults;
319
- if (args.maxTokens !== undefined) options.maxTokens = args.maxTokens;
320
- // Set allowTests to true by default if not specified
321
- if (args.allowTests !== undefined) {
322
- options.allowTests = args.allowTests;
323
- } else {
324
- options.allowTests = true;
325
- }
326
- // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
327
- if (args.noGitignore !== undefined) {
328
- options.noGitignore = args.noGitignore;
329
- } else if (process.env.PROBE_NO_GITIGNORE) {
330
- options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
331
- }
332
- if (args.session !== undefined && args.session.trim() !== '') {
333
- options.session = args.session;
334
- } else {
335
- options.session = "new";
336
- }
337
336
 
338
- // Handle format options
339
- if (this.defaultFormat === 'outline-xml') {
340
- // For outline-xml format, we pass it as a format flag to the search command
341
- options.format = 'outline-xml';
337
+ // Handle format based on server default
338
+ if (this.defaultFormat === 'outline' || this.defaultFormat === 'outline-xml') {
339
+ options.format = this.defaultFormat;
342
340
  } else if (this.defaultFormat === 'json') {
343
341
  options.json = true;
344
342
  }
345
-
343
+
346
344
  console.error("Executing search with options:", JSON.stringify(options, null, 2));
347
345
 
348
- // Double-check that path is still in the options object
349
- if (!options.path) {
350
- console.error("Path is missing from options object after construction");
351
- throw new Error("Path is missing from options object");
352
- }
353
-
354
346
  try {
355
347
  // Call search with the options object
356
348
  const result = await search(options);
@@ -379,27 +371,14 @@ class ProbeServer {
379
371
  throw new Error("Files array is required and must not be empty");
380
372
  }
381
373
 
382
- // Create a single options object with files and other parameters
374
+ // Build options with smart defaults
383
375
  const options: any = {
384
376
  files: args.files,
385
377
  path: args.path,
386
- format: 'xml'
378
+ format: 'xml',
379
+ allowTests: true, // Include test files by default
387
380
  };
388
381
 
389
- // Set allowTests to true by default if not specified
390
- if (args.allowTests !== undefined) {
391
- options.allowTests = args.allowTests;
392
- } else {
393
- options.allowTests = true;
394
- }
395
-
396
- // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
397
- if (args.noGitignore !== undefined) {
398
- options.noGitignore = args.noGitignore;
399
- } else if (process.env.PROBE_NO_GITIGNORE) {
400
- options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
401
- }
402
-
403
382
  // Call extract with the complete options object
404
383
  try {
405
384
  // Track request size for token usage
@@ -454,6 +433,50 @@ class ProbeServer {
454
433
  }
455
434
  }
456
435
 
436
+ private async executeGrep(args: GrepArgs): Promise<string> {
437
+ try {
438
+ // Validate required parameters
439
+ if (!args.pattern) {
440
+ throw new Error("Pattern is required");
441
+ }
442
+ if (!args.paths) {
443
+ throw new Error("Paths are required");
444
+ }
445
+
446
+ // Build options object with good defaults
447
+ const options: any = {
448
+ pattern: args.pattern,
449
+ paths: args.paths,
450
+ // Default: show line numbers (makes output more useful)
451
+ lineNumbers: true,
452
+ // Default: never use color in MCP context (better for parsing)
453
+ color: 'never'
454
+ };
455
+
456
+ // Only add user-specified optional parameters
457
+ if (args.ignoreCase !== undefined) options.ignoreCase = args.ignoreCase;
458
+ if (args.count !== undefined) options.count = args.count;
459
+ if (args.context !== undefined) options.context = args.context;
460
+
461
+ console.error("Executing grep with options:", JSON.stringify(options, null, 2));
462
+
463
+ try {
464
+ // Call grep with the options object
465
+ const result = await grep(options);
466
+ return result || 'No matches found';
467
+ } catch (grepError: any) {
468
+ console.error("Grep function error:", grepError);
469
+ throw new Error(`Grep function error: ${grepError.message || String(grepError)}`);
470
+ }
471
+ } catch (error: any) {
472
+ console.error('Error executing grep:', error);
473
+ throw new McpError(
474
+ 'MethodNotFound' as unknown as ErrorCode,
475
+ `Error executing grep: ${error.message || String(error)}`
476
+ );
477
+ }
478
+ }
479
+
457
480
  async run() {
458
481
  // The @probelabs/probe package now handles binary path management internally
459
482
  // We don't need to verify or download the binary in the MCP server anymore
@@ -465,5 +488,5 @@ class ProbeServer {
465
488
  }
466
489
  }
467
490
 
468
- const server = new ProbeServer(cliConfig.timeout, cliConfig.format || 'outline-xml');
491
+ const server = new ProbeServer(cliConfig.timeout, cliConfig.format || 'outline');
469
492
  server.run().catch(console.error);