You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
stdio transport: -32600 reply for an invalid JSON-RPC frame can be lost (AsyncRwTransport::receive is not cancel-safe) #1353
With the stdio transport (AsyncRwTransport), the -32600 Invalid Request reply that receive() sends for a well-formed JSON line that is not a valid JSON-RPC message is sometimes lost. The bad frame is consumed and never answered; the session otherwise continues normally.
AsyncRwTransport::receive (src/transport/async_rw.rs) clears line_buf (line 160) and then, for a serde_jsonData/Io error, awaits the writer lock (line 179) and sends the -32600 response from inside receive (lines 182-185).
The service loop polls transport.receive() inside tokio::select! together with response_send_tasks.join_next() (src/service.rs, lines 1412-1451). When the bad frame arrives right after a response, that response's send task can still hold the writer lock or be about to complete. If the join_next branch wins, the receive() future is dropped while it waits for the lock or while sending. The line has already been taken out of the buffer, so the error reply is never sent and nothing retries it.
receive is therefore not cancel-safe on this path. The existing comment covers cancellation of read_until only.
Reproduction
Start a minimal stdio server (any ServerHandler) and complete initialize.
Send {"jsonrpc":"2.0","id":1,"method":"ping"} and, without waiting, a line that parses as JSON but is not JSON-RPC, for example [1, 2] or {"id": 3, "method": "ping"} (no jsonrpc member).
Wait for the -32600 reply.
Observed: the reply is missing in roughly 3-12% of attempts (4 of 40 and 2 of 60 in two runs). When the bad line is sent after a 150 ms pause, 0 of 100 replies were lost.
Expected
Every frame that receive decides to answer with -32600 gets that answer, regardless of what else the service loop is doing. Possible fixes: send the error reply outside the cancellable receive future (for example by returning it to the service loop, or via the regular response path), or make the error path cancel-safe by not consuming the line until the reply has been queued.
Related: #1347 (a known method whose params do not deserialize is answered with -32601).
Activity
github-actions commentedon Oct 14, 2026
@bolinfest is on triage rotation this week and is the reviewer for this one.
Codex labeled this bug / P1 (T-transport, T-service). Please confirm or correct those labels.
Automated triage. Labels are a first pass and the assigned maintainer has the final say.
test(mcp): assert rmcp malformed JSON-RPC contract