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

未关闭 适合新手
#5,419 1 条评论 0 个 reaction 已指派 1 人 在 GitHub 查看

维护者通常 1 天内回复

@cliffhall 已经在做这个了。

开始于 2026年11月5日。

  • #5449 来自 @cliffhall —— 未关闭

评估

难度
1/5
预计耗时
1 小时以内
新手友好度
78/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
typescript
领域
backend

调研方向

大小检查位于 src/everything/server/tasks.ts 中 tasks/update 处理程序的第 411-424 行附近,其中 JSON.stringify(answered).length 与 MAX_ANSWER_BYTES 进行比较。请先阅读该代码块,然后发送一个包含 33,000 个 é 字符的答案以复现问题,并确认它被接受。当度量按 UTF-8 字节计数,使该负载返回 -32602 错误且任务保持不变时,即完成。

由索引模型根据 Issue 内容生成。

描述

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.

主要语言
TypeScript
星标
91.7k
派生
11.9k
平均合并
3 小时 48 分钟
30 天内合并 PR
180

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

modelcontextprotocol/servers 的其他 Issue

查看 modelcontextprotocol/servers 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。