search_files
Search for patterns in project files using regex, filtering by file type, size, date, and content. Exclude comments and strings for cleaner code searches.
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 |
|---|---|---|---|
| pattern | No | Search pattern - can be literal text or regex depending on regexMode. Defaults to searching for common file types if not specified | .* |
| searchPath | No | Directory path to search in. Must be within allowed directories. Defaults to first allowed directory if not specified | |
| extensions | No | Array of file extensions to include (e.g., ['.js', '.ts', '.py']). Include the dot prefix | |
| excludeExtensions | No | Array of file extensions to exclude | |
| excludePatterns | No | Array of filename patterns to exclude (supports simple wildcards) | |
| regexMode | No | Whether to treat pattern as a regular expression | |
| caseSensitive | No | Whether search should be case sensitive | |
| wordBoundary | No | Whether to match whole words only | |
| multiline | No | Whether to enable multiline regex matching | |
| maxDepth | No | Maximum directory recursion depth. Unlimited if not specified | |
| followSymlinks | No | Whether to follow symbolic links | |
| includeBinary | No | Whether to search in binary files | |
| minSize | No | Minimum file size in bytes | |
| maxSize | No | Maximum 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) | |
| snippetLength | No | Length of text snippet around matches | |
| maxResults | No | Maximum number of match results to return | |
| sortBy | No | How to sort the results | relevance |
| groupByFile | No | Whether to group results by file | |
| excludeComments | No | Whether to exclude comments from search (language-aware) | |
| excludeStrings | No | Whether to exclude string literals from search | |
| outputFormat | No | Output format for results | text |
Implementation Reference
- src/search.ts:525-629 (handler)Main handler function for executing the search_files tool. Validates inputs, sets defaults, performs recursive file search, sorts and formats results.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)Tool schema definition including name, description, and detailed inputSchema with all parameters for the search_files tool.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 (via searchTool) in the MCP server's listTools handler.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ exploreProjectTool, listAllowedTool, searchTool, renameFileTool, deleteFileTool, checkOutdatedTool ] }; });
- src/index.ts:59-60 (registration)Dispatcher case in CallToolRequestSchema handler that routes 'search_files' calls to the handleSearch function.case "search_files": return await handleSearch(args, ALLOWED_DIRECTORIES);
- src/search.ts:257-329 (helper)Core helper function that recursively searches directories for files matching the search criteria.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; }