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

クローズ 初心者向け
#5,419 コメント 1 件 リアクション 0 件 担当者 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時間 30分
マージ済み PR(30日)
204

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

modelcontextprotocol/servers のほかの issue

modelcontextprotocol/servers の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。