ruvnet/ruflo · error

HTTP transport failed: ${firstError instanceof Error ? first

Error message

HTTP transport failed: ${firstError instanceof Error ? firstError.message : firstError}; SSE fallback failed: ${err instanceof Error ? err.message : err}

What it means

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.

Source

Thrown at ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts:208

				recordFailure(key, 429, err);
				throw new McpRateLimitedError(
					server.name,
					429,
					retryAfterMs,
					err instanceof Error ? err.message : String(err)
				);
			}

			if (effectiveStatus !== undefined) {
				recordFailure(key, effectiveStatus, err);
			}

			const message =
				"HTTP transport failed: " +
				String(firstError instanceof Error ? firstError.message : firstError) +
				"; SSE fallback failed: " +
				String(err instanceof Error ? err.message : err);
			throw new Error(message, { cause: err instanceof Error ? err : undefined });
		}
	}

	pool.set(key, client);
	return client;
}

export async function drainPool() {
	for (const [key, client] of pool) {
		try {
			await client.close?.();
		} catch {}
		pool.delete(key);
	}
	failureCooldown.clear();
}

export function evictFromPool(server: McpServerConfig): Client | undefined {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Read both halves of the message plus err.cause — the first names the HTTP transport error, the second the SSE error
  2. Verify reachability from the same host: curl -v https://<host>/mcp (and /sse), check DNS and TLS
  3. Fix the URL/port in MCP_SERVERS; for TLS issues add the CA to NODE_EXTRA_CA_CERTS or fix the cert
  4. If the server is stdio-only, front it with an HTTP/SSE gateway (e.g. mcp-proxy) before configuring it here
  5. Retry with backoff for genuinely transient network blips

Example fix

// before
const client = await getClient(server); // HTTP transport failed: fetch failed; SSE fallback failed: ...

// after (surface actionable detail + retry transient failures once)
try {
	client = await getClient(server);
} catch (e) {
	const msg = e instanceof Error ? e.message : String(e);
	if (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|certificate/i.test(msg)) {
		throw error(502, `MCP server unreachable — check MCP_SERVERS url/TLS: ${msg}`);
	}
	throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

async function mcpReachable(url: string): Promise<boolean> {
	try {
		const u = new URL(url);
		await new Promise((r) => setTimeout(r, 0));
		await fetch(u.origin, { method: "HEAD" });
		return true;
	} catch {
		return false;
	}
}

Type guard

function isMcpDualTransportError(e: unknown): boolean {
	return (
		e instanceof Error &&
		e.message.startsWith("HTTP transport failed:") &&
		e.message.includes("SSE fallback failed:")
	);
}

Try / catch

try {
	client = await getClient(server);
} catch (e) {
	if (isMcpDualTransportError(e)) {
		// transient network class: bounded backoff, then surface 502 with both causes
		throw error(502, `MCP server unreachable: ${e.message}`);
	}
	throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/b2956df200c4c10b. Report an issue: GitHub.