Skip to content

Commit 358910d

Browse files
committed
fix(client): replace body.cancel() with text() to prevent hanging
body.cancel() can hang indefinitely with certain server responses (e.g., Sentry MCP's OAuth flow). Using text?.().catch(() => {}) is safer: - Always completes (reads all data) - Releases connection (body consumed) - No stream coordination needed - Uses optional chaining for compatibility with mock responses Fixes regression from #1173 and #1214.
1 parent 8a1b457 commit 358910d

File tree

3 files changed

+13
-13
lines changed

3 files changed

+13
-13
lines changed

packages/client/src/client/auth.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,12 +670,12 @@ export async function discoverOAuthProtectedResourceMetadata(
670670
});
671671

672672
if (!response || response.status === 404) {
673-
await response?.body?.cancel();
673+
await response?.text?.().catch(() => {});
674674
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
675675
}
676676

677677
if (!response.ok) {
678-
await response.body?.cancel();
678+
await response.text?.().catch(() => {});
679679
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
680680
}
681681
return OAuthProtectedResourceMetadataSchema.parse(await response.json());
@@ -803,12 +803,12 @@ export async function discoverOAuthMetadata(
803803
});
804804

805805
if (!response || response.status === 404) {
806-
await response?.body?.cancel();
806+
await response?.text?.().catch(() => {});
807807
return undefined;
808808
}
809809

810810
if (!response.ok) {
811-
await response.body?.cancel();
811+
await response.text?.().catch(() => {});
812812
throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
813813
}
814814

@@ -918,7 +918,7 @@ export async function discoverAuthorizationServerMetadata(
918918
}
919919

920920
if (!response.ok) {
921-
await response.body?.cancel();
921+
await response.text?.().catch(() => {});
922922
// Continue looking for any 4xx response code.
923923
if (response.status >= 400 && response.status < 500) {
924924
continue; // Try next URL

packages/client/src/client/sse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export class SSEClientTransport implements Transport {
260260

261261
const response = await (this._fetch ?? fetch)(this._endpoint, init);
262262
if (!response.ok) {
263-
const text = await response.text().catch(() => null);
263+
const text = await response.text?.().catch(() => null);
264264

265265
if (response.status === 401 && this._authProvider) {
266266
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
@@ -285,7 +285,7 @@ export class SSEClientTransport implements Transport {
285285
}
286286

287287
// Release connection - POST responses don't have content we need
288-
await response.body?.cancel();
288+
await response.text?.().catch(() => {});
289289
} catch (error) {
290290
this.onerror?.(error as Error);
291291
throw error;

packages/client/src/client/streamableHttp.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export class StreamableHTTPClientTransport implements Transport {
235235
});
236236

237237
if (!response.ok) {
238-
await response.body?.cancel();
238+
await response.text?.().catch(() => {});
239239

240240
if (response.status === 401 && this._authProvider) {
241241
// Need to authenticate
@@ -495,7 +495,7 @@ export class StreamableHTTPClientTransport implements Transport {
495495
}
496496

497497
if (!response.ok) {
498-
const text = await response.text().catch(() => null);
498+
const text = await response.text?.().catch(() => null);
499499

500500
if (response.status === 401 && this._authProvider) {
501501
// Prevent infinite recursion when server returns 401 after successful auth
@@ -568,7 +568,7 @@ export class StreamableHTTPClientTransport implements Transport {
568568

569569
// If the response is 202 Accepted, there's no body to process
570570
if (response.status === 202) {
571-
await response.body?.cancel();
571+
await response.text?.().catch(() => {});
572572
// if the accepted notification is initialized, we start the SSE stream
573573
// if it's supported by the server
574574
if (isInitializedNotification(message)) {
@@ -603,12 +603,12 @@ export class StreamableHTTPClientTransport implements Transport {
603603
this.onmessage?.(msg);
604604
}
605605
} else {
606-
await response.body?.cancel();
606+
await response.text?.().catch(() => {});
607607
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
608608
}
609609
} else {
610610
// No requests in message but got 200 OK - still need to release connection
611-
await response.body?.cancel();
611+
await response.text?.().catch(() => {});
612612
}
613613
} catch (error) {
614614
this.onerror?.(error as Error);
@@ -647,7 +647,7 @@ export class StreamableHTTPClientTransport implements Transport {
647647
};
648648

649649
const response = await (this._fetch ?? fetch)(this._url, init);
650-
await response.body?.cancel();
650+
await response.text?.().catch(() => {});
651651

652652
// We specifically handle 405 as a valid response according to the spec,
653653
// meaning the server does not support explicit session termination

0 commit comments

Comments
 (0)