Skip to main content
Glama
pwilkin

MCP File Editor Server

by pwilkin

search_directory

Search for regex patterns in files within a specified directory, including optional recursive search, line context, and file inclusion/exclusion filters. Use to locate matches efficiently across multiple files.

Instructions

Search for regex patterns across all files in a directory.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
directory_pathYesAbsolute path to the directory to search
excludeNoGlob pattern for files/directories to exclude
includeNoGlob pattern for files to include (e.g., "*.ts", "*.js")
lines_afterNoNumber of lines to show after each match
lines_beforeNoNumber of lines to show before each match
recursiveNoSearch recursively in subdirectories
regexpYesRegular expression pattern to search for

Implementation Reference

  • The execute handler for the 'search_directory' tool. It validates the directory, defines inner helper functions for searching files and directories, and performs recursive regex search with context lines, include/exclude globs.
    execute: async ({ directory_path, regexp, recursive, lines_before, lines_after, include, exclude }) => {
      const absolutePath = validateAbsolutePath(directory_path, 'directory_path');
      validateDirectoryExists(absolutePath);
    
      try {
        const regex = new RegExp(regexp);
        const results: string[] = [];
        let totalMatches = 0;
    
        function searchInFile(filePath: string): void {
          try {
            const content = fs.readFileSync(filePath, 'utf-8');
            const lines = content.split('\n');
            const fileMatches: string[] = [];
    
            lines.forEach((line, index) => {
              if (regex.test(line)) {
                const lineNumber = index + 1;
                const startLine = Math.max(1, lineNumber - (lines_before || 0));
                const endLine = Math.min(lines.length, lineNumber + (lines_after || 0));
    
                const contextLines = lines.slice(startLine - 1, endLine);
                const context = contextLines.map((ctxLine, ctxIndex) => {
                  const ctxLineNumber = startLine + ctxIndex;
                  const marker = ctxLineNumber === lineNumber ? '>' : ' ';
                  return `${marker} ${ctxLineNumber} | ${ctxLine}`;
                }).join('\n');
    
                fileMatches.push(`Match at line ${lineNumber}:\n${context}`);
                totalMatches++;
              }
            });
    
            if (fileMatches.length > 0) {
              results.push(`File: ${filePath}\n${fileMatches.join('\n\n')}`);
            }
          } catch (error: any) {
            // Skip files that can't be read
            results.push(`Warning: Could not read file ${filePath}: ${error.message}`);
          }
        }
    
        function matchesPattern(filename: string, pattern?: string): boolean {
          if (!pattern) return true;
          // Simple glob matching (could be enhanced with a proper glob library)
          const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
          return new RegExp(`^${regexPattern}$`).test(filename);
        }
    
        function searchDirectory(dirPath: string): void {
          const items = fs.readdirSync(dirPath);
    
          items.forEach(item => {
            const itemPath = path.join(dirPath, item);
            const stats = fs.statSync(itemPath);
    
            if (stats.isDirectory()) {
              if (recursive && !(exclude && matchesPattern(item, exclude))) {
                searchDirectory(itemPath);
              }
            } else if (stats.isFile()) {
              const includeMatch = !include || matchesPattern(item, include);
              const excludeMatch = exclude && matchesPattern(item, exclude);
              if (includeMatch && !excludeMatch) {
                searchInFile(itemPath);
              }
            }
          });
        }
    
        searchDirectory(absolutePath);
    
        if (totalMatches === 0) {
          return `No matches found for pattern "${regexp}" in directory "${absolutePath}".`;
        }
    
        return `Found ${totalMatches} match(es) in ${results.length} file(s):\n\n${results.join('\n\n')}`;
      } catch (error: any) {
        if (error instanceof UserError) throw error;
        throw new UserError(`Error searching directory "${absolutePath}": ${error.message}`);
      }
    }
  • Zod schema defining the input parameters for the search_directory tool, including directory path, regex, recursion flag, context lines, and glob patterns for include/exclude.
    parameters: z.object({
      directory_path: z.string().describe('Absolute path to the directory to search'),
      regexp: z.string().describe('Regular expression pattern to search for'),
      recursive: z.boolean().optional().default(false).describe('Search recursively in subdirectories'),
      lines_before: z.number().int().min(0).optional().describe('Number of lines to show before each match'),
      lines_after: z.number().int().min(0).optional().describe('Number of lines to show after each match'),
      include: z.string().optional().describe('Glob pattern for files to include (e.g., "*.ts", "*.js")'),
      exclude: z.string().optional().describe('Glob pattern for files/directories to exclude')
    }),
  • src/index.ts:519-519 (registration)
    The server.addTool call registering the search_directory tool.
    server.addTool({
  • Inner helper function that recursively traverses the directory structure, applies include/exclude filters, and calls searchInFile on matching files.
    function searchDirectory(dirPath: string): void {
      const items = fs.readdirSync(dirPath);
    
      items.forEach(item => {
        const itemPath = path.join(dirPath, item);
        const stats = fs.statSync(itemPath);
    
        if (stats.isDirectory()) {
          if (recursive && !(exclude && matchesPattern(item, exclude))) {
            searchDirectory(itemPath);
          }
        } else if (stats.isFile()) {
          const includeMatch = !include || matchesPattern(item, include);
          const excludeMatch = exclude && matchesPattern(item, exclude);
          if (includeMatch && !excludeMatch) {
            searchInFile(itemPath);
          }
        }
      });
    }
  • Helper function to match filenames against simple glob patterns for include/exclude filtering.
    function matchesPattern(filename: string, pattern?: string): boolean {
      if (!pattern) return true;
      // Simple glob matching (could be enhanced with a proper glob library)
      const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
      return new RegExp(`^${regexPattern}$`).test(filename);
    }
Install Server

Other Tools

Related Tools

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/pwilkin/mcp-file-edit'

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