Skip to content

fix(client): treat a JSON-RPC error response as a terminal response - #2638

Open
latent-9 wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
latent-9:fix/streamable-http-error-response-no-reconnect
Open

fix(client): treat a JSON-RPC error response as a terminal response#2638
latent-9 wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
latent-9:fix/streamable-http-error-response-no-reconnect

Conversation

@latent-9

Copy link
Copy Markdown

Summary

In StreamableHTTPClientTransport, _handleSseStream marks a request as complete only for result responses:

if (isJSONRPCResultResponse(message)) {
    receivedResponse = true;
    if (replayMessageId !== undefined) {
        message.id = replayMessageId;
    }
}

A JSON-RPC 2.0 error response is a terminal response to a request just like a result response (a Response Object carries either result or error). Because the guard omits the error case, when a request's SSE POST stream ends with an error response and closes gracefully, receivedResponse stays false, so needsReconnect = canResume && !receivedResponse is true.

The client then schedules needless GET-SSE reconnections (up to maxRetries, replaying with Last-Event-ID) for a request that is already complete, and on the resume path it never remaps message.id to replayMessageId, so a resumed error response can miss request/response correlation.

The v2 code on main already handles this correctly (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)); this backports the same fix to v1.x.

Fix

Include isJSONRPCErrorResponse in the guard so an error response also stops reconnection and gets its id remapped.

Test

Added a regression test alongside the existing "should NOT reconnect a POST stream when response was received" case, using a JSON-RPC error response as the terminal message. It fails before the fix (fetch is called twice, the second being the needless reconnect) and passes after.

_handleSseStream only marked receivedResponse for result responses, so a
request whose SSE POST stream ended with a JSON-RPC error response was
treated as incomplete. The client then scheduled needless reconnections
and, on the resume path, failed to remap the message id. An error
response is a terminal response just like a result, so include
isJSONRPCErrorResponse in the guard, matching the behavior already on
main. Add a regression test with an error response as the terminal
message.
@latent-9
latent-9 requested a review from a team as a code owner August 11, 2026 01:09
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7300bb6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@modelcontextprotocol/sdk@2638

commit: 7300bb6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant