{"record":{"id":"3cbc2b47aca3c939","repo":"mastra-ai/mastra","slug":"mcp-client-too-many-redirects","errorCode":"MCP_CLIENT_TOO_MANY_REDIRECTS","errorMessage":"Exceeded the maximum of ${MAX_REDIRECT_HOPS} redirect hops while requesting host \"${currentUrl.host}\" under the allowedHosts policy.","messagePattern":"Exceeded the maximum of (.+?) redirect hops while requesting host \"(.+?)\" under the allowedHosts policy\\.","errorType":"error_code","errorClass":"MastraError","httpStatus":null,"severity":"error","filePath":"packages/mcp/src/client/url-policy.ts","lineNumber":213,"sourceCode":"  const headers = new Headers(init?.headers);\n\n  for (let redirectsFollowed = 0; ; redirectsFollowed++) {\n    const response = await fetchImpl(currentUrl, { ...init, method, body, headers, redirect: 'manual' });\n    if (!REDIRECT_STATUS_CODES.has(response.status)) {\n      return response;\n    }\n\n    const location = response.headers.get('location');\n    if (!location) {\n      throw new MastraError({\n        id: 'MCP_CLIENT_REDIRECT_MISSING_LOCATION',\n        domain: ErrorDomain.MCP,\n        category: ErrorCategory.THIRD_PARTY,\n        text: `Received a ${response.status} redirect with no Location header from \"${currentUrl.host}\" while following redirects under the allowedHosts policy.`,\n      });\n    }\n    if (redirectsFollowed >= MAX_REDIRECT_HOPS) {\n      throw new MastraError({\n        id: 'MCP_CLIENT_TOO_MANY_REDIRECTS',\n        domain: ErrorDomain.MCP,\n        category: ErrorCategory.THIRD_PARTY,\n        // Interpolate only the host, not the full URL: a URL's path/query could\n        // contain substrings (e.g. \"sessionId\") that isReconnectableMCPError\n        // matches, which would misclassify this terminal failure as transient.\n        text: `Exceeded the maximum of ${MAX_REDIRECT_HOPS} redirect hops while requesting host \"${currentUrl.host}\" under the allowedHosts policy.`,\n      });\n    }\n\n    // Release the redirect response's body so its socket can be reused.\n    cancelResponseBody(response);\n\n    const nextUrl = new URL(location, currentUrl);\n    assertHostAllowed(nextUrl, allowedHosts, `A redirect from \"${currentUrl.host}\" pointed at it; the hop was not followed.`);\n\n    const methodUpper = method.toUpperCase();\n    const dropsBody =","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/mcp/src/client/url-policy.ts#L195-L231","documentation":"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.","triggerScenarios":"A chain of redirects between allowed hosts that loops (A→B→A) or is longer than MAX_REDIRECT_HOPS while using the allowedHosts policy.","commonSituations":"Misconfigured gateway ping-ponging between two URLs; auth redirects bouncing between login and resource URLs; proxy rewrite loops in Kubernetes ingress or API gateways.","solutions":["Trace the redirect chain with curl -IL and break the loop in server/gateway config","Call the final URL directly to avoid the redirect chain","Add all loop endpoints consistently to allowedHosts or restructure the deployment so the loop disappears","Increase hops only if the chain is legitimately long (rare; prefer fixing the loop)"],"exampleFix":"// before (gateway misconfig loop)\n/app -> redirect /app/ -> redirect /app (loop)\n// after\nserve /app directly, or a single 308 redirect /app -> /app/ with no further redirect","handlingStrategy":"fallback","validationCode":"let url = startUrl; let hops = 0;\nwhile (REDIRECT_STATUS_CODES.has((await fetch(url, { redirect: 'manual' })).status)) {\n  if (++hops > 5) throw new Error(`Redirect loop detected starting at ${startUrl}`);\n  url = new URL((await fetch(url, { redirect: 'manual' })).headers.get('location'), url);\n}","typeGuard":"null","tryCatchPattern":"try {\n  await client.tools();\n} catch (e) {\n  if (e instanceof MastraError && e.id === 'MCP_CLIENT_TOO_MANY_REDIRECTS') {\n    // Do NOT retry: terminal error; message deliberately non-transient\n    console.error(`Redirect loop at host '${extractHost(e)}'; fix gateway config or call final URL directly`);\n  } else throw e;\n}","preventionTips":["Trace redirect chains with curl -IL before wiring clients","Eliminate gateway/ingress rewrite loops (trailing-slash, auth bounce)","Keep allowedHosts consistent across the whole chain"],"tags":["mcp","redirect-loop","network","url-policy"],"backgroundTag":"redirect-loop","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}