Skip to content

Commit 0b98618

Browse files
authored
mcp: fix not following redirect for POST MCP calls (#252572)
Handle it manually. It seems like there is a behavior/bug in undici where it errors with 'Cannot perform ArrayBuffer.prototype.slice on a detached ArrayBuffer' on following a POST redirect. So, handle it ourselves. Closes #252388
1 parent 9e65768 commit 0b98618

File tree

1 file changed

+36
-8
lines changed

1 file changed

+36
-8
lines changed

src/vs/workbench/api/common/extHostMcp.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ type HttpModeT =
176176
| { value: HttpMode.Http; sessionId: string | undefined }
177177
| { value: HttpMode.SSE; endpoint: string };
178178

179+
const MAX_FOLLOW_REDIRECTS = 5;
180+
const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
181+
179182
/**
180183
* Implementation of both MCP HTTP Streaming as well as legacy SSE.
181184
*
@@ -702,21 +705,46 @@ class McpHTTPHandle extends Disposable {
702705
}
703706
this._log(LogLevel.Trace, `Fetching ${url} with options: ${JSON.stringify(traceObj)}`);
704707
}
705-
const res = await fetch(url, {
706-
...init,
707-
signal: this._abortCtrl.signal,
708-
});
708+
709+
let currentUrl = url;
710+
let response!: Response;
711+
for (let redirectCount = 0; redirectCount < MAX_FOLLOW_REDIRECTS; redirectCount++) {
712+
response = await fetch(currentUrl, {
713+
...init,
714+
signal: this._abortCtrl.signal,
715+
redirect: 'manual'
716+
});
717+
718+
// Check for redirect status codes (301, 302, 303, 307, 308)
719+
if (!REDIRECT_STATUS_CODES.includes(response.status)) {
720+
break;
721+
}
722+
723+
const location = response.headers.get('location');
724+
if (!location) {
725+
break;
726+
}
727+
728+
const nextUrl = new URL(location, currentUrl).toString();
729+
this._log(LogLevel.Trace, `Redirect (${response.status}) from ${currentUrl} to ${nextUrl}`);
730+
currentUrl = nextUrl;
731+
// Per fetch spec, for 303 always use GET, keep method unless original was POST and 301/302, then GET.
732+
if (response.status === 303 || ((response.status === 301 || response.status === 302) && init.method === 'POST')) {
733+
init.method = 'GET';
734+
delete init.body;
735+
}
736+
}
709737

710738
if (canLog(this._logService.getLevel(), LogLevel.Trace)) {
711739
const headers: Record<string, string> = {};
712-
res.headers.forEach((value, key) => { headers[key] = value; });
713-
this._log(LogLevel.Trace, `Fetched ${url}: ${JSON.stringify({
714-
status: res.status,
740+
response.headers.forEach((value, key) => { headers[key] = value; });
741+
this._log(LogLevel.Trace, `Fetched ${currentUrl}: ${JSON.stringify({
742+
status: response.status,
715743
headers: headers,
716744
})}`);
717745
}
718746

719-
return res;
747+
return response;
720748
}
721749
}
722750

0 commit comments

Comments
 (0)