Skip to content

Spec-compliant MCP server over Streamable HTTP (official SDK) + Node 18 install fix + modernized nodes#9

Open
vvzvlad wants to merge 11 commits into
MadTinker:mainfrom
vvzvlad:feat/spec-compliant-streamable-http
Open

Spec-compliant MCP server over Streamable HTTP (official SDK) + Node 18 install fix + modernized nodes#9
vvzvlad wants to merge 11 commits into
MadTinker:mainfrom
vvzvlad:feat/spec-compliant-streamable-http

Conversation

@vvzvlad

@vvzvlad vvzvlad commented Jun 7, 2026

Copy link
Copy Markdown

What & why

The mcp-flow-server node currently implements a bespoke POST /mcp JSON-RPC switch that is not MCP-spec-compliant: there is no Streamable HTTP transport, no session handling, and /sse is a plain heartbeat rather than the MCP SSE transport. As a result, standard MCP clients (the official Python/TypeScript SDK clients, etc.) cannot talk to it. This PR turns it into a real, spec-compliant MCP server while keeping the visual, flow-based tool definition.

Changes

  • Real MCP server over Streamable HTTP built on the official @modelcontextprotocol/sdk (low-level Server + StreamableHTTPServerTransport, stateless). New Node-RED-independent module lib/mcp-streamable.js; mcp-flow-server.js delegates to it. Tools are read from the registry on every tools/list, so dynamic add/remove keeps working without listChanged.
  • Verified with real clients: official JS StreamableHTTPClientTransport and the Python mcp streamablehttp_client (initialize + tools/list + tools/call), including inside a real Node-RED 4.x Docker container on Node 18.
  • Fixes install on Node 18 (EBADENGINE): dropped the node-red peerDependency that pulled a Node-20-only transitive (validate-npm-package-name@7); slimmed deps; engines.node >= 18.
  • Modernized Node-RED API: close(removed, done) / input(msg, send, done) with done(), node.error(err, msg), admin endpoints behind RED.auth.needsPermission(...), port validation, palette labels.
  • Concurrency fix: tool executions are tracked in a Map keyed by executionId instead of a per-call input listener (fixes listener leak / done-accounting under concurrent calls); added a concurrency test.
  • Tests: real-MCP-client suite + Node-RED e2e (registry → flow-server → flow) + error cases (node-red-node-test-helper, mocha/chai).
  • Calmer node palette; README/CHANGELOG updated; version 2.0.1.

Notes

Tested on Node 18.20.8 / Node-RED 4.1.x. The cosmetic color change is a separate commit and can be dropped if you'd prefer. Happy to adjust scope.

vvzvlad and others added 11 commits June 7, 2026 04:11
Drop the node-red peerDependency (auto-installed node-red@4.1 -> validate-npm-package-name@7,
which requires Node >=20 and caused EBADENGINE on Node 18). Declare host compat via the
node-red.version field instead. Add @modelcontextprotocol/sdk ^1.29, bump uuid to 11, add
dev deps (node-red, node-red-node-test-helper, mocha, chai). Bump to 2.0.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RED-independent module exposing attachMcpStreamableEndpoint(app, {serverInfo, listTools,
callTool, logger}). Stateless Streamable HTTP on the official SDK low-level Server: per-request
Server+transport (sessionIdGenerator undefined), raw JSON Schema tools (no Zod), POST /mcp real
transport, GET/DELETE -> 405, JSON-RPC parse-error handler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the hand-rolled POST /mcp method switch (initialize/tools_list/tools_call/*_tool) and the
fake /sse heartbeat. Wire the real MCP endpoint via lib/mcp-streamable. Keep CORS (now with
mcp-session-id / mcp-protocol-version headers + exposed Mcp-Session-Id), /health, the full
lifecycle (start/stop/restart/autoStart/status, port 8001 default), and the executeToolFlow
bridge (mcp-tool-execute -> mcp-tool-response, 30s timeout). Modernize Node-RED API:
close(removed, done), input(msg, send, done), node.error, and RED.auth.needsPermission on the
admin endpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mcp-tool-registry: close(removed, done) signature, input(msg, send, done) with done callback,
safer payload access; tool-register/unregister event contract unchanged. Protect the admin
endpoints (/mcp-servers, /mcp-tools/:serverUrl) with RED.auth.needsPermission.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three suites run with a real @modelcontextprotocol/sdk Client over StreamableHTTPClientTransport:
(1) lib-level listTools/callTool incl. raw JSON Schema passthrough; (2) Node-RED e2e proving the
registry -> server -> flow bridge via node-red-node-test-helper; (3) error cases (unknown tool ->
isError, malformed JSON -> JSON-RPC parse error, GET /mcp -> 405). Stop gitignoring test/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code review found that adding a fresh node.on('input') per executeToolFlow corrupts
Node-RED 4.x done-accounting (_expectedDoneCount/_complete) and leaks listeners under
concurrent calls. Replace with one persistent input handler dispatching on a node._pending
Map keyed by executionId. Reject and clear pending calls on close. Restart now starts in the
stopServer callback (port already released) instead of a fixed 1s delay. Drop dead close-arg
normalization in both nodes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 lib edge-case tests (normalizeResult variants, multi-tool listing, schema defaulting,
MCP-shape passthrough, dynamic add/remove, concurrent lib calls, throwing tool) and a
Node-RED e2e concurrent-call test that exercises the node._pending dispatch path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per editor review: add paletteLabel to the MCP Flow Server and MCP Tool Registry
nodes, and validate serverPort via parseInt so non-numeric input is rejected
predictably.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the README intro around the real MCP Flow Server, add JS and Python
standard-client connection examples, a migration note, updated requirements
(Node >=18, Node-RED >=3) and dependency list. Add a 2.0.0 CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g for flow)

New mcp-flow-server checkbox `optimisticAck` (default off). When on, a tool call
replies "ok" to the MCP client immediately after emitting mcp-tool-execute,
without waiting for an mcp-tool-response from the flow — fire-and-forget device
control, no response wiring needed. Adds an e2e test (22 passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant