find
Locate files in your filesystem using glob patterns like **/*.ts, returning matches with metadata for efficient file management.
Instructions
Find files by glob pattern (e.g. **/*.ts). Returns matching files with metadata. For content search, use grep. For bulk edits, pass the same glob to search_and_replace.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Base directory (default: root). Absolute path required if multiple roots. | |
| pattern | Yes | Glob pattern (e.g. "**/*.ts", "src/*.js") | |
| maxResults | No | Max results (1-10000). Default: 100 | |
| includeIgnored | No | Include ignored items (node_modules, etc). | |
| includeHidden | No | Include hidden items (starting with .) | |
| sortBy | No | Sort by path, name, size, or modified | path |
| maxDepth | No | Maximum directory depth to scan | |
| cursor | No | Pagination cursor from a previous response |
Implementation Reference
- src/tools/search-files.ts:50-141 (handler)Core logic for the 'find' tool that searches files based on a pattern.
async function handleSearchFiles( args: z.infer<typeof SearchFilesInputSchema>, signal?: AbortSignal, onProgress?: (progress: { total?: number; current: number }) => void ): Promise<ToolResponse<z.infer<typeof SearchFilesOutputSchema>>> { const basePath = resolvePathOrRoot(args.path); const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS; const cursorOffset = args.cursor !== undefined ? decodeOffsetCursor(args.cursor) : 0; const pageSize = args.maxResults; const fetchMax = cursorOffset + pageSize; const searchOptions: Parameters<typeof searchFiles>[3] = { maxResults: fetchMax, includeHidden: args.includeHidden, sortBy: args.sortBy, respectGitignore: !args.includeIgnored, ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}), ...(onProgress ? { onProgress } : {}), ...(signal ? { signal } : {}), }; const result = await searchFiles( basePath, args.pattern, excludePatterns, searchOptions ); const allResults = result.results; const displayResults = cursorOffset > 0 ? allResults.slice(cursorOffset) : allResults; const nextCursor = result.summary.truncated && displayResults.length > 0 ? encodeOffsetCursor(cursorOffset + displayResults.length) : undefined; const relativeResults: z.infer<typeof SearchFilesOutputSchema>['results'] = []; for (const entry of displayResults) { relativeResults.push({ path: path.relative(result.basePath, entry.path), size: entry.size, modified: entry.modified?.toISOString(), }); } const structured: z.infer<typeof SearchFilesOutputSchema> = { ok: true, root: basePath, results: relativeResults, totalMatches: result.summary.matched, filesScanned: result.summary.filesScanned, ...(result.summary.truncated ? { truncated: result.summary.truncated } : {}), ...(result.summary.skippedInaccessible ? { skippedInaccessible: result.summary.skippedInaccessible } : {}), ...(result.summary.stoppedReason ? { stoppedReason: result.summary.stoppedReason } : {}), ...(nextCursor !== undefined ? { nextCursor } : {}), }; let truncatedReason: string | undefined; if (result.summary.truncated) { if (result.summary.stoppedReason === 'timeout') { truncatedReason = 'timeout'; } else if (result.summary.stoppedReason === 'maxFiles') { truncatedReason = `max files (${result.summary.filesScanned})`; } else { truncatedReason = `max results (${result.summary.matched})`; } } const summaryOptions: Parameters<typeof formatOperationSummary>[0] = { truncated: result.summary.truncated, ...(truncatedReason ? { truncatedReason } : {}), }; const textLines: string[] = []; if (relativeResults.length === 0) { textLines.push('No matches'); } else { textLines.push(`Found ${relativeResults.length}:`); for (const entry of relativeResults) { textLines.push(` ${entry.path}`); } } let text = joinLines(textLines) + formatOperationSummary(summaryOptions); if (nextCursor) { text += `\n[Next page available. Use cursor: "${nextCursor}"]`; } return buildToolResponse(text, structured); } - src/tools/search-files.ts:143-259 (registration)Registration function for the 'find' tool in the MCP server.
export function registerSearchFilesTool( server: McpServer, options: ToolRegistrationOptions = {} ): void { const handler = ( args: z.infer<typeof SearchFilesInputSchema>, extra: ToolExtra ): Promise<ToolResult<z.infer<typeof SearchFilesOutputSchema>>> => executeToolWithDiagnostics({ toolName: 'find', extra, outputSchema: SearchFilesOutputSchema, timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS }, context: { path: args.path ?? '.' }, run: async (signal) => { const rawScopeLabel = args.path ? path.basename(args.path) : '.'; const scopeLabel = rawScopeLabel || '.'; const { pattern } = args; const truncatedPattern = truncateProgressPattern(pattern); const context = `${truncatedPattern} in ${scopeLabel}`; let progressCursor = 0; notifyProgress(extra, { current: 0, message: `🔎︎ find: ${truncatedPattern}`, }); const baseReporter = createProgressReporter(extra); const progressWithMessage = ({ current, total, }: { total?: number; current: number; }): void => { if (current > progressCursor) progressCursor = current; baseReporter({ current, ...(total !== undefined ? { total } : {}), message: `🔎︎ find: ${truncatedPattern} [${current} files]`, }); }; try { const result = await handleSearchFiles( args, signal, progressWithMessage ); const sc = result.structuredContent; const { totalMatches = 0, stoppedReason } = sc; let suffix: string; if (totalMatches === 0) { suffix = 'No matches'; } else { suffix = `${totalMatches} ${totalMatches === 1 ? 'match' : 'matches'}`; if (stoppedReason === 'timeout') { suffix += ' [timeout]'; } else if (stoppedReason === 'maxResults') { suffix += ' [max results]'; } else if (stoppedReason === 'maxFiles') { suffix += ' [max files]'; } } const finalCurrent = Math.max( (sc.filesScanned ?? 0) + 1, progressCursor + 1 ); notifyProgress(extra, { current: finalCurrent, total: finalCurrent, message: `🔎︎ find: ${context} • ${suffix}`, }); return result; } catch (error) { const finalCurrent = Math.max(progressCursor + 1, 1); notifyProgress(extra, { current: finalCurrent, total: finalCurrent, message: `🔎︎ find: ${context} • failed`, }); throw error; } }, onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path), }); const { isInitialized } = options; const wrappedHandler = wrapToolHandler(handler, { guard: isInitialized, }); const validatedHandler = withValidatedArgs( SearchFilesInputSchema, wrappedHandler ); if ( registerToolTaskIfAvailable( server, 'find', SEARCH_FILES_TOOL, validatedHandler, options.iconInfo, isInitialized ) ) return; server.registerTool( 'find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo), validatedHandler ); } - src/tools/search-files.ts:37-48 (schema)Tool definition and schema association for the 'find' tool.
export const SEARCH_FILES_TOOL: ToolContract = { name: 'find', title: 'Find Files', description: 'Find files by glob pattern (e.g. `**/*.ts`). Returns matching files with metadata. ' + 'For content search, use `grep`. For bulk edits, pass the same glob to `search_and_replace`.', inputSchema: SearchFilesInputSchema, outputSchema: SearchFilesOutputSchema, annotations: READ_ONLY_TOOL_ANNOTATIONS, nuances: ['Respects `.gitignore` unless `includeIgnored=true`.'], taskSupport: 'optional', } as const;