#6715·Flowise

MCP Custom Tool: SSE fallback always fails with 'Invalid response body, expected a web ReadableStream' (node-fetch v2 body is not a web ReadableStream)

Author: PseudoSkyCreated Aug 7, 2026Updated Aug 9, 2026

Summary

Connecting a Custom MCP tool to any remote SSE server (URL with sse transport) always fails with:

Failed to connect to Custom MCP server: SSE error: Invalid response body, expected a web ReadableStream

Root cause (traced in flowise-components 3.1.4)

In flowise-components/dist/nodes/tools/MCP/core.js, MCPToolkit.createClient() first tries StreamableHTTPClientTransport, and on failure falls back to SSEClientTransport with a custom fetch:

transport = new sse_js_1.SSEClientTransport(baseUrl, {
  eventSourceInit: {
    fetch: async (url, init) => {
      return (0, httpSecurity_1.secureFetch)(url.toString(), init);
    }
  }
});

secureFetch (flowise-components/dist/src/httpSecurity.js, ~line 234) performs the SSRF/TLS checks and then calls node-fetch v2:

const response = await (0, node_fetch_1.default)(currentUrl, { ...currentInit, agent: () => agent });

node-fetch v2 represents a streaming response body as a Node.js PassThrough stream. The MCP SDK's SSEClientTransport hands the response to the eventsource package, which hard-requires a web ReadableStream and rejects any other body shape (its check: if (typeof body !== 'object' || !body || !('getReader' in body)) failConnection(...)). A PassThrough has no getReader, so every SSE connection fails — regardless of which server is behind the URL. This reproduces against any spec-compliant MCP SSE server (tested against the MCP SDK's own SSEServerTransport).

How to reproduce

  1. Run any MCP SSE server (e.g. an apigen/@modelcontextprotocol/sdk SSEServerTransport at http://127.0.0.1:3100/sse).
  2. Add a Custom MCP tool with config {"url": "http://127.0.0.1:3100/sse"}.
  3. Load Available Actions / run the flow → the error above.

Suggested fix

Convert secureFetch's node-fetch body to a web ReadableStream before handing it to the SSE client (Node's static stream.Readable.toWeb(stream) — there is no instance .toWeb()). Minimal patch, verified working against a live SSE server:

const stream_1 = require("stream");

async function secureFetchForSse(url, init, headers) {
  const response = await (0, httpSecurity_1.secureFetch)(url, { ...init, ...(headers ? { headers } : {}) });
  const body = response.body;
  if (body && typeof body.pipe === 'function' && typeof stream_1.Readable.toWeb === 'function') {
    const headersObj = {};
    if (response.headers && typeof response.headers.forEach === 'function') {
      response.headers.forEach((value, key) => { headersObj[key] = value; });
    }
    return new Response(stream_1.Readable.toWeb(body), {
      status: response.status,
      statusText: response.statusText,
      headers: headersObj
    });
  }
  return response;
}

...and use secureFetchForSse(url, init[, headers]) in both SSE-fallback branches of createClient.

Environment