Skip to content

Commit c1ee3c6

Browse files
authored
fix(opencode): stop MCP SSE error reconnect loops (#39697)
1 parent a1ab489 commit c1ee3c6

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { expect, test } from "bun:test"
2+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
3+
4+
test("does not reconnect an SSE stream after a JSON-RPC error response", async () => {
5+
let requests = 0
6+
const transport = new StreamableHTTPClientTransport(new URL("http://mcp.invalid"), {
7+
fetch: async () => {
8+
requests += 1
9+
return new Response(
10+
new ReadableStream({
11+
start(controller) {
12+
controller.enqueue(new TextEncoder().encode("id: prime\nretry: 1\ndata:\n\n"))
13+
controller.enqueue(
14+
new TextEncoder().encode(
15+
'id: error\ndata: {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}\n\n',
16+
),
17+
)
18+
controller.close()
19+
},
20+
}),
21+
{ status: 200, headers: { "content-type": "text/event-stream" } },
22+
)
23+
},
24+
reconnectionOptions: {
25+
initialReconnectionDelay: 1,
26+
maxReconnectionDelay: 1,
27+
reconnectionDelayGrowFactor: 1,
28+
maxRetries: 2,
29+
},
30+
})
31+
32+
await transport.start()
33+
await transport.send({ jsonrpc: "2.0", method: "resources/list", id: 1 })
34+
await Bun.sleep(25)
35+
await transport.close()
36+
37+
expect(requests).toBe(1)
38+
})

patches/@modelcontextprotocol%2Fsdk@1.29.0.patch

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ diff --git a/dist/cjs/client/streamableHttp.js b/dist/cjs/client/streamableHttp.
141141
index a29a7d3a0f14d9cd800ef5b296485237350c666f..c362ae5fe6c62c8c8eae7e2e61de1eedff5443c9 100644
142142
--- a/dist/cjs/client/streamableHttp.js
143143
+++ b/dist/cjs/client/streamableHttp.js
144+
@@ -204,7 +204,7 @@ class StreamableHTTPClientTransport {
145+
if (!event.event || event.event === 'message') {
146+
try {
147+
const message = types_js_1.JSONRPCMessageSchema.parse(JSON.parse(event.data));
148+
- if ((0, types_js_1.isJSONRPCResultResponse)(message)) {
149+
+ if ((0, types_js_1.isJSONRPCResultResponse)(message) || (0, types_js_1.isJSONRPCErrorResponse)(message)) {
150+
// Mark that we received a response - no need to reconnect for this request
151+
receivedResponse = true;
152+
if (replayMessageId !== undefined) {
144153
@@ -290,7 +290,38 @@ class StreamableHTTPClientTransport {
145154
this.onclose?.();
146155
}
@@ -518,10 +527,19 @@ index 624172aa24ae255a67c083f9c19053343e4a0581..ac75b14545fda44aff7ff4d97cc5da88
518527
@@ -1,5 +1,5 @@
519528
import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js';
520529
-import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
521-
+import { isInitializedNotification, isInitializeRequest, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
530+
+import { isInitializedNotification, isInitializeRequest, isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
522531
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
523532
import { EventSourceParserStream } from 'eventsource-parser/stream';
524533
// Default reconnection options for StreamableHTTP connections
534+
@@ -200,7 +200,7 @@ export class StreamableHTTPClientTransport {
535+
if (!event.event || event.event === 'message') {
536+
try {
537+
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
538+
- if (isJSONRPCResultResponse(message)) {
539+
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
540+
// Mark that we received a response - no need to reconnect for this request
541+
receivedResponse = true;
542+
if (replayMessageId !== undefined) {
525543
@@ -286,7 +286,38 @@ export class StreamableHTTPClientTransport {
526544
this.onclose?.();
527545
}

0 commit comments

Comments
 (0)