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

Cerrado Apto para principiantes
#5,419 1 comentario 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@cliffhall ya está trabajando en esto.

Desde el 5/11/2026.

  • #5449 de @cliffhall — fusionado

Evaluación

Dificultad
1/5
Tiempo estimado
Menos de una hora
Aptitud para principiantes
78/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
typescript
Área
backend

Línea de trabajo

La comprobación de tamaño está en el manejador tasks/update de src/everything/server/tasks.ts, alrededor de las líneas 411-424, donde JSON.stringify(answered).length se compara con MAX_ANSWER_BYTES. Lee ese bloque y luego reproduce el problema enviando una respuesta de 33.000 caracteres é y confirmando que se acepta. Está terminado cuando la medición cuenta bytes UTF-8, de modo que esa carga recibe un error -32602 y la tarea queda sin cambios.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
TypeScript
Estrellas
91.7k
Forks
11.9k
Merge medio
3 h 30 min
PR fusionados (30 d)
204

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de modelcontextprotocol/servers

Todos los issues de modelcontextprotocol/servers

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.