grep_search
Search file contents using regex patterns to find specific text within files or directories, with options for case sensitivity, file filtering, and context lines around matches.
Instructions
Search file contents with regex
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| pattern | Yes | Regex pattern to search for | |
| path | No | File or directory to search | |
| glob | No | File pattern filter (e.g., *.ts) | |
| case_sensitive | No | Case sensitive search | |
| max_results | No | Maximum results (default: 100) | |
| context_lines | No | Lines of context around matches |
Implementation Reference
- src/tools/search.ts:107-238 (handler)The grepSearchImpl function performs the regex-based content search across files.
async function grepSearchImpl(input: GrepSearchInput): Promise<ToolResult> { try { const searchPath = input.path ? path.resolve(input.path) : process.cwd(); const matches: GrepMatch[] = []; // Create regex const flags = input.case_sensitive ? '' : 'i'; let regex: RegExp; try { regex = new RegExp(input.pattern, flags); } catch { return { isError: true, content: [ { type: 'text', text: JSON.stringify({ code: 'INVALID_PATH', message: `Invalid regex pattern: ${input.pattern}`, }), }, ], }; } // Determine files to search let files: string[]; const stats = await fs.stat(searchPath); if (stats.isFile()) { files = [searchPath]; } else { // Use glob to find files in directory const globPattern = input.glob ?? '**/*'; files = await glob(globPattern, { cwd: searchPath, nodir: true, absolute: true, maxDepth: 20, }); } // Search each file for (const file of files) { if (matches.length >= input.max_results) break; try { const content = await fs.readFile(file, 'utf-8'); const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { if (matches.length >= input.max_results) break; const line = lines[i]; const match = regex.exec(line); if (match) { const grepMatch: GrepMatch = { file, line: i + 1, column: match.index + 1, match: line.trim(), }; // Add context if requested if (input.context_lines > 0) { const beforeStart = Math.max(0, i - input.context_lines); const afterEnd = Math.min(lines.length, i + input.context_lines + 1); grepMatch.context = { before: lines.slice(beforeStart, i).map((l) => l.trim()), after: lines.slice(i + 1, afterEnd).map((l) => l.trim()), }; } matches.push(grepMatch); } } } catch { // Skip files that can't be read (binary, permissions, etc.) } } return { content: [ { type: 'text', text: JSON.stringify( { pattern: input.pattern, count: matches.length, truncated: matches.length >= input.max_results, matches, }, null, 2 ), }, ], }; } catch (error) { const err = error as NodeJS.ErrnoException; if (err.code === 'ENOENT') { return { isError: true, content: [ { type: 'text', text: JSON.stringify({ code: 'FILE_NOT_FOUND', message: `Path not found: ${input.path}`, }), }, ], }; } return { isError: true, content: [ { type: 'text', text: JSON.stringify({ code: 'UNKNOWN_ERROR', message: `Error in grep search: ${err.message}`, }), }, ], }; } } - src/tools/search.ts:282-298 (registration)Registration of the grep_search tool in the MCP server.
// grep_search tool server.tool( 'grep_search', 'Search file contents with regex', { pattern: z.string().describe('Regex pattern to search for'), path: z.string().optional().describe('File or directory to search'), glob: z.string().optional().describe('File pattern filter (e.g., *.ts)'), case_sensitive: z.boolean().optional().describe('Case sensitive search'), max_results: z.number().optional().describe('Maximum results (default: 100)'), context_lines: z.number().optional().describe('Lines of context around matches'), }, async (args) => { const input = GrepSearchInputSchema.parse(args); return await grepSearchImpl(input); } );