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;
    };
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It adds useful context about recursive listing and output limiting, but doesn't cover important aspects like error handling, permission requirements, performance implications for large directories, or what the return structure looks like. It's adequate but has clear gaps for a read operation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured in three sentences that each add value: stating the core purpose, explaining optional recursive behavior, and detailing the maxEntries parameter. There's no wasted language or redundancy, making it easy to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only tool with no output schema and no annotations, the description provides basic operational information but lacks details about return format, error conditions, or performance characteristics. It's minimally viable but doesn't fully compensate for the missing structured metadata that would help an agent understand the complete behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters thoroughly. The description mentions the maxEntries parameter and its default, and implies recursive functionality, but doesn't add significant meaning beyond what the schema provides. This meets the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Lists files and directories') and resource ('within the specified directory'), distinguishing it from siblings like copy_path, delete_file, or read_file that perform different operations on files. It precisely communicates the tool's function without being vague or tautological.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for listing directory contents but doesn't explicitly state when to use this tool versus alternatives like read_file or other file operations. It mentions optional parameters but provides no guidance on scenarios where this tool is preferred over others or any prerequisites for use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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

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