Skip to main content
Glama

list_files

Lists files and directories within a specified path, optionally recursively, and limits the number of entries returned. Ideal for managing and navigating filesystem structures.

Instructions

Lists files and directories within the specified directory. Optionally lists recursively and returns a tree-like structure. Includes an optional maxEntries parameter (default 50) to limit the number of items returned.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
includeNestedNoIf true, list files and directories recursively. Defaults to false (top-level only).
maxEntriesNoMaximum number of directory entries (files + folders) to return. Defaults to 50. Helps prevent excessive output for large directories.
pathYesThe path to the directory to list. Can be relative or absolute (resolved like readFile).

Implementation Reference

  • Core handler function that executes the 'list_files' tool logic: resolves path using serverState, reads directory recursively with limits, formats tree output, handles errors.
    export const listFilesLogic = async (input: ListFilesInput, context: RequestContext): Promise<ListFilesOutput> => { // Destructure validated input, including the new maxEntries const { path: requestedPath, includeNested, maxEntries } = input; const logicContext = { ...context, includeNested, maxEntries }; logger.debug(`listFilesLogic: Received request for path "${requestedPath}" with limit ${maxEntries}`, logicContext); // Resolve the path const absolutePath = serverState.resolvePath(requestedPath, context); logger.debug(`listFilesLogic: Resolved path to "${absolutePath}"`, { ...logicContext, requestedPath }); try { // Initialize state for tracking count and limit, using the potentially updated default const state = { count: 0, limit: maxEntries, truncated: false }; // Read directory structure using the state object const items = await readDirectoryRecursive(absolutePath, includeNested, logicContext, state); // Format the tree, passing the final truncated state const rootName = path.basename(absolutePath); const tree = `📁 ${rootName}\n` + formatTree(items, state.truncated); // Pass truncated flag const message = state.truncated ? `Successfully listed ${state.count} items in ${absolutePath} (truncated at limit of ${maxEntries}).` // Use maxEntries from input for message : `Successfully listed ${state.count} items in ${absolutePath}.`; logger.info(`listFilesLogic: ${message}`, { ...logicContext, requestedPath, itemCount: state.count, truncated: state.truncated, limit: maxEntries }); return { message: message, tree: tree, requestedPath: requestedPath, resolvedPath: absolutePath, itemCount: state.count, // Return the actual count processed truncated: state.truncated, }; } catch (error: any) { // Errors during readDirectoryRecursive are already logged and potentially thrown as McpError logger.error(`listFilesLogic: Error listing files at "${absolutePath}"`, { ...logicContext, requestedPath, error: error.message, code: error.code }); if (error instanceof McpError) { throw error; // Re-throw known McpErrors } // Catch any other unexpected errors throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to list files: ${error.message || 'Unknown I/O error'}`, { ...context, requestedPath, resolvedPath: absolutePath, originalError: error }); } };
  • Zod schema for validating input parameters to the list_files tool: path (required string), includeNested (boolean, default false), maxEntries (number, default 50).
    export const ListFilesInputSchema = z.object({ path: z.string().min(1, 'Path cannot be empty') .describe('The path to the directory to list. Can be relative or absolute (resolved like readFile).'), includeNested: z.boolean().default(false) .describe('If true, list files and directories recursively. Defaults to false (top-level only).'), maxEntries: z.number().int().positive().optional().default(50) // Updated default to 50 .describe('Maximum number of directory entries (files + folders) to return. Defaults to 50. Helps prevent excessive output for large directories.'), });
  • Function to register the 'list_files' tool on the MCP server, including inline handler that validates input and delegates to listFilesLogic.
    export const registerListFilesTool = async (server: McpServer): Promise<void> => { const registrationContext = requestContextService.createRequestContext({ operation: 'RegisterListFilesTool' }); logger.info("Attempting to register 'list_files' tool", registrationContext); await ErrorHandler.tryCatch( async () => { server.tool( 'list_files', // Tool name 'Lists files and directories within the specified directory. Optionally lists recursively and returns a tree-like structure. Includes an optional `maxEntries` parameter (default 50) to limit the number of items returned.', // Updated Description ListFilesInputSchema.shape, // Pass the schema shape (already updated in logic file) async (params, extra) => { // Validate input using the Zod schema const validationResult = ListFilesInputSchema.safeParse(params); if (!validationResult.success) { // Create context without explicit parentRequestId const errorContext = requestContextService.createRequestContext({ operation: 'ListFilesToolValidation' }); logger.error('Invalid input parameters for list_files tool', { ...errorContext, errors: validationResult.error.errors }); throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid parameters: ${validationResult.error.errors.map(e => `${e.path.join('.')} - ${e.message}`).join(', ')}`, errorContext); } const typedParams = validationResult.data; // Use validated data // Create context for this execution without explicit parentRequestId const callContext = requestContextService.createRequestContext({ operation: 'ListFilesToolExecution' }); logger.info(`Executing 'list_files' tool for path: ${typedParams.path}, nested: ${typedParams.includeNested}`, callContext); // Call the logic function const result = await ErrorHandler.tryCatch( () => listFilesLogic(typedParams, callContext), { operation: 'listFilesLogic', context: callContext, input: sanitization.sanitizeForLogging(typedParams), // Sanitize input for logging errorCode: BaseErrorCode.INTERNAL_ERROR } ); logger.info(`Successfully executed 'list_files' for path: ${result.resolvedPath}. Items: ${result.itemCount}`, callContext); // Format the successful response - return the tree structure return { content: [{ type: 'text', text: result.tree }], }; } ); logger.info("'list_files' tool registered successfully", registrationContext); }, { operation: 'registerListFilesTool', context: registrationContext, errorCode: BaseErrorCode.CONFIGURATION_ERROR, critical: true // Critical for server startup } ); };
  • Invocation of registerListFilesTool as part of registering all filesystem tools during server initialization.
    logger.debug("Registering filesystem tools...", context); const registrationPromises = [ registerReadFileTool(server), registerSetFilesystemDefaultTool(server), registerWriteFileTool(server), registerUpdateFileTool(server), registerListFilesTool(server), registerDeleteFileTool(server), registerDeleteDirectoryTool(server), registerCreateDirectoryTool(server), registerMovePathTool(server), registerCopyPathTool(server) ]; await Promise.all(registrationPromises); logger.info("Filesystem tools registered successfully", context);
  • Helper function for recursively reading directory contents, enforcing maxEntries limit, sorting items (dirs first), handling subdir errors gracefully.
    const readDirectoryRecursive = async ( dirPath: string, includeNested: boolean, context: RequestContext, state: { count: number; limit: number; truncated: boolean } // Pass state object ): Promise<DirectoryItem[]> => { if (state.truncated || state.count >= state.limit) { state.truncated = true; // Ensure truncated flag is set if limit reached before starting return []; // Stop processing if limit already reached } const items: DirectoryItem[] = []; let entries; try { entries = await fs.readdir(dirPath, { withFileTypes: true }); } catch (error: any) { if (error.code === 'ENOENT') { logger.warning(`Directory not found: ${dirPath}`, context); throw new McpError(BaseErrorCode.NOT_FOUND, `Directory not found at path: ${dirPath}`, { ...context, dirPath, originalError: error }); } else if (error.code === 'ENOTDIR') { logger.warning(`Path is not a directory: ${dirPath}`, context); throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Path is not a directory: ${dirPath}`, { ...context, dirPath, originalError: error }); } logger.error(`Failed to read directory: ${dirPath}`, { ...context, error: error.message }); throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to read directory: ${error.message}`, { ...context, dirPath, originalError: error }); } for (const entry of entries) { if (state.count >= state.limit) { state.truncated = true; logger.debug(`Max entries limit (${state.limit}) reached while processing ${dirPath}`, context); break; // Stop processing entries in this directory } state.count++; // Increment count for this entry const itemPath = path.join(dirPath, entry.name); const item: DirectoryItem = { name: entry.name, isDirectory: entry.isDirectory(), }; if (item.isDirectory && includeNested) { // Recursively read subdirectory, passing the shared state object try { // Pass the same state object down item.children = await readDirectoryRecursive(itemPath, includeNested, { ...context, parentPath: dirPath }, state); } catch (recursiveError) { // Log the error from the recursive call but continue processing other entries logger.error(`Error reading nested directory ${itemPath}`, { ...context, error: (recursiveError as Error).message, code: (recursiveError as McpError).code }); // Log the error and mark the item const errorMessage = (recursiveError as McpError)?.message || (recursiveError as Error)?.message || 'Unknown error reading directory'; logger.error(`Error reading nested directory ${itemPath}`, { ...context, error: errorMessage, code: (recursiveError as McpError)?.code }); item.error = errorMessage; // Store the error message on the item item.children = undefined; // Ensure no children are processed or displayed for errored directories } } items.push(item); // Check limit again after potentially adding children (though count is incremented per item) if (state.truncated) { break; // Exit loop if limit was hit during recursive call } } // Sort items: directories first, then files, alphabetically items.sort((a, b) => { if (a.isDirectory !== b.isDirectory) { return a.isDirectory ? -1 : 1; // Directories first } return a.name.localeCompare(b.name); // Then sort alphabetically }); return items; };

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/cyanheads/filesystem-mcp-server'

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