read_text_file
Extract and read the contents of a file as text, including options for first or last N lines. Handles various encodings and provides clear error messages for file access issues.
Instructions
Read the complete contents of a file from the file system as text. Handles various text encodings and provides detailed error messages if the file cannot be read. Use this tool when you need to examine the contents of a single file. Use the 'head' parameter to read only the first N lines of a file, or the 'tail' parameter to read only the last N lines of a file. Operates on the file as text regardless of extension. Only works within allowed directories.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| head | No | If provided, returns only the first N lines of the file | |
| path | Yes | ||
| tail | No | If provided, returns only the last N lines of the file |
Input Schema (JSON Schema)
Implementation Reference
- src/filesystem/index.ts:172-192 (handler)The handler function that executes the read_text_file tool. It validates the path, checks for conflicting head/tail parameters, and reads the full file content or the first/last N lines using helper functions.const readTextFileHandler = async (args: z.infer<typeof ReadTextFileArgsSchema>) => { const validPath = await validatePath(args.path); if (args.head && args.tail) { throw new Error("Cannot specify both head and tail parameters simultaneously"); } let content: string; if (args.tail) { content = await tailFile(validPath, args.tail); } else if (args.head) { content = await headFile(validPath, args.head); } else { content = await readFileContent(validPath); } return { content: [{ type: "text" as const, text: content }], structuredContent: { content } }; };
- src/filesystem/index.ts:77-81 (schema)Zod schema defining the input arguments for the read_text_file tool (path, optional tail lines, optional head lines). Used for the deprecated read_file tool and shares structure with the inline schema in registration.const ReadTextFileArgsSchema = z.object({ path: z.string(), tail: z.number().optional().describe('If provided, returns only the last N lines of the file'), head: z.number().optional().describe('If provided, returns only the first N lines of the file') });
- src/filesystem/index.ts:206-227 (registration)Registration of the read_text_file tool with MCP server, including title, detailed description, input/output schemas, annotations, and the handler function.server.registerTool( "read_text_file", { title: "Read Text File", description: "Read the complete contents of a file from the file system as text. " + "Handles various text encodings and provides detailed error messages " + "if the file cannot be read. Use this tool when you need to examine " + "the contents of a single file. Use the 'head' parameter to read only " + "the first N lines of a file, or the 'tail' parameter to read only " + "the last N lines of a file. Operates on the file as text regardless of extension. " + "Only works within allowed directories.", inputSchema: { path: z.string(), tail: z.number().optional().describe("If provided, returns only the last N lines of the file"), head: z.number().optional().describe("If provided, returns only the first N lines of the file") }, outputSchema: { content: z.string() }, annotations: { readOnlyHint: true } }, readTextFileHandler );
- src/filesystem/lib.ts:134-136 (helper)Core helper function to read the full content of a text file using Node.js fs.readFile with UTF-8 encoding. Called by the handler when neither head nor tail is specified.export async function readFileContent(filePath: string, encoding: string = 'utf-8'): Promise<string> { return await fs.readFile(filePath, encoding as BufferEncoding); }
- src/filesystem/lib.ts:76-117 (helper)Security-focused path validation helper that resolves, normalizes, checks against allowed directories, handles symlinks, and verifies parent dirs for non-existent files. Called first in the handler.export async function validatePath(requestedPath: string): Promise<string> { const expandedPath = expandHome(requestedPath); const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) : path.resolve(process.cwd(), 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; } }