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
- Run any MCP SSE server (e.g. an apigen/
@modelcontextprotocol/sdk SSEServerTransport at http://127.0.0.1:3100/sse).
- Add a Custom MCP tool with config
{"url": "http://127.0.0.1:3100/sse"}.
- 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
- flowise 3.1.4 (global install via npm)
- Node v24.11.1
@modelcontextprotocol/sdk@1.29.0, eventsource@3.0.7, node-fetch@2.7.0
Summary
Connecting a Custom MCP tool to any remote SSE server (URL with
ssetransport) always fails with:Root cause (traced in flowise-components 3.1.4)
In
flowise-components/dist/nodes/tools/MCP/core.js,MCPToolkit.createClient()first triesStreamableHTTPClientTransport, and on failure falls back toSSEClientTransportwith a custom fetch:secureFetch(flowise-components/dist/src/httpSecurity.js, ~line 234) performs the SSRF/TLS checks and then callsnode-fetchv2:node-fetchv2 represents a streaming response body as a Node.jsPassThroughstream. The MCP SDK'sSSEClientTransporthands the response to theeventsourcepackage, which hard-requires a webReadableStreamand rejects any other body shape (its check:if (typeof body !== 'object' || !body || !('getReader' in body)) failConnection(...)). APassThroughhas nogetReader, 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 ownSSEServerTransport).How to reproduce
@modelcontextprotocol/sdkSSEServerTransportathttp://127.0.0.1:3100/sse).{"url": "http://127.0.0.1:3100/sse"}.Suggested fix
Convert
secureFetch's node-fetch body to a webReadableStreambefore handing it to the SSE client (Node's staticstream.Readable.toWeb(stream)— there is no instance.toWeb()). Minimal patch, verified working against a live SSE server:...and use
secureFetchForSse(url, init[, headers])in both SSE-fallback branches ofcreateClient.Environment
@modelcontextprotocol/sdk@1.29.0,eventsource@3.0.7,node-fetch@2.7.0