everything/tasks: tasks/update checks MAX_ANSWER_BYTES against string length, so non-ASCII answers exceed the documented byte budget

Chiusa Adatta ai principianti
#5,419 1 commento 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@cliffhall ci sta già lavorando.

Dal 5/11/2026.

  • #5449 di @cliffhall — integrata

Valutazione

Difficoltà
1/5
Tempo stimato
Meno di un'ora
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
typescript
Ambito
backend

Direzione di ricerca

Il controllo delle dimensioni si trova nel gestore tasks/update in src/everything/server/tasks.ts, intorno alle righe 411-424, dove JSON.stringify(answered).length viene confrontato con MAX_ANSWER_BYTES. Leggi quel blocco, poi riproduci il problema inviando una risposta di 33.000 caratteri é e verificando che venga accettata. L'obiettivo è raggiunto quando la misura conta i byte UTF-8, così che quel payload riceva un errore -32602 e l'attività resti invariata.

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

Descrizione

bug server-everything v2
Field Value
Labels (suggested) bug, everything, tasks
Reproduced on commit 163a5e935756 (default branch at the time of analysis)
Introduced by commit 27b2d2415cdc0ad13782edb8b6c3cecd7846b78a — "feat(everything): serve both protocol eras and demonstrate …"

Summary

The everything server's tasks/update handler limits the size of client-supplied answers with
MAX_ANSWER_BYTES, but computes the size with JSON.stringify(answered).length. In JavaScript that is
the number of UTF-16 code units, not bytes. For any non-ASCII answer the guard under-counts by up to
3× and lets a payload that is larger than the documented byte budget through, after which the oversized
answers are handed to task.behavior.receive() and retained in the task.

Affected code

src/everything/server/tasks.ts, lines 411–424 at 163a5e935756:

      const answered = Object.fromEntries(
        Object.entries(inputResponses).filter(([key]) =>
          outstanding.includes(key),
        ),
      );
      const size = JSON.stringify(answered).length;   // <- UTF-16 code units, not bytes
      if (size > MAX_ANSWER_BYTES) {
        throw new ProtocolError(
          ProtocolErrorCode.InvalidParams,
          `Invalid params for tasks/update: the answers are ${size} bytes, over the ${MAX_ANSWER_BYTES}-byte limit; the task is unchanged`,
        );
      }
      if (Object.keys(answered).length > 0) {
        task.behavior.receive(answered, now);
      }

The constant is named MAX_ANSWER_BYTES and the error message promises a byte comparison
("the answers are N bytes"), so the unit mismatch is internal to the handler, not a documentation
question.

Expected behavior

tasks/update rejects the request with -32602 Invalid params because the serialized answers are
66 012 bytes, which is over the 65 536-byte budget; the task is left unchanged.

Actual behavior

Calling tasks/update with one outstanding answer whose text is 33 000 é characters, the handler accepts the payload and retains it in the task (MAX_ANSWER_BYTES = 65536).

Payload: one outstanding answer whose text is 33 000 U+00E9 (é) characters.
MAX_ANSWER_BYTES = 65536.

{"mode":"bug","outcome":{"accepted":true,"received":66012}}
TASKS_UPDATE_BYTE_BUDGET_BYPASSED
  • JSON.stringify(...).length = 33 012 → passes the guard.
  • UTF-8 Buffer.byteLength(..., "utf8") = 66 012 → 476 bytes over the limit.
  • The task retained the answer (received: 66012), so the documented limit was not enforced.

Control: the same probe against a copy of tasks.ts with line 416 changed to
Buffer.byteLength(JSON.stringify(answered), "utf8") produces

{"mode":"control","outcome":{"accepted":false,
  "error":"-32602: Invalid params for tasks/update: the answers are 66012 bytes, over the 65536-byte limit; the task is unchanged"}}
CONTROL_REJECTED_BY_BYTES

and retained is undefined — the divergence disappears with that one line.

Why it matters

  • The limit is a resource guard for retained task state (the answer is stored until the task settles).
    Non-ASCII answers — ordinary for any non-English client, or for base64/emoji content — get up to a 3×
    larger effective budget. 33 000 characters of CJK/Cyrillic/emoji-style text becomes ~99 KB against a
    stated 64 KiB ceiling.
  • The rejection message itself reports a byte count that is not a byte count, which makes the failure
    mode confusing for client authors.

Suggested fix

Use a byte-accurate measure:

const size = Buffer.byteLength(JSON.stringify(answered), "utf8");

(new TextEncoder().encode(...).length if the file must stay runtime-agnostic). Note that both forms
count the UTF-8 serialization of the JSON text, which is what the limit documents.

Lingua principale
TypeScript
Stelle
91.7k
Fork
11.9k
Merge medio
3h 30m
PR unite (30g)
204

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.