Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

StdioTransport.send() still interleaves concurrent sends under EAGAIN backpressure — #252 was closed without a merged fix

Open
#263 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
58/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
swift
Domain
networking

Research direction

Start at StdioTransport.send() and inspect the write loop around the EAGAIN backpressure sleep and actor suspension. Reproduce with a slowly drained pipe, a large send, and a concurrent small send; done means concurrent newline-delimited JSON frames remain intact while backpressure is retried.

Written by the indexing model from the issue text.

Description

bug

Summary

StdioTransport.send() can splice the bytes of one JSON-RPC frame into the
middle of another when two sends run concurrently and the first hits stdout
backpressure. This corrupts the newline-delimited framing for any client
reading the stream.

This is the same defect reported in #252 (2026-07-02). That issue was closed
by its reporter the next day with no linked commit or PR, and the code on
main today is unchanged — the bug is still present. Filing fresh with an
independent, in-the-wild reproduction.

Root cause

StdioTransport is an actor, but send(_:) contains an await inside its
write loop — the EAGAIN backpressure sleep:

} catch let error where MCPError.isResourceTemporarilyUnavailable(error) {
    try await Task.sleep(for: .milliseconds(10))   // ← actor reentrancy point
    continue
}

Actor isolation guarantees mutual exclusion only between suspension
points. While one send is suspended in that sleep mid-frame, a second
send enters the actor and writes its complete frame into the middle of the
first one.

Observed in the wild

Version 0.12.1, macOS 15 (Darwin 25.5), a stdio MCP server whose
initialize result carries a ~1.9 KB instructions string. A pipelining
client sent initialize + a tools/call back-to-back; the captured stdout
shows the tool response spliced mid-word into the initialize result:

...has not made any bucket visi{"id":2,"jsonrpc":"2.0","result":{...}}
ble to agents yet...

Both frames are unparseable to a line-delimited JSON reader at that point.
Any client that issues parallel tool calls (several do) can trigger this
whenever a response exceeds what the pipe accepts in one write — no
misbehaving client required.

Reproduction sketch

Same shape as #252's: create the transport over a pipe the reader drains
slowly, start a send large enough to hit EAGAIN (hundreds of KB, or a small
pipe), and issue a second small send while the first is suspended. The small
frame lands inside the large one.

Suggested fix

Serialize sends so one completes before the next begins — either drop to a
blocking write for the remainder of a frame, or chain sends FIFO. We shipped
the FIFO as a wrapper in our server and it eliminates the corruption:

private var lastSend: Task<Void, Never>?

func send(_ data: Data) async throws {
    let previous = lastSend
    let task = Task<Void, Error> { [base] in
        await previous?.value
        try await base.send(data)
    }
    lastSend = Task { try? await task.value }
    try await task.value
}

The same pattern inlined into StdioTransport.send (chain before the write
loop) would fix it at the source. Happy to turn this into a PR if that's
welcome.

Dominant language
Swift
Stars
1.5k
Forks
246
PR merge metrics
No merged PRs in 30d

Getting set up

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.

More from modelcontextprotocol/swift-sdk

All issues in modelcontextprotocol/swift-sdk

Similar issues

More Swift issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.