Read Text File
read_text_fileReads a file's complete text content. Optionally, specify head or tail to read only the first or last N lines.
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 |
|---|---|---|---|
| path | Yes | ||
| tail | No | If provided, returns only the last N lines of the file | |
| head | No | If provided, returns only the first N lines of the file |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes |
Implementation Reference
- src/filesystem/index.ts:191-211 (handler)The main handler function for the read_text_file tool. It validates the path, then reads the file content (optionally using head/tail for partial reads), and returns the result as text content.
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:96-100 (schema)Zod schema defining the input arguments for read_text_file: path (string, required), tail (optional number for last N lines), head (optional number for first N lines).
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:225-246 (registration)Registration of the 'read_text_file' tool with the MCP server, including its title, description, input schema, output schema, and the readTextFileHandler.
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:157-159 (helper)Helper function that reads the entire file content 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:285-334 (helper)Helper function that efficiently reads the last N lines of a file by reading chunks from the end.
export async function tailFile(filePath: string, numLines: number): Promise<string> { const CHUNK_SIZE = 1024; // Read 1KB at a time const stats = await fs.stat(filePath); const fileSize = stats.size; if (fileSize === 0) return ''; // Open file for reading const fileHandle = await fs.open(filePath, 'r'); try { const lines: string[] = []; let position = fileSize; let chunk = Buffer.alloc(CHUNK_SIZE); let linesFound = 0; let remainingText = ''; // Read chunks from the end of the file until we have enough lines while (position > 0 && linesFound < numLines) { const size = Math.min(CHUNK_SIZE, position); position -= size; const { bytesRead } = await fileHandle.read(chunk, 0, size, position); if (!bytesRead) break; // Get the chunk as a string and prepend any remaining text from previous iteration const readData = chunk.slice(0, bytesRead).toString('utf-8'); const chunkText = readData + remainingText; // Split by newlines and count const chunkLines = normalizeLineEndings(chunkText).split('\n'); // If this isn't the end of the file, the first line is likely incomplete // Save it to prepend to the next chunk if (position > 0) { remainingText = chunkLines[0]; chunkLines.shift(); // Remove the first (incomplete) line } // Add lines to our result (up to the number we need) for (let i = chunkLines.length - 1; i >= 0 && linesFound < numLines; i--) { lines.unshift(chunkLines[i]); linesFound++; } } return lines.join('\n'); } finally { await fileHandle.close(); } }