{"record":{"id":"b2956df200c4c10b","repo":"ruvnet/ruflo","slug":"http-transport-failed-firsterror-instanceof-err","errorCode":null,"errorMessage":"HTTP transport failed: ${firstError instanceof Error ? firstError.message : firstError}; SSE fallback failed: ${err instanceof Error ? err.message : err}","messagePattern":"HTTP transport failed: (.+?); SSE fallback failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts","lineNumber":208,"sourceCode":"\t\t\t\trecordFailure(key, 429, err);\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\terr instanceof Error ? err.message : String(err)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (effectiveStatus !== undefined) {\n\t\t\t\trecordFailure(key, effectiveStatus, err);\n\t\t\t}\n\n\t\t\tconst message =\n\t\t\t\t\"HTTP transport failed: \" +\n\t\t\t\tString(firstError instanceof Error ? firstError.message : firstError) +\n\t\t\t\t\"; SSE fallback failed: \" +\n\t\t\t\tString(err instanceof Error ? err.message : err);\n\t\t\tthrow new Error(message, { cause: err instanceof Error ? err : undefined });\n\t\t}\n\t}\n\n\tpool.set(key, client);\n\treturn client;\n}\n\nexport async function drainPool() {\n\tfor (const [key, client] of pool) {\n\t\ttry {\n\t\t\tawait client.close?.();\n\t\t} catch {}\n\t\tpool.delete(key);\n\t}\n\tfailureCooldown.clear();\n}\n\nexport function evictFromPool(server: McpServerConfig): Client | undefined {","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts#L190-L226","documentation":"The combined failure of last resort in getClient(): Streamable HTTP failed recoverably (network-level or 408, i.e. not a definitive HTTP status) and the SSE fallback also failed without a definitive status. The message concatenates both transport errors (clientPool.ts:208) and attaches the SSE error as cause — meaning the server was never reached successfully over either transport.","triggerScenarios":"DNS resolution failure, connection refused, TLS certificate error, firewall/egress block, or a server supporting neither streamable HTTP nor SSE — e.g. wrong host/port in MCP_SERVERS, self-signed cert rejected by Node, or a proxy that terminates both /mcp and /sse requests.","commonSituations":"Typo'd hostname or port; corporate egress proxy blocking outbound MCP traffic; cert chain not trusted in the container; the MCP server only speaks stdio (not supported by this remote-client pool); IPv6-only upstream unreachable from the host.","solutions":["Read both halves of the message plus err.cause — the first names the HTTP transport error, the second the SSE error","Verify reachability from the same host: curl -v https://<host>/mcp (and /sse), check DNS and TLS","Fix the URL/port in MCP_SERVERS; for TLS issues add the CA to NODE_EXTRA_CA_CERTS or fix the cert","If the server is stdio-only, front it with an HTTP/SSE gateway (e.g. mcp-proxy) before configuring it here","Retry with backoff for genuinely transient network blips"],"exampleFix":"// before\nconst client = await getClient(server); // HTTP transport failed: fetch failed; SSE fallback failed: ...\n\n// after (surface actionable detail + retry transient failures once)\ntry {\n\tclient = await getClient(server);\n} catch (e) {\n\tconst msg = e instanceof Error ? e.message : String(e);\n\tif (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|certificate/i.test(msg)) {\n\t\tthrow error(502, `MCP server unreachable — check MCP_SERVERS url/TLS: ${msg}`);\n\t}\n\tthrow e;\n}","handlingStrategy":"retry","validationCode":"async function mcpReachable(url: string): Promise<boolean> {\n\ttry {\n\t\tconst u = new URL(url);\n\t\tawait new Promise((r) => setTimeout(r, 0));\n\t\tawait fetch(u.origin, { method: \"HEAD\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}","typeGuard":"function isMcpDualTransportError(e: unknown): boolean {\n\treturn (\n\t\te instanceof Error &&\n\t\te.message.startsWith(\"HTTP transport failed:\") &&\n\t\te.message.includes(\"SSE fallback failed:\")\n\t);\n}","tryCatchPattern":"try {\n\tclient = await getClient(server);\n} catch (e) {\n\tif (isMcpDualTransportError(e)) {\n\t\t// transient network class: bounded backoff, then surface 502 with both causes\n\t\tthrow error(502, `MCP server unreachable: ${e.message}`);\n\t}\n\tthrow e;\n}","preventionTips":["Pre-flight MCP_SERVERS URLs (DNS + TLS) from the deployment network, not just your laptop","For stdio-only MCP servers, front them with an HTTP gateway before configuring them here","Include err.cause in logs — the SSE half usually names the real network error"],"tags":["mcp","network","transport","sse","connection-failed","tls"],"backgroundTag":"connection-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}