Skip to content

Commit 6e1b5a9

Browse files
authored
Add recursive depth dir listing (#250)
1 parent 53329c5 commit 6e1b5a9

File tree

5 files changed

+64
-7
lines changed

5 files changed

+64
-7
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/handlers/filesystem-handlers.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,15 @@ export async function handleCreateDirectory(args: unknown): Promise<ServerResult
209209
*/
210210
export async function handleListDirectory(args: unknown): Promise<ServerResult> {
211211
try {
212+
const startTime = Date.now();
212213
const parsed = ListDirectoryArgsSchema.parse(args);
213-
const entries = await listDirectory(parsed.path);
214+
const entries = await listDirectory(parsed.path, parsed.depth);
215+
const duration = Date.now() - startTime;
216+
217+
const resultText = entries.join('\n');
218+
214219
return {
215-
content: [{ type: "text", text: entries.join('\n') }],
220+
content: [{ type: "text", text: resultText }],
216221
};
217222
} catch (error) {
218223
const errorMessage = error instanceof Error ? error.message : String(error);

src/server.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
324324
325325
Use this instead of 'execute_command' with ls/dir commands.
326326
Results distinguish between files and directories with [FILE] and [DIR] prefixes.
327+
328+
Supports recursive listing with the 'depth' parameter (default: 2):
329+
- depth=1: Only direct contents of the directory
330+
- depth=2: Contents plus one level of subdirectories
331+
- depth=3+: Multiple levels deep
332+
333+
Results show full relative paths from the root directory being listed.
334+
Example output with depth=2:
335+
[DIR] src
336+
[FILE] src/index.ts
337+
[DIR] src/tools
338+
[FILE] src/tools/filesystem.ts
339+
340+
If a directory cannot be accessed, it will show [DENIED] instead.
327341
Only works within allowed directories.
328342
329343
${PATH_GUIDANCE}

src/tools/filesystem.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -890,10 +890,47 @@ export async function createDirectory(dirPath: string): Promise<void> {
890890
await fs.mkdir(validPath, { recursive: true });
891891
}
892892

893-
export async function listDirectory(dirPath: string): Promise<string[]> {
893+
export async function listDirectory(dirPath: string, depth: number = 2): Promise<string[]> {
894894
const validPath = await validatePath(dirPath);
895-
const entries = await fs.readdir(validPath, { withFileTypes: true });
896-
return entries.map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`);
895+
const results: string[] = [];
896+
897+
async function listRecursive(currentPath: string, currentDepth: number, relativePath: string = ''): Promise<void> {
898+
if (currentDepth <= 0) return;
899+
900+
let entries;
901+
try {
902+
entries = await fs.readdir(currentPath, { withFileTypes: true });
903+
} catch (error) {
904+
// If we can't read this directory (permission denied), show as denied
905+
const displayPath = relativePath || path.basename(currentPath);
906+
results.push(`[DENIED] ${displayPath}`);
907+
return;
908+
}
909+
910+
for (const entry of entries) {
911+
const fullPath = path.join(currentPath, entry.name);
912+
const displayPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
913+
914+
// Add this entry to results
915+
results.push(`${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${displayPath}`);
916+
917+
// If it's a directory and we have depth remaining, recurse
918+
if (entry.isDirectory() && currentDepth > 1) {
919+
try {
920+
// Validate the path before recursing
921+
await validatePath(fullPath);
922+
await listRecursive(fullPath, currentDepth - 1, displayPath);
923+
} catch (error) {
924+
// If validation fails or we can't access it, it will be marked as denied
925+
// when we try to read it in the recursive call
926+
continue;
927+
}
928+
}
929+
}
930+
}
931+
932+
await listRecursive(validPath, depth);
933+
return results;
897934
}
898935

899936
export async function moveFile(sourcePath: string, destinationPath: string): Promise<void> {

src/tools/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const CreateDirectoryArgsSchema = z.object({
6363

6464
export const ListDirectoryArgsSchema = z.object({
6565
path: z.string(),
66+
depth: z.number().optional().default(2),
6667
});
6768

6869
export const MoveFileArgsSchema = z.object({

0 commit comments

Comments
 (0)