{"record":{"id":"b6544cb5f9387363","repo":"can1357/oh-my-pi","slug":"http-response-status-server-redirected-a-ini","errorCode":null,"errorMessage":"HTTP ${response.status}: server redirected a ${init.method} request; refusing to follow","messagePattern":"HTTP (.+?): server redirected a (.+?) request; refusing to follow","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/transports/header-policy.ts","lineNumber":118,"sourceCode":"\toriginLocked: boolean,\n): Promise<Response> {\n\tif (!originLocked) {\n\t\treturn fetch(url, { ...init, headers: mergeMCPHeaders(sources) });\n\t}\n\n\tconst configuredOrigin = new URL(url).origin;\n\tlet currentUrl = url;\n\tfor (let hop = 0; hop <= MAX_REDIRECT_HOPS; hop++) {\n\t\tconst attachConfigured = new URL(currentUrl).origin === configuredOrigin;\n\t\tconst headers = mergeMCPHeaders(attachConfigured ? sources : { generated: sources.generated });\n\t\tconst response = await fetch(currentUrl, { ...init, headers, redirect: \"manual\" });\n\t\tif (!REDIRECT_STATUSES[response.status]) return response;\n\n\t\tconst location = response.headers.get(\"Location\");\n\t\tif (!location) return response;\n\t\tawait response.body?.cancel();\n\t\tif (init.method !== \"GET\" && response.status !== 307 && response.status !== 308) {\n\t\t\tthrow new Error(`HTTP ${response.status}: server redirected a ${init.method} request; refusing to follow`);\n\t\t}\n\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t}\n\tthrow new Error(`Too many redirects (> ${MAX_REDIRECT_HOPS}) fetching ${url}`);\n}\n","sourceCodeStart":100,"sourceCodeEnd":124,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/transports/header-policy.ts#L100-L124","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Update the MCP server URL to the final post-redirect destination (follow the redirect once with curl -i to find it)","Use 307/308 redirects server-side if you control the endpoint, since those preserve method and body","Fix reverse-proxy/lb rules so the MCP endpoint doesn't 301/302 non-GET requests","Ensure you're using https:// directly to avoid scheme-upgrade redirects"],"exampleFix":"// before: POST to a redirecting URL\nawait client.callTool(\"http://old.example.com/mcp\", params); // 301 -> error\n// after: POST directly to the final URL\nawait client.callTool(\"https://api.example.com/v2/mcp\", params);","handlingStrategy":"try-catch","validationCode":"// detect redirect-prone URLs before issuing non-GET MCP requests\nconst probe = await fetch(url, { method: \"HEAD\", redirect: \"follow\" });\nif (probe.url !== url) throw new Error(`MCP endpoint redirects; use final URL ${probe.url} instead of ${url}`);","typeGuard":"function isRedirectRefusal(err: unknown): boolean {\n  return err instanceof Error && /refusing to follow/.test(err.message);\n}","tryCatchPattern":"try {\n  await mcpFetch(url, { method: \"POST\", body });\n} catch (err) {\n  if (isRedirectRefusal(err)) {\n    const finalUrl = await resolveFinalUrl(url); // HEAD with redirect:follow\n    return mcpFetch(finalUrl, { method: \"POST\", body });\n  }\n  throw err;\n}","preventionTips":["Configure the MCP server URL as the canonical https endpoint, not a legacy path that redirects","Use 307/308 (method-preserving) redirects if you control the server","Check reverse-proxy rules that issue 301 on trailing-slash or scheme upgrades","Discover the final URL once with redirect: \"follow\" and cache it for subsequent calls"],"tags":["http","redirect","mcp","security"],"backgroundTag":"redirect-method-not-preserved","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}