mastra-ai/mastra · error · MastraError
MCP_CLIENT_REDIRECT_BODY_NOT_REPLAYABLE
MCP_CLIENT_REDIRECT_BODY_NOT_REPLAYABLE
Error message
Cannot follow a ${response.status} redirect under the allowedHosts policy: the request body is not replayable (only string bodies can be re-sent). What it means
Redirects that preserve the body (307/308, or 301/302 for non-POST methods) require resending the request body. Under the allowedHosts policy, if the current body is neither null nor a string (e.g. a stream, FormData, or an already-consumed one-shot body), following the redirect would re-send an unusable body, so MCP_CLIENT_REDIRECT_BODY_NOT_REPLAYABLE (USER category) is thrown.
Source
Thrown at packages/mcp/src/client/url-policy.ts:242
// Release the redirect response's body so its socket can be reused.
cancelResponseBody(response);
const nextUrl = new URL(location, currentUrl);
assertHostAllowed(nextUrl, allowedHosts, `A redirect from "${currentUrl.host}" pointed at it; the hop was not followed.`);
const methodUpper = method.toUpperCase();
const dropsBody =
response.status === 303 || ((response.status === 301 || response.status === 302) && methodUpper === 'POST');
if (dropsBody) {
method = 'GET';
body = undefined;
headers.delete('content-type');
headers.delete('content-length');
} else if (body != null && typeof body !== 'string') {
// Any hop that preserves the body (307/308 always; 301/302 for non-POST
// methods) would re-send an already consumed one-shot body, so guard on
// "the body is preserved", not on specific status codes.
throw new MastraError({
id: 'MCP_CLIENT_REDIRECT_BODY_NOT_REPLAYABLE',
domain: ErrorDomain.MCP,
category: ErrorCategory.USER,
text: `Cannot follow a ${response.status} redirect under the allowedHosts policy: the request body is not replayable (only string bodies can be re-sent).`,
});
}
// WHATWG Fetch strips Authorization when the ORIGIN (scheme + host + port)
// changes — host alone is not enough: a same-host https→http downgrade must
// also drop the header or the bearer token is re-sent in cleartext.
if (nextUrl.origin !== currentUrl.origin) {
headers.delete('authorization');
}
currentUrl = nextUrl;
}
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Send the request body as a string (e.g. JSON.stringify(payload)) so it can be replayed across hops
- Target the post-redirect URL directly to avoid the redirect hop
- Buffer the body into a string before fetching
- Restructure the server so the endpoint does not redirect for body-bearing requests
Example fix
// before
await fetch(url, { method: 'POST', body: fs.createReadStream(path) });
// after
await fetch(url, { method: 'POST', body: JSON.stringify(payload), headers: { 'content-type': 'application/json' } }); Defensive patterns
Strategy: validation
Validate before calling
if (body != null && typeof body !== 'string') {
throw new Error('Body must be a string (JSON.stringify it) to survive 307/308 redirects under allowedHosts policy');
} Type guard
function isReplayableBody(b: unknown): b is string | null | undefined {
return b == null || typeof b === 'string';
} Try / catch
try {
await client.tools();
} catch (e) {
if (e instanceof MastraError && e.id === 'MCP_CLIENT_REDIRECT_BODY_NOT_REPLAYABLE') {
console.error('Re-send with a stringified body or target the post-redirect URL directly');
} else throw e;
} Prevention
- Always send JSON-stringified bodies to MCP endpoints
- Avoid streaming/FormData bodies when redirects are possible
- Resolve final URLs up front so no body-bearing redirect occurs
When it happens
Trigger: Sending a request with a non-string body (stream/Buffer/FormData) to a host that answers 307/308 redirect under an allowedHosts URL policy.
Common situations: Posting binary uploads or streaming JSON-RPC bodies to an MCP endpoint that now redirects (e.g. trailing-slash or domain migration); ReadableStream bodies consumed on the first hop.
Related errors
- MCP_CLIENT_REDIRECT_MISSING_LOCATION
- MCP_CLIENT_TOO_MANY_REDIRECTS
- runId is required when resumeData is provided
- Messages must be an array of UIMessage objects
- Platform API request did not return a redirect.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/be6ae8778a8e7473.
Report an issue: GitHub.