Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

filesystem: read_text_file with tail: N returns N-1 lines (or "" for tail: 1) when the file ends with a newline

Aperta Adatta ai principianti
#5,215 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
79/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
node.js, typescript
Ambito
backend

Direzione di ricerca

Inizia da tailFile intorno alla riga 596 di src/filesystem/lib.ts, poi esamina il ciclo di lettura intorno alla riga 581 e confrontane il comportamento con quello di headFile. Riproduci il problema usando l’esempio autonomo fornito, soprattutto con file più grandi del blocco da 1 KB e con terminazioni di riga LF e CRLF. Il lavoro è completato quando tail: N restituisce le ultime N righe complete senza un carattere di nuova riga finale superfluo, anche per i file che terminano con una nuova riga.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug v2
Which server?

everything (@modelcontextprotocol/server-everything)

Server version

@modelcontextprotocol/server-filesystem 1.0.0 (npm)

How did you run the server?

npx (npm package)

Transport

stdio

Protocol era

Modern (2026-07-28)

MCP client and version

pytest-mcptest 0.1.1 (built on the official mcp Python SDK 2.3.0), over stdio. The bug doesn't depend on the client: the standalone repro in "Steps to reproduce" calls tailFile directly and shows the same result without any MCP client.

Operating system and runtime

Linux Node version: 22.22.0

Server configuration

Summary
read_text_file with tail: N treats the empty string after a file's final \n as a line. Files ending in a newline (almost every text file) get one line fewer than requested, plus a stray trailing \n. tail: 1 returns an empty string.

PR #4977 pins this as a quirk ("tail counts the trailing newline as a line"), but I couldn't find an issue for it. The tool description says tail "returns only the last N lines of the file", and head: N on the same file does return N lines, so this looks like a bug rather than intended behavior.

Steps to reproduce
  1. Create a file that ends with a newline (as most text files do):
    printf 'alpha\nbeta\ngamma\n' > /tmp/allowed/repro.txt
    
  2. Start the server with that directory allowed:
    npx -y @modelcontextprotocol/[email protected] /tmp/allowed
    
  3. Call read_text_file with {"path": "/tmp/allowed/repro.txt", "tail": 1}.
    • Expected: "gamma"
    • Actual: ""
  4. Call it again with "tail": 2.
    • Expected: "beta\ngamma"
    • Actual: "gamma\n" (only 1 line)
  5. For comparison, "head": 2 correctly returns "alpha\nbeta".

Minimal standalone repro, without an MCP client (run in an empty folder):

npm install @modelcontextprotocol/[email protected]
printf 'alpha\nbeta\ngamma\n' > repro.txt
node --input-type=module -e '
import { tailFile, headFile } from "@modelcontextprotocol/server-filesystem/dist/lib.js";
for (const n of [1, 2, 3]) console.log("tail", n, JSON.stringify(await tailFile("repro.txt", n)));
console.log("head 2", JSON.stringify(await headFile("repro.txt", 2)));
'

Output:

tail 1 ""
tail 2 "gamma\n"
tail 3 "beta\ngamma\n"
head 2 "alpha\nbeta"
Expected behavior

read_text_file with tail: N returns the last N lines of the file, as the tool description says ("returns only the last N lines of the file"). A trailing newline ends the final line; it does not start an extra, empty one. This matches head: N, which returns N lines with no trailing newline.

For a file containing alpha\nbeta\ngamma\n:

  • tail: 1 → "gamma"
  • tail: 2 → "beta\ngamma"
  • tail: 3 → "alpha\nbeta\ngamma"
Actual behavior

When the file ends with a newline, tail counts the empty string after the final \n as a line. So it returns N-1 real lines plus a stray trailing \n, and tail: 1 returns an empty string:

  • tail: 1 → ""
  • tail: 2 → "gamma\n" (1 line instead of 2)
  • tail: 3 → "beta\ngamma\n" (2 lines instead of 3)

Files without a trailing newline are unaffected. Because most text files end with a newline, tail returns one line fewer than requested for almost every file.

Cause: tailFile in src/filesystem/lib.ts (line 596) does text.split("\n").slice(-numLines), and the split leaves a trailing "" element. Simply stripping the trailing newline isn't a complete fix: the read loop (line 581) stops after numLines newlines, so the first returned line can be cut off on files larger than the 1 KB chunk. The full fix and a 2,000-file differential test are in the "Additional context" section below.

Logs or error output

Already prototyped a fix?

Read until there is one more newline than lines wanted, and drop a single trailing newline before splitting:

  •    while (position > 0 && newlinesFound < numLines) {
    
  •    // One more newline than lines wanted, so the earliest returned line is
    
  •    // complete even when the file ends with a newline.
    
  •    while (position > 0 && newlinesFound <= numLines) {
    

@@

  •    const text = normalizeLineEndings(Buffer.concat(chunks).toString("utf-8"));
    
  •    let text = normalizeLineEndings(Buffer.concat(chunks).toString("utf-8"));
    
  •    // A trailing newline terminates the last line; it does not start a new, empty one.
    
  •    if (text.endsWith("\n"))
    
  •        text = text.slice(0, -1);
       return text.split("\n").slice(-numLines).join("\n");
    

This makes tail match head: neither result ends with a newline. The cost is reading at most one extra line from disk.
I ran a differential test with 2,000 random files against a reference ("last N lines; a trailing newline ends the final line"). The files had 1–40 lines, some lines longer than the 1 KB chunk, a mix of LF and CRLF, and about two-thirds ended with a trailing newline.

Before you submit
  • I searched existing issues and this is not a duplicate.
  • This is not a security vulnerability report (those go through the private advisory form).
Lingua principale
TypeScript
Stelle
91.4k
Fork
11.8k
Merge medio
7h 36m
PR unite (30g)
126

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di modelcontextprotocol/servers

Tutte le issue di modelcontextprotocol/servers

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.