modelcontextprotocol / modelcontextprotocol/typescript-sdk

stdio.ts/ReadBuffer retains consumed input and copies the next stdio chunk

Open Beginner friendly
#2,536 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3 ready for work v1 v2
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

ReadBuffer.readMessage() currently advances the buffer with:

this._buffer = this._buffer.subarray(index + 1);

When the newline is the final byte, subarray() returns an empty Buffer view. The view still references the backing allocation of the accumulated input buffer.

That allocation can contain more than the final message, for example when one input chunk contains multiple messages or uses a pooled buffer. It stays referenced until another chunk is appended, clear() is called, or the transport closes.

There is also a cost on the next append. An empty Buffer is truthy, so this code still takes the concat path:
https://github.com/modelcontextprotocol/typescript-sdk/blob/69749aa5081ddfe675d36da8d96c7e27d83742b8/src/shared/stdio.ts#L22

The next append therefore copies the incoming chunk even though no buffered bytes remain. During that copy, the retained backing allocation, the incoming chunk, and the new concat destination are all live.

This is not an unbounded leak, but it unnecessarily increases idle external memory, peak memory, and copying for long-lived stdio processes. The extra peak memory matters when large messages are allowed or the process runs under a memory limit.

I confirmed this behavior in:

A small fix would clear the buffer when no bytes remain:

const remainder = this._buffer.subarray(index + 1);
this._buffer = remainder.length === 0 ? undefined : remainder;

This does not copy non-empty remainders or change the public API. It releases the consumed backing allocation and lets the next append assign its chunk directly instead of using Buffer.concat().

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ReadBuffer.readMessage() and the append logic in src/shared/stdio.ts, then compare the corresponding entry point in packages/core-internal/src/shared/stdio.ts. Verify that an exhausted buffer releases its backing allocation and that the next append avoids concatenation, while non-empty remainders remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
performance
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.