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
- 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
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
- 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
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
- Failed to fetch latest-mac.yml (${response.status} ${respons
- a request body cannot be redirected to another origin
- OIDC discovery failed with ${response.status}. Enter manual
- OpenWork workspace discovery failed (${response.status} ${re
- Failed to load desktop policies (${response.status}).
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/1af95d94cf0d5fc2.
Report an issue: GitHub.