different-ai/openwork · error

Managed MCP outbound request exceeded the guarded redirect l

Error message

Managed MCP outbound request exceeded the guarded redirect limit.

What it means

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.

Source

Thrown at apps/server/src/local-managed-mcp-url-guard.ts:253

export function createLocalManagedMcpGuardedFetch(): FetchLike {
  return async (input, init) => {
    let current = new URL(String(input));
    let currentInit: RequestInit = { ...init, redirect: "manual" };
    for (let redirectCount = 0; ; redirectCount += 1) {
      const parsed = parseHttpUrl(current.toString());
      if (!allowPrivateUrls() && parsed.protocol !== "https:") {
        throw new LocalManagedMcpPrivateUrlError(current.toString(), "managed MCP egress requires HTTPS");
      }
      const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
      if (!allowPrivateUrls() && isIP(hostname) && isLocalManagedMcpPrivateAddress(hostname)) {
        throw new LocalManagedMcpPrivateUrlError(current.toString(), "the address is private or reserved");
      }
      const response = await guardedTransportFetch(current, currentInit);
      const location = response.headers.get("location");
      if (!REDIRECT_STATUSES.has(response.status) || !location) return response;
      if (redirectCount >= 5) {
        await response.body?.cancel();
        throw new Error("Managed MCP outbound request exceeded the guarded redirect limit.");
      }
      const next = new URL(location, current);
      try {
        currentInit = redirectedRequestInit(currentInit, response.status, current, next);
      } catch (error) {
        await response.body?.cancel();
        throw error;
      }
      current = next;
      await response.body?.cancel();
    }
  };
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Break the redirect loop on the server (fix the rewriting rule causing the bounce)
  2. Update the configured MCP URL to the final destination directly so few or no redirects occur
  3. Trace with curl -sIL to enumerate the loop and identify the offending hop
  4. If a legitimately long chain is required, pre-resolve to the final URL in configuration

Example fix

// before (server)
app.use((req, res) => res.redirect(301, req.url === "/mcp" ? "/mcp/" : "/mcp")); // loop
// after
app.use((req, res) => res.redirect(301, "/mcp/final"));
Defensive patterns

Strategy: retry

Validate before calling

let hops = 0;
let cur = url;
while (true) {
  const res = await fetch(cur, { redirect: "manual" });
  if (![301,302,303,307,308].includes(res.status) || !res.headers.get("location")) break;
  if (++hops > 5) throw new Error("Redirect chain exceeds 5 hops");
  cur = new URL(res.headers.get("location"), cur).toString();
}

Try / catch

try {
  const res = await guardedFetch(url);
} catch (error) {
  if (error.message.includes("exceeded the guarded redirect limit")) {
    // do not blind-retry: resolve the loop server-side or point config at the final URL
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/1af95d94cf0d5fc2. Report an issue: GitHub.