Read Multiple Files
read_multiple_filesRead contents of multiple files at once to analyze or compare them. Returns each file's content with its path; individual failures do not halt the operation.
Instructions
Read the contents of multiple files simultaneously. This is more efficient than reading files one by one when you need to analyze or compare multiple files. Each file's content is returned with its path as a reference. Failed reads for individual files won't stop the entire operation. Only works within allowed directories.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| paths | Yes | Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes |
Implementation Reference
- src/filesystem/index.ts:106-111 (schema)Zod schema for ReadMultipleFilesArgs - validates that the 'paths' input is an array of strings with at least 1 element.
const ReadMultipleFilesArgsSchema = z.object({ paths: z .array(z.string()) .min(1, "At least one file path must be provided") .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories."), }); - src/filesystem/index.ts:300-337 (registration)Registration of the 'read_multiple_files' tool with the MCP server, including title, description, input/output schemas, and annotations.
server.registerTool( "read_multiple_files", { title: "Read Multiple Files", description: "Read the contents of multiple files simultaneously. This is more " + "efficient than reading files one by one when you need to analyze " + "or compare multiple files. Each file's content is returned with its " + "path as a reference. Failed reads for individual files won't stop " + "the entire operation. Only works within allowed directories.", inputSchema: { paths: z.array(z.string()) .min(1) .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories.") }, outputSchema: { content: z.string() }, annotations: { readOnlyHint: true } }, async (args: z.infer<typeof ReadMultipleFilesArgsSchema>) => { const results = await Promise.all( args.paths.map(async (filePath: string) => { try { const validPath = await validatePath(filePath); const content = await readFileContent(validPath); return `${filePath}:\n${content}\n`; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return `${filePath}: Error - ${errorMessage}`; } }), ); const text = results.join("\n---\n"); return { content: [{ type: "text" as const, text }], structuredContent: { content: text } }; } ); - src/filesystem/index.ts:318-337 (handler)Handler function for read_multiple_files - reads multiple files concurrently using Promise.all, validates each path, returns content with path prefix per file, with error handling per file.
async (args: z.infer<typeof ReadMultipleFilesArgsSchema>) => { const results = await Promise.all( args.paths.map(async (filePath: string) => { try { const validPath = await validatePath(filePath); const content = await readFileContent(validPath); return `${filePath}:\n${content}\n`; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return `${filePath}: Error - ${errorMessage}`; } }), ); const text = results.join("\n---\n"); return { content: [{ type: "text" as const, text }], structuredContent: { content: text } }; } ); - src/filesystem/lib.ts:157-159 (helper)The readFileContent helper function that reads file content from disk using fs.readFile with UTF-8 encoding.
export async function readFileContent(filePath: string, encoding: string = 'utf-8'): Promise<string> { return await fs.readFile(filePath, encoding as BufferEncoding); } - src/filesystem/lib.ts:99-140 (helper)The validatePath helper function that resolves and validates that a requested path is within allowed directories, following symlinks for security.
export async function validatePath(requestedPath: string): Promise<string> { const expandedPath = expandHome(requestedPath); const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) : resolveRelativePathAgainstAllowedDirectories(expandedPath); const normalizedRequested = normalizePath(absolute); // Security: Check if path is within allowed directories before any file operations const isAllowed = isPathWithinAllowedDirectories(normalizedRequested, allowedDirectories); if (!isAllowed) { throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`); } // Security: Handle symlinks by checking their real path to prevent symlink attacks // This prevents attackers from creating symlinks that point outside allowed directories try { const realPath = await fs.realpath(absolute); const normalizedReal = normalizePath(realPath); if (!isPathWithinAllowedDirectories(normalizedReal, allowedDirectories)) { throw new Error(`Access denied - symlink target outside allowed directories: ${realPath} not in ${allowedDirectories.join(', ')}`); } return realPath; } catch (error) { // Security: For new files that don't exist yet, verify parent directory // This ensures we can't create files in unauthorized locations if ((error as NodeJS.ErrnoException).code === 'ENOENT') { const parentDir = path.dirname(absolute); try { const realParentPath = await fs.realpath(parentDir); const normalizedParent = normalizePath(realParentPath); if (!isPathWithinAllowedDirectories(normalizedParent, allowedDirectories)) { throw new Error(`Access denied - parent directory outside allowed directories: ${realParentPath} not in ${allowedDirectories.join(', ')}`); } return absolute; } catch { throw new Error(`Parent directory does not exist: ${parentDir}`); } } throw error; } }