can1357/oh-my-pi · error · Error

HTTP ${response.status}: server redirected a ${init.method}

Error message

HTTP ${response.status}: server redirected a ${init.method} request; refusing to follow

What it means

Thrown by mcpFetch's redirect-following loop in header-policy.ts when the server responds with a redirect status (301/302/303) to a non-GET request. Per the fetch redirect spec, such redirects may not preserve the method and body, so instead of silently converting a POST (e.g. an MCP JSON-RPC call) into a GET, the client refuses to follow and surfaces the error.

Source

Thrown at packages/coding-agent/src/mcp/transports/header-policy.ts:118

	originLocked: boolean,
): Promise<Response> {
	if (!originLocked) {
		return fetch(url, { ...init, headers: mergeMCPHeaders(sources) });
	}

	const configuredOrigin = new URL(url).origin;
	let currentUrl = url;
	for (let hop = 0; hop <= MAX_REDIRECT_HOPS; hop++) {
		const attachConfigured = new URL(currentUrl).origin === configuredOrigin;
		const headers = mergeMCPHeaders(attachConfigured ? sources : { generated: sources.generated });
		const response = await fetch(currentUrl, { ...init, headers, redirect: "manual" });
		if (!REDIRECT_STATUSES[response.status]) return response;

		const location = response.headers.get("Location");
		if (!location) return response;
		await response.body?.cancel();
		if (init.method !== "GET" && response.status !== 307 && response.status !== 308) {
			throw new Error(`HTTP ${response.status}: server redirected a ${init.method} request; refusing to follow`);
		}
		currentUrl = new URL(location, currentUrl).href;
	}
	throw new Error(`Too many redirects (> ${MAX_REDIRECT_HOPS}) fetching ${url}`);
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Update the MCP server URL to the final post-redirect destination (follow the redirect once with curl -i to find it)
  2. Use 307/308 redirects server-side if you control the endpoint, since those preserve method and body
  3. Fix reverse-proxy/lb rules so the MCP endpoint doesn't 301/302 non-GET requests
  4. Ensure you're using https:// directly to avoid scheme-upgrade redirects

Example fix

// before: POST to a redirecting URL
await client.callTool("http://old.example.com/mcp", params); // 301 -> error
// after: POST directly to the final URL
await client.callTool("https://api.example.com/v2/mcp", params);
Defensive patterns

Strategy: try-catch

Validate before calling

// detect redirect-prone URLs before issuing non-GET MCP requests
const probe = await fetch(url, { method: "HEAD", redirect: "follow" });
if (probe.url !== url) throw new Error(`MCP endpoint redirects; use final URL ${probe.url} instead of ${url}`);

Type guard

function isRedirectRefusal(err: unknown): boolean {
  return err instanceof Error && /refusing to follow/.test(err.message);
}

Try / catch

try {
  await mcpFetch(url, { method: "POST", body });
} catch (err) {
  if (isRedirectRefusal(err)) {
    const finalUrl = await resolveFinalUrl(url); // HEAD with redirect:follow
    return mcpFetch(finalUrl, { method: "POST", body });
  }
  throw err;
}

Prevention

When it happens

Trigger: Making a POST/PUT/DELETE MCP request through mcpFetch when the server answers 301, 302, or 303 with a Location header (307/308 are followed since they preserve method).

Common situations: Server migrated its MCP endpoint to a new URL and redirects old paths; a reverse proxy rewrites POSTs to a login/redirect page; trailing-slash normalization issuing 301 on POST; HTTP-to-HTTPS redirect on a POST.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/b6544cb5f9387363. Report an issue: GitHub.