Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 36 additions & 18 deletions package-lock.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear if the changes to the lockfile are necessary for this change since no dependencies are added/changed.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/handlers/filesystem-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export async function handleReadFile(args: unknown): Promise<ServerResult> {
const textContent = typeof fileResult.content === 'string'
? fileResult.content
: fileResult.content.toString('utf8');
const fileType = resolvePreviewFileType(resolvedFilePath);
const fileType = fileResult.metadata?.isDirectory ? 'directory' as const : resolvePreviewFileType(resolvedFilePath);
return {
content: [{ type: "text", text: textContent }],
structuredContent: {
Expand Down Expand Up @@ -326,9 +326,15 @@ export async function handleListDirectory(args: unknown): Promise<ServerResult>
const duration = Date.now() - startTime;

const resultText = entries.join('\n');
const resolvedPath = resolveAbsolutePath(parsed.path);

return {
content: [{ type: "text", text: resultText }],
structuredContent: {
fileName: path.basename(resolvedPath),
filePath: resolvedPath,
fileType: 'directory' as const,
},
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
Expand Down
1 change: 1 addition & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
${PATH_GUIDANCE}
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
annotations: {
title: "List Directory Contents",
readOnlyHint: true,
Expand Down
34 changes: 34 additions & 0 deletions src/tools/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,40 @@ export async function readFileFromDisk(
// Get file extension for telemetry
const fileExtension = getFileExtension(validPath);

// Check if path is a directory — return listing instead of EISDIR error
try {
const stats = await fs.stat(validPath);
if (stats.isDirectory()) {
const dirListOp = async () => {
const entries = await listDirectory(validPath);
const listing = entries.join('\n');
return {
content: `This is a directory, not a file. Use the list_directory tool instead of read_file for directories.\n\n${listing}`,
mimeType: 'text/plain',
metadata: { isImage: false, isDirectory: true }
} as FileResult;
};
const dirResult = await withTimeout(
dirListOp(),
FILE_OPERATION_TIMEOUTS.FILE_READ,
'Directory listing fallback',
null
);
if (dirResult === null) {
throw new Error(`Directory listing timed out for: ${filePath}`);
}
return dirResult;
}
} catch (error) {
// If stat itself failed, fall through to the read path which will produce a proper error.
// But if this was a directory-listing error, re-throw — don't let it fall into the file-read path.
const err = error as NodeJS.ErrnoException;
if (err.message?.includes('Directory listing') || err.message?.includes('list_directory')) {
throw error;
}
// stat() failed (e.g. ENOENT) — fall through to the read path below
}

// Check file size before attempting to read
try {
const stats = await fs.stat(validPath);
Expand Down
Loading
Loading