{"record":{"id":"b6270584b4428f5a","repo":"ruvnet/ruflo","slug":"mcp-server-server-name-returned-http-httpst","errorCode":null,"errorMessage":"MCP server \"${server.name}\" returned HTTP ${httpStatus}: ${httpErr instanceof Error ? httpErr.message : String(httpErr)}","messagePattern":"MCP server \"(.+?)\" returned HTTP (.+?): (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts","lineNumber":164,"sourceCode":"\t\t// 4xx/5xx (except 408): falling back to SSE will hit the same upstream\n\t\t// and fail the same way. Surface a clean error with rate-limit info.\n\t\tif (shouldSkipSseFallback(httpStatus)) {\n\t\t\ttry {\n\t\t\t\tawait client.close?.();\n\t\t\t} catch {}\n\n\t\t\trecordFailure(key, httpStatus, httpErr);\n\n\t\t\tif (httpStatus === 429) {\n\t\t\t\tconst retryAfterMs = extractRetryAfterMs(httpErr);\n\t\t\t\tthrow new McpRateLimitedError(\n\t\t\t\t\tserver.name,\n\t\t\t\t\t429,\n\t\t\t\t\tretryAfterMs,\n\t\t\t\t\thttpErr instanceof Error ? httpErr.message : String(httpErr)\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`MCP server \"${server.name}\" returned HTTP ${httpStatus}: ` +\n\t\t\t\t\t(httpErr instanceof Error ? httpErr.message : String(httpErr)),\n\t\t\t\t{ cause: httpErr instanceof Error ? httpErr : undefined }\n\t\t\t);\n\t\t}\n\n\t\t// Recoverable failure (network, 408, transport mismatch) — try SSE fallback.\n\t\ttry {\n\t\t\tawait client.connect(new SSEClientTransport(url, { requestInit }));\n\t\t} catch (err) {\n\t\t\ttry {\n\t\t\t\tawait client.close?.();\n\t\t\t} catch {}\n\n\t\t\t// Combine both errors for the caller. Honor 429 status if either side\n\t\t\t// surfaced a rate-limit (the upstream is rate-limiting independent of\n\t\t\t// transport).\n\t\t\tconst sseStatus = statusFromTransportError(err);","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts#L146-L182","documentation":"Thrown by getClient when the StreamableHTTP connect fails with a definitive non-429 HTTP status in 400-599 (excluding 408). After recordFailure stores the cooldown, the pool throws a generic Error: 'MCP server \"<name>\" returned HTTP <status>: <message>', attaching the original error as cause. SSE fallback is intentionally skipped because a 4xx/5xx response from the streamable endpoint will fail identically over SSE.","triggerScenarios":"Initial connect to an MCP server that returns 401 Unauthorized, 403 Forbidden, 404 Not Found, 500/502/503, or another definitive 4xx/5xx on the StreamableHTTP handshake. Reachable whenever McpServerConfig.url is wrong, auth is missing/invalid, or the upstream is erroring.","commonSituations":"Wrong base URL for the MCP server; missing or expired bearer token in headers; server requires a different auth scheme; upstream incident returning 5xx; reverse proxy returning 502/504 because the MCP backend is down; CORS or path prefix issues surfacing as 404.","solutions":["For 401/403: add or refresh McpServerConfig.headers Authorization.","For 404: verify the MCP server URL and path (often /mcp or /sse).","For 5xx: confirm upstream health, then wait out the 5s cooldown (or evictFromPool) before retrying.","Inspect err.cause for the SDK's original transport error for more detail."],"exampleFix":"// before\nconst client = await getClient({ url: mcpUrl, name: 'svc', headers: {} });\n\n// after\ntry {\n  const client = await getClient({\n    url: mcpUrl,\n    name: 'svc',\n    headers: { Authorization: `Bearer ${token}` },\n  });\n} catch (e) {\n  if (e instanceof Error && /returned HTTP 401|403/.test(e.message)) {\n    await refreshToken();\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: a HEAD/GET probe is NOT recommended (it also consumes quota).\n// Instead, validate config shape before calling getClient:\nfunction validateServerConfig(server: McpServerConfig): string[] {\n  const issues: string[] = [];\n  try { new URL(server.url); } catch { issues.push(\"invalid-url\"); }\n  if (/401|403/.test(String(server.url)) || !server.headers?.Authorization) {\n    // warn, not block\n  }\n  return issues;\n}","typeGuard":"function isHttpFailureFor(e: unknown, statusRe: RegExp): e is Error {\n  return e instanceof Error && new RegExp(`returned HTTP ${statusRe.source}`).test(e.message);\n}","tryCatchPattern":"try { const client = await getClient(server); }\ncatch (e) {\n  if (e instanceof Error && /returned HTTP 401|403/.test(e.message)) {\n    await refreshMcpToken(server);\n    return getClient(server);\n  }\n  if (e instanceof Error && /returned HTTP 404/.test(e.message)) {\n    logger.error({ url: server.url }, \"MCP URL is wrong\");\n  }\n  throw e;\n}","preventionTips":["Validate McpServerConfig.url shape and auth header presence at config-load time.","Inspect err.cause for the SDK's original transport error to distinguish auth vs network vs cert.","Surface the embedded HTTP status to monitoring so 4xx config drift is visible."],"tags":["mcp","network","http","auth","config"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}