mastra-ai/mastra · error · MastraError

MCP_CLIENT_TOO_MANY_REDIRECTS

MCP_CLIENT_TOO_MANY_REDIRECTS

Error message

Exceeded the maximum of ${MAX_REDIRECT_HOPS} redirect hops while requesting host "${currentUrl.host}" under the allowedHosts policy.

What it means

To prevent infinite redirect loops, fetchFollowingAllowedRedirects caps the number of hops at MAX_REDIRECT_HOPS. Exceeding the cap throws MCP_CLIENT_TOO_MANY_REDIRECTS (THIRD_PARTY), and the message deliberately interpolates only the host (not the full URL) so the error is not misclassified as transient by isReconnectableMCPError.

Source

Thrown at packages/mcp/src/client/url-policy.ts:213

  const headers = new Headers(init?.headers);

  for (let redirectsFollowed = 0; ; redirectsFollowed++) {
    const response = await fetchImpl(currentUrl, { ...init, method, body, headers, redirect: 'manual' });
    if (!REDIRECT_STATUS_CODES.has(response.status)) {
      return response;
    }

    const location = response.headers.get('location');
    if (!location) {
      throw new MastraError({
        id: 'MCP_CLIENT_REDIRECT_MISSING_LOCATION',
        domain: ErrorDomain.MCP,
        category: ErrorCategory.THIRD_PARTY,
        text: `Received a ${response.status} redirect with no Location header from "${currentUrl.host}" while following redirects under the allowedHosts policy.`,
      });
    }
    if (redirectsFollowed >= MAX_REDIRECT_HOPS) {
      throw new MastraError({
        id: 'MCP_CLIENT_TOO_MANY_REDIRECTS',
        domain: ErrorDomain.MCP,
        category: ErrorCategory.THIRD_PARTY,
        // Interpolate only the host, not the full URL: a URL's path/query could
        // contain substrings (e.g. "sessionId") that isReconnectableMCPError
        // matches, which would misclassify this terminal failure as transient.
        text: `Exceeded the maximum of ${MAX_REDIRECT_HOPS} redirect hops while requesting host "${currentUrl.host}" under the allowedHosts policy.`,
      });
    }

    // 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 =

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Trace the redirect chain with curl -IL and break the loop in server/gateway config
  2. Call the final URL directly to avoid the redirect chain
  3. Add all loop endpoints consistently to allowedHosts or restructure the deployment so the loop disappears
  4. Increase hops only if the chain is legitimately long (rare; prefer fixing the loop)

Example fix

// before (gateway misconfig loop)
/app -> redirect /app/ -> redirect /app (loop)
// after
serve /app directly, or a single 308 redirect /app -> /app/ with no further redirect
Defensive patterns

Strategy: fallback

Validate before calling

let url = startUrl; let hops = 0;
while (REDIRECT_STATUS_CODES.has((await fetch(url, { redirect: 'manual' })).status)) {
  if (++hops > 5) throw new Error(`Redirect loop detected starting at ${startUrl}`);
  url = new URL((await fetch(url, { redirect: 'manual' })).headers.get('location'), url);
}

Type guard

null

Try / catch

try {
  await client.tools();
} catch (e) {
  if (e instanceof MastraError && e.id === 'MCP_CLIENT_TOO_MANY_REDIRECTS') {
    // Do NOT retry: terminal error; message deliberately non-transient
    console.error(`Redirect loop at host '${extractHost(e)}'; fix gateway config or call final URL directly`);
  } else throw e;
}

Prevention

When it happens

Trigger: A chain of redirects between allowed hosts that loops (A→B→A) or is longer than MAX_REDIRECT_HOPS while using the allowedHosts policy.

Common situations: Misconfigured gateway ping-ponging between two URLs; auth redirects bouncing between login and resource URLs; proxy rewrite loops in Kubernetes ingress or API gateways.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/3cbc2b47aca3c939. Report an issue: GitHub.