Performance of Recursive Directory Listing with Depth in Desktop Commander MCP

Desktop Commander MCP implements a depth-limited recursive directory listing in src/tools/filesystem.ts that caps sub-directory entries at 100 items and uses iterative depth decrementing to prevent unbounded traversal.

Desktop Commander MCP is a Model Context Protocol server that provides secure filesystem access for AI assistants. Understanding the performance of recursive directory listing with depth in MCP is critical for developers building file management tools that must handle large directory trees without blocking the event loop or consuming excessive memory.

How Recursive Directory Listing Works in Desktop Commander MCP

Entry Point and Security Validation

The listDirectory function in src/tools/filesystem.ts serves as the primary entry point. It accepts a dirPath string and an optional depth parameter defaulting to 2. Before any filesystem access, it validates the path through validatePath to enforce MCP's security sandbox.

export async function listDirectory(dirPath: string, depth: number = 2): Promise<string[]> {
  // Security validation and initialization
  validatePath(dirPath);
  const result: string[] = [];
  // Delegation to recursive helper...
}

The Recursive Traversal Engine

The internal listRecursive helper performs the actual filesystem walk. It accepts currentPath, currentDepth, and relativePath parameters, tracking depth through the recursion stack. Each call decrements the depth counter (currentDepth - 1) until reaching zero.

async function listRecursive(
  currentPath: string, 
  currentDepth: number, 
  relativePath: string = '', 
  isTopLevel: boolean = true
): Promise<void> {
  // Depth-controlled traversal logic
}

Depth Control and Safety Limits

The implementation guards against excessive recursion using a simple depth check: if (currentDepth <= 0) return;. This ensures the traversal stops exactly at the requested level, preventing accidental deep crawls of massive directory structures.

The recursion step explicitly passes the decremented depth:

await listRecursive(fullPath, currentDepth - 1, displayPath, false);

Performance Optimizations and Memory Management

Truncation with MAX_NESTED_ITEMS

To prevent memory blow-ups, the code limits non-top-level directories to MAX_NESTED_ITEMS (100 entries). When truncation occurs, the output appends a warning like [WARNING] Projects/Archive: 250 items hidden (showing first 100 of 350 total).

The truncation logic lives at lines 51-53 of src/tools/filesystem.ts:

if (!isTopLevel && totalEntries > MAX_NESTED_ITEMS) {
  // Truncate and append warning marker
}

Lazy I/O and Error Handling

The function uses await fs.readdir for asynchronous directory reading and only calls fs.stat when necessary. Errors translate to markers like [NOT_FOUND] or [DENIED] rather than thrown exceptions, allowing the traversal to continue past inaccessible directories while keeping I/O overhead minimal.

Implementation Examples

Default Two-Level Listing

import { listDirectory } from './src/tools/filesystem.js';

const entries = await listDirectory('/Users/alice/Documents');
console.log(entries.join('\n'));

Result (excerpt):


[DIR] Projects
[FILE] notes.txt
[DIR] Projects/Archive
[FILE] Projects/report.pdf
[WARNING] Projects/Archive: 250 items hidden (showing first 100 of 350 total)

Shallow Listing with Depth 1

const shallow = await listDirectory('/Users/alice/Documents', 1);

Only immediate contents return; sub-directories appear as [DIR] entries but remain un traversed.

Deep Listing with Depth 3

const deep = await listDirectory('/Users/alice/Documents', 3);

The traversal proceeds three levels deep, respecting the truncation rule at each non-top level.

Graceful Error Handling

try {
  const result = await listDirectory('/path/does/not/exist', 2);
  console.log(result);
} catch (e) {
  console.error('Listing failed:', e.message);
}

The function returns [NOT_FOUND] path entries rather than throwing, allowing UI code to present helpful messages.

Summary

  • Depth-limited recursion: The listDirectory function accepts a configurable depth parameter (default 2) and decrements it at each recursive level until reaching zero.
  • Memory protection: Non-top-level directories truncate at 100 items (MAX_NESTED_ITEMS) to prevent O(N) memory consumption in large folders.
  • Non-blocking I/O: All filesystem operations use async fs.readdir and fs.stat, keeping the Node.js event loop responsive.
  • Resilient error handling: Permission errors and missing files generate string markers ([DENIED], [NOT_FOUND]) instead of aborting the entire operation.

Frequently Asked Questions

What is the default recursion depth in Desktop Commander MCP?

The default recursion depth is 2, defined in the listDirectory function signature in src/tools/filesystem.ts. This provides a balance between showing useful context and preventing excessive filesystem scanning.

How does MCP prevent memory issues when listing huge directories?

The implementation limits non-top-level directories to 100 entries using the MAX_NESTED_ITEMS constant. When truncation occurs, the output includes a warning indicating how many items remain hidden, ensuring predictable memory usage regardless of directory size.

Can the recursive listing continue past permission errors?

Yes. According to the source code in src/tools/filesystem.ts, errors from fs.readdir are caught and translated into user-friendly markers like [NOT_FOUND] or [DENIED]. This design allows the traversal to skip inaccessible directories and continue processing siblings rather than aborting the entire operation.

Where does the depth decrement logic reside?

The depth check occurs at lines 27-29 of src/tools/filesystem.ts with the guard clause if (currentDepth <= 0) return;. The recursive call at lines 63-68 passes currentDepth - 1, ensuring the depth limit propagates correctly through the call stack.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →