search_files
Search for patterns in files with advanced filtering options like regex, file type, size, and date constraints. Preprocess content by excluding comments and strings, and format results as text, JSON, or structured output.
Instructions
Advanced file and code search tool with comprehensive filtering and matching capabilities. Searches for patterns in files within allowed directories with support for regex patterns, file type filtering, size constraints, date filtering, and content preprocessing. When called without arguments, searches for common patterns in the current directory. Supports excluding comments and string literals for cleaner code searches. Results can be formatted as text, JSON, or structured output with configurable sorting and grouping options.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| caseSensitive | No | Whether search should be case sensitive | |
| excludeComments | No | Whether to exclude comments from search (language-aware) | |
| excludeExtensions | No | Array of file extensions to exclude | |
| excludePatterns | No | Array of filename patterns to exclude (supports simple wildcards) | |
| excludeStrings | No | Whether to exclude string literals from search | |
| extensions | No | Array of file extensions to include (e.g., ['.js', '.ts', '.py']). Include the dot prefix | |
| followSymlinks | No | Whether to follow symbolic links | |
| groupByFile | No | Whether to group results by file | |
| includeBinary | No | Whether to search in binary files | |
| maxDepth | No | Maximum directory recursion depth. Unlimited if not specified | |
| maxResults | No | Maximum number of match results to return | |
| maxSize | No | Maximum file size in bytes | |
| minSize | No | Minimum file size in bytes | |
| modifiedAfter | No | Only include files modified after this date (ISO 8601 format) | |
| modifiedBefore | No | Only include files modified before this date (ISO 8601 format) | |
| multiline | No | Whether to enable multiline regex matching | |
| outputFormat | No | Output format for results | text |
| pattern | No | Search pattern - can be literal text or regex depending on regexMode. Defaults to searching for common file types if not specified | .* |
| regexMode | No | Whether to treat pattern as a regular expression | |
| searchPath | No | Directory path to search in. Must be within allowed directories. Defaults to first allowed directory if not specified | |
| snippetLength | No | Length of text snippet around matches | |
| sortBy | No | How to sort the results | relevance |
| wordBoundary | No | Whether to match whole words only |
Implementation Reference
- src/search.ts:525-629 (handler)The primary handler function that executes the search_files tool logic. It sets up search options, validates the search path against allowed directories, performs recursive directory search, sorts and limits results, formats output, and returns the response.export async function handleSearch(args: any, allowedDirectories: string[]) { // Search files handler // Set up default options const options: SearchOptions = { pattern: args.pattern || ".*", searchPath: args.searchPath || (allowedDirectories.length > 0 ? allowedDirectories[0] : ""), extensions: args.extensions, excludeExtensions: args.excludeExtensions, excludePatterns: args.excludePatterns || [], regexMode: args.regexMode || false, caseSensitive: args.caseSensitive || false, wordBoundary: args.wordBoundary || false, multiline: args.multiline || false, maxDepth: args.maxDepth, followSymlinks: args.followSymlinks || false, includeBinary: args.includeBinary || false, minSize: args.minSize, maxSize: args.maxSize, modifiedAfter: args.modifiedAfter, modifiedBefore: args.modifiedBefore, snippetLength: args.snippetLength || 50, maxResults: args.maxResults || 100, sortBy: args.sortBy || 'relevance', groupByFile: args.groupByFile !== undefined ? args.groupByFile : true, excludeComments: args.excludeComments || false, excludeStrings: args.excludeStrings || false, outputFormat: args.outputFormat || 'text' }; // Validate search path if (!options.searchPath) { throw new McpError( ErrorCode.InvalidRequest, "No search path specified and no allowed directories available" ); } // Ensure searchPath is not empty string if (options.searchPath.trim() === "") { throw new McpError( ErrorCode.InvalidRequest, "Search path cannot be empty" ); } // Normalize search path options.searchPath = path.normalize(options.searchPath); // Check if search path is allowed if (!isPathAllowed(options.searchPath, allowedDirectories)) { throw new McpError( ErrorCode.InvalidRequest, `Access denied: The path '${options.searchPath}' is not in the list of allowed directories: ${allowedDirectories.join(', ')}` ); } // Validate that the search path exists and is a directory try { const stat = await fs.promises.stat(options.searchPath); if (!stat.isDirectory()) { throw new McpError( ErrorCode.InvalidRequest, `The path '${options.searchPath}' is not a directory` ); } } catch (error) { throw new McpError( ErrorCode.InvalidRequest, `The path '${options.searchPath}' does not exist or cannot be accessed` ); } try { // Perform the search const results = await searchDirectory(options.searchPath, options, allowedDirectories); // Sort results const sortedResults = sortResults(results, options.sortBy || 'relevance'); // Limit results const limitedResults = sortedResults.slice(0, options.maxResults); // Format output const formattedResults = formatResults(limitedResults, options); return { content: [ { type: "text", text: formattedResults } ] }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Error during search: ${error instanceof Error ? error.message : String(error)}` ); } }
- src/search.ts:402-522 (schema)The tool object definition for search_files, including name, detailed description, and comprehensive inputSchema with all parameters for search options like pattern, paths, filters, regex flags, etc.export const searchTool = { name: "search_files", description: "Advanced file and code search tool with comprehensive filtering and matching capabilities. Searches for patterns in files within allowed directories with support for regex patterns, file type filtering, size constraints, date filtering, and content preprocessing. When called without arguments, searches for common patterns in the current directory. Supports excluding comments and string literals for cleaner code searches. Results can be formatted as text, JSON, or structured output with configurable sorting and grouping options.", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Search pattern - can be literal text or regex depending on regexMode. Defaults to searching for common file types if not specified", default: ".*" }, searchPath: { type: "string", description: "Directory path to search in. Must be within allowed directories. Defaults to first allowed directory if not specified" }, extensions: { type: "array", items: { type: "string" }, description: "Array of file extensions to include (e.g., ['.js', '.ts', '.py']). Include the dot prefix" }, excludeExtensions: { type: "array", items: { type: "string" }, description: "Array of file extensions to exclude" }, excludePatterns: { type: "array", items: { type: "string" }, description: "Array of filename patterns to exclude (supports simple wildcards)" }, regexMode: { type: "boolean", description: "Whether to treat pattern as a regular expression", default: false }, caseSensitive: { type: "boolean", description: "Whether search should be case sensitive", default: false }, wordBoundary: { type: "boolean", description: "Whether to match whole words only", default: false }, multiline: { type: "boolean", description: "Whether to enable multiline regex matching", default: false }, maxDepth: { type: "integer", description: "Maximum directory recursion depth. Unlimited if not specified" }, followSymlinks: { type: "boolean", description: "Whether to follow symbolic links", default: false }, includeBinary: { type: "boolean", description: "Whether to search in binary files", default: false }, minSize: { type: "integer", description: "Minimum file size in bytes" }, maxSize: { type: "integer", description: "Maximum file size in bytes" }, modifiedAfter: { type: "string", description: "Only include files modified after this date (ISO 8601 format)" }, modifiedBefore: { type: "string", description: "Only include files modified before this date (ISO 8601 format)" }, snippetLength: { type: "integer", description: "Length of text snippet around matches", default: 50 }, maxResults: { type: "integer", description: "Maximum number of match results to return", default: 100 }, sortBy: { type: "string", enum: ["relevance", "file", "lineNumber", "modified", "size"], description: "How to sort the results", default: "relevance" }, groupByFile: { type: "boolean", description: "Whether to group results by file", default: true }, excludeComments: { type: "boolean", description: "Whether to exclude comments from search (language-aware)", default: false }, excludeStrings: { type: "boolean", description: "Whether to exclude string literals from search", default: false }, outputFormat: { type: "string", enum: ["text", "json", "structured"], description: "Output format for results", default: "text" } }, required: [] } };
- src/index.ts:33-44 (registration)Registration of the search_files tool (as searchTool) in the MCP server's ListToolsRequestHandler, making it discoverable by clients.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ exploreProjectTool, listAllowedTool, searchTool, renameFileTool, deleteFileTool, checkOutdatedTool ] }; });
- src/index.ts:59-60 (registration)Dispatch/execution routing for search_files tool in the CallToolRequestHandler switch statement, calling the handleSearch function.case "search_files": return await handleSearch(args, ALLOWED_DIRECTORIES);
- src/search.ts:257-329 (helper)Core recursive search function that traverses directories (respecting depth, exclusions), stats files, filters by criteria, and invokes searchInFile on candidates.async function searchDirectory( dirPath: string, options: SearchOptions, allowedDirectories: string[], currentDepth: number = 0 ): Promise<SearchResult[]> { const results: SearchResult[] = []; if (options.maxDepth !== undefined && currentDepth > options.maxDepth) { return results; } try { const entries = await fs.promises.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(dirPath, entry.name); // Skip excluded paths if (shouldExcludePath(entryPath, options.excludePatterns || [])) { continue; } if (entry.isDirectory()) { // Recursively search subdirectories const subdirResults = await searchDirectory(entryPath, options, allowedDirectories, currentDepth + 1); results.push(...subdirResults); } else if (entry.isFile() || (entry.isSymbolicLink() && options.followSymlinks)) { try { const stat = await fs.promises.stat(entryPath); // Check size criteria if (!matchesSize(stat.size, options.minSize, options.maxSize)) { continue; } // Check date criteria if (!matchesDate(stat, options.modifiedAfter, options.modifiedBefore)) { continue; } // Check extension criteria if (!matchesExtension(entryPath, options.extensions, options.excludeExtensions)) { continue; } // Search in file const matches = await searchInFile(entryPath, options); if (matches.length > 0) { const relativePath = options.searchPath ? path.relative(options.searchPath, entryPath) : entryPath; results.push({ filePath: entryPath, relativePath: relativePath, matches: matches, fileSize: stat.size, fileSizeFormatted: formatFileSize(stat.size), lastModified: stat.mtime, fileExtension: path.extname(entryPath).toLowerCase() }); } } catch (error) { // Error processing file, skip } } } } catch (error) { // Error searching directory, skip } return results; }