{"record":{"id":"1af95d94cf0d5fc2","repo":"different-ai/openwork","slug":"managed-mcp-outbound-request-exceeded-the-guarded","errorCode":null,"errorMessage":"Managed MCP outbound request exceeded the guarded redirect limit.","messagePattern":"Managed MCP outbound request exceeded the guarded redirect limit\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/server/src/local-managed-mcp-url-guard.ts","lineNumber":253,"sourceCode":"export function createLocalManagedMcpGuardedFetch(): FetchLike {\n  return async (input, init) => {\n    let current = new URL(String(input));\n    let currentInit: RequestInit = { ...init, redirect: \"manual\" };\n    for (let redirectCount = 0; ; redirectCount += 1) {\n      const parsed = parseHttpUrl(current.toString());\n      if (!allowPrivateUrls() && parsed.protocol !== \"https:\") {\n        throw new LocalManagedMcpPrivateUrlError(current.toString(), \"managed MCP egress requires HTTPS\");\n      }\n      const hostname = parsed.hostname.replace(/^\\[|\\]$/g, \"\");\n      if (!allowPrivateUrls() && isIP(hostname) && isLocalManagedMcpPrivateAddress(hostname)) {\n        throw new LocalManagedMcpPrivateUrlError(current.toString(), \"the address is private or reserved\");\n      }\n      const response = await guardedTransportFetch(current, currentInit);\n      const location = response.headers.get(\"location\");\n      if (!REDIRECT_STATUSES.has(response.status) || !location) return response;\n      if (redirectCount >= 5) {\n        await response.body?.cancel();\n        throw new Error(\"Managed MCP outbound request exceeded the guarded redirect limit.\");\n      }\n      const next = new URL(location, current);\n      try {\n        currentInit = redirectedRequestInit(currentInit, response.status, current, next);\n      } catch (error) {\n        await response.body?.cancel();\n        throw error;\n      }\n      current = next;\n      await response.body?.cancel();\n    }\n  };\n}\n","sourceCodeStart":235,"sourceCodeEnd":267,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/local-managed-mcp-url-guard.ts#L235-L267","documentation":"The guarded fetch follows at most 5 redirects to prevent redirect loops and unbounded hops. When a 6th redirect is detected the response body is cancelled and a plain Error is thrown; unlike the other guards this is not a LocalManagedMcpPrivateUrlError.","triggerScenarios":"An MCP endpoint (or anything in its redirect chain) returns 301/302/303/307/308 more than 5 times, e.g. a redirect loop between two URLs or a chain longer than the cap.","commonSituations":"Misconfigured server bouncing between www/non-www or trailing-slash variants; auth middleware redirect loop; proxies appending/striping slashes repeatedly; moving a server and leaving both old and new hosts redirecting to each other.","solutions":["Break the redirect loop on the server (fix the rewriting rule causing the bounce)","Update the configured MCP URL to the final destination directly so few or no redirects occur","Trace with curl -sIL to enumerate the loop and identify the offending hop","If a legitimately long chain is required, pre-resolve to the final URL in configuration"],"exampleFix":"// before (server)\napp.use((req, res) => res.redirect(301, req.url === \"/mcp\" ? \"/mcp/\" : \"/mcp\")); // loop\n// after\napp.use((req, res) => res.redirect(301, \"/mcp/final\"));","handlingStrategy":"retry","validationCode":"let hops = 0;\nlet cur = url;\nwhile (true) {\n  const res = await fetch(cur, { redirect: \"manual\" });\n  if (![301,302,303,307,308].includes(res.status) || !res.headers.get(\"location\")) break;\n  if (++hops > 5) throw new Error(\"Redirect chain exceeds 5 hops\");\n  cur = new URL(res.headers.get(\"location\"), cur).toString();\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = await guardedFetch(url);\n} catch (error) {\n  if (error.message.includes(\"exceeded the guarded redirect limit\")) {\n    // do not blind-retry: resolve the loop server-side or point config at the final URL\n  }\n  throw error;\n}","preventionTips":["Configure the final destination URL directly to avoid long chains","Fix redirect loops (www/trailing-slash/auth bounce) at the server","Trace chains with curl -sIL before onboarding an MCP server","Only retry after confirming the chain terminates, not on this error itself"],"tags":["network","redirect","http","loop"],"backgroundTag":"too-many-redirects","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}