Skip to main content
Glama

regex_search_content

Search file content recursively using a regex pattern. Scan subdirectories from a specified path, return matching files with line details. Optional filters: file pattern, depth, size, and result limits. Works within secure directories.

Instructions

Recursively search file content using a regex pattern. Searches through subdirectories from the starting path. Returns a list of files containing matches, including line numbers and matching lines. Requires regex pattern. Optional: path, filePattern, maxDepth, maxFileSize, maxResults. Only searches within allowed directories.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filePatternNoGlob pattern to filter files to search within (e.g., "*.ts", "data/**.json"). Defaults to searching all files.*
maxDepthNoMaximum directory depth to search recursively. Defaults to 2.
maxFileSizeNoMaximum file size in bytes to read for searching. Defaults to 10MB.
maxResultsNoMaximum number of files with matches to return. Defaults to 50.
pathYesDirectory path to start the search from.
regexYesThe regular expression pattern to search for within file content.

Implementation Reference

  • The main handler function that parses arguments, validates path, calls the regexSearchContent helper, formats results, and returns the response.
    export async function handleRegexSearchContent( args: unknown, allowedDirectories: string[], symlinksMap: Map<string, string>, noFollowSymlinks: boolean ) { const parsed = parseArgs(RegexSearchContentArgsSchema, args, 'regex_search_content'); const { path: startPath, regex, filePattern, maxDepth, maxFileSize, maxResults } = parsed; const validPath = await validatePath(startPath, allowedDirectories, symlinksMap, noFollowSymlinks); try { const results = await regexSearchContent( validPath, regex, filePattern, maxDepth, maxFileSize, maxResults ); if (results.length === 0) { return { content: [{ type: "text", text: "No matches found for the given regex pattern." }] }; } // Format the output const formattedResults = results.map(fileResult => { const matchesText = fileResult.matches .map(match => ` Line ${match.lineNumber}: ${match.lineContent.trim()}`) .join('\n'); return `File: ${fileResult.path}\n${matchesText}`; }).join('\n\n'); return { content: [{ type: "text", text: formattedResults }], }; } catch (error: any) { // Catch errors from regexSearchContent (e.g., invalid regex) throw new Error(`Error during regex content search: ${error.message}`); } }
  • TypeBox schema definition for the tool's input arguments and corresponding TypeScript type.
    export const RegexSearchContentArgsSchema = Type.Object({ path: Type.String({ description: 'Directory path to start the search from.' }), regex: Type.String({ description: 'The regular expression pattern to search for within file content.' }), filePattern: Type.Optional(Type.String({ default: '*', description: 'Glob pattern to filter files to search within (e.g., "*.ts", "data/**.json"). Defaults to searching all files.' })), maxDepth: Type.Optional(Type.Integer({ minimum: 1, default: 2, description: 'Maximum directory depth to search recursively. Defaults to 2.' })), maxFileSize: Type.Optional(Type.Integer({ minimum: 1, default: 10 * 1024 * 1024, description: 'Maximum file size in bytes to read for searching. Defaults to 10MB.' })), maxResults: Type.Optional(Type.Integer({ minimum: 1, default: 50, description: 'Maximum number of files with matches to return. Defaults to 50.' })) }); export type RegexSearchContentArgs = Static<typeof RegexSearchContentArgsSchema>;
  • Core utility function that performs the recursive regex search on file contents using streaming readline for large files, respects limits, and returns structured match results.
    export async function regexSearchContent( rootPath: string, regexPattern: string, filePattern: string = '*', maxDepth: number = 2, maxFileSize: number = 10 * 1024 * 1024, // 10MB default maxResults: number = 50 ): Promise<ReadonlyArray<RegexSearchResult>> { const results: RegexSearchResult[] = []; let regex: RegExp; try { regex = new RegExp(regexPattern, 'g'); // Global flag to find all matches } catch (error: any) { throw new Error(`Invalid regex pattern provided: ${error.message}`); } async function search(currentPath: string, currentDepth: number) { if (currentDepth >= maxDepth || results.length >= maxResults) { return; } let entries; try { entries = await fsPromises.readdir(currentPath, { withFileTypes: true }); } catch (error: any) { console.warn(`Skipping directory ${currentPath}: ${error.message}`); return; // Skip directories we can't read } for (const entry of entries) { if (results.length >= maxResults) return; // Check results limit again const fullPath = path.join(currentPath, entry.name); const relativePath = path.relative(rootPath, fullPath); if (entry.isDirectory()) { await search(fullPath, currentDepth + 1); } else if (entry.isFile()) { // Check if file matches the filePattern glob // Match file pattern against the relative path (removed matchBase: true) if (!minimatch(relativePath, filePattern, { dot: true })) { continue; } try { const stats = await fsPromises.stat(fullPath); if (stats.size > maxFileSize) { console.warn(`Skipping large file ${fullPath}: size ${stats.size} > max ${maxFileSize}`); continue; } // Use streaming approach for large files const fileStream = createReadStream(fullPath, { encoding: 'utf-8' }); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity, // Handle different line endings }); const fileMatches: { lineNumber: number; lineContent: string }[] = []; let currentLineNumber = 0; // Wrap readline processing in a promise await new Promise<void>((resolve, reject) => { rl.on('line', (line) => { currentLineNumber++; // Reset regex lastIndex before each test if using global flag regex.lastIndex = 0; if (regex.test(line)) { fileMatches.push({ lineNumber: currentLineNumber, lineContent: line }); } }); rl.on('close', () => { resolve(); }); rl.on('error', (err) => { // Don't reject, just warn and resolve to continue processing other files console.warn(`Error reading file ${fullPath}: ${err.message}`); resolve(); }); fileStream.on('error', (err) => { // Handle stream errors (e.g., file not found during read) console.warn(`Error reading file stream ${fullPath}: ${err.message}`); resolve(); // Resolve to allow processing to continue }); }); if (fileMatches.length > 0) { if (results.length < maxResults) { results.push({ path: fullPath, matches: fileMatches }); } if (results.length >= maxResults) return; // Stop searching this branch } } catch (error: any) { console.warn(`Skipping file ${fullPath}: ${error.message}`); // Continue searching other files even if one fails } } } } await search(rootPath, 0); return results; }
  • index.ts:295-301 (registration)
    Registers the tool handler in the toolHandlers object, binding it to the FastMCP server execution.
    regex_search_content: (a: unknown) => handleRegexSearchContent( a, allowedDirectories, symlinksMap, noFollowSymlinks, ),
  • index.ts:334-337 (registration)
    Defines the tool metadata (name and description) in the allTools array used for server.addTool.
    { name: "regex_search_content", description: "Search file content with regex", },

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rawr-ai/mcp-filesystem'

If you have feedback or need assistance with the MCP directory API, please join our Discord server