Commit ff486ea
fix: stdio exits on stdin close in containers + route bin through wrapper (v2.47.5) (czlonkowski#712)
* fix: stdio exits on stdin close in containers + route bin through wrapper (v2.47.5)
Two layered root causes for Issue czlonkowski#711 (reported by @jbjardine), which
surfaced as "Container stdio path ignores stdin close; default npx process
exits only after SIGTERM".
Layer 1 — src/mcp/index.ts container guard
The stdio entrypoint guarded stdin end/close handler registration behind
an isContainerEnvironment() check, so IS_DOCKER=true npx -y n8n-mcp </dev/null
stayed alive until SIGTERM arrived. The guard was added to prevent premature
shutdown when stdin closes in detached containers, but stdio MCP cannot
operate without an open stdin, so the guard protected a non-functional
scenario while breaking valid stateless stdio clients (mark3labs/mcp-go,
MCPJungle). Removed the guard; stdin handlers now register unconditionally.
isContainerEnvironment() and its fs.existsSync import were deleted.
Layer 2 — .github/workflows/release.yml bin path
Commit bc191b0 (v2.45.1) fixed Issue czlonkowski#693 (reported by @gjenkins20) by
switching the bin from dist/mcp/index.js to dist/mcp/stdio-wrapper.js in
package.json and both publish scripts — but missed release.yml:375 which
hardcoded the old path. CI publishes via release.yml, so every release from
v2.45.1 through v2.47.4 still shipped bin: dist/mcp/index.js. The fix
never reached users. release.yml is now consistent with the other three
sources, and tests/unit/bin-consistency.test.ts guards against the same
drift recurring.
Telemetry CLI preservation
Once the published bin routes through stdio-wrapper.js, npx n8n-mcp
telemetry enable|disable|status would stop working because the wrapper
suppresses console and never inspects argv. Extracted the handler to
src/telemetry/telemetry-cli.ts and called it from both index.ts and the
wrapper (the wrapper lazy-requires it before MCP_MODE/console setup).
Eliminates ~35 lines of near-identical duplication.
Tests
- tests/integration/mcp/stdio-shutdown.test.ts: 3 regression cases that
spawn dist/mcp/index.js and assert exit-on-stdin-close (with and without
IS_DOCKER=true) and exit-on-SIGTERM within a 500ms budget.
- tests/unit/bin-consistency.test.ts: static drift guard asserting that
package.json, both publish scripts, and release.yml agree on the
stdio-wrapper bin path.
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: apply simplify + review pass refinements
- telemetry-cli: drop unreachable break statements after process.exit()
(process.exit is typed 'never', noFallthroughCasesInSwitch stays green)
- stdio-shutdown test: use describe.skipIf() instead of ternary
- stdio-shutdown test: expectExitWithin early-return now also checks
signalCode, closing a gap where a child already killed by signal (null
exitCode, non-null signalCode) would fall through to the 'exit' event
await and time out instead of returning immediately
No behavioral change to production code beyond the telemetry-cli cleanup.
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore container guard in index.ts; test against stdio-wrapper.js
CI caught that removing the container guard broke the Docker entrypoint
tests (tests/integration/docker/docker-entrypoint.test.ts). Those tests
run `docker run -d --user root` which hits the entrypoint's root-switch
path at docker/docker-entrypoint.sh:122, which hardcodes
`node /app/dist/mcp/index.js` — NOT the wrapper. Detached containers
redirect stdin from /dev/null, so with the guard removed the MCP process
exited immediately and subsequent `docker exec` commands failed with
"container is not running".
The container guard wasn't protecting a non-functional scenario — it was
load-bearing for Docker's detached lifecycle, where the process receives
SIGTERM from `docker stop` instead of reading stdin.
Fix:
- Restore isContainerEnvironment() helper and the guarded block in
src/mcp/index.ts. Updated the surrounding comment to explain the
split: stdio-wrapper.ts is the npx path and handles stdin close
unconditionally (fixing czlonkowski#711); index.ts is the Docker path and keeps
the guard.
- Update tests/integration/mcp/stdio-shutdown.test.ts to spawn
dist/mcp/stdio-wrapper.js instead of dist/mcp/index.js. The wrapper
is the published bin entry (after the release.yml fix), so this
matches the actual `npx n8n-mcp` path users hit.
- Reframe the CHANGELOG to make clear the czlonkowski#711 fix is delivered via the
release.yml bin path change (Layer 2), not via Layer 1 (index.ts).
Layer 1 is intentionally left in place for Docker introspection.
Verified locally:
- IS_DOCKER=true node dist/mcp/stdio-wrapper.js </dev/null → exits
- IS_DOCKER=true node dist/mcp/index.js </dev/null → stays alive
(Docker introspection intact)
- node dist/mcp/index.js </dev/null → exits (non-container guard removed)
- npm test → 5313 pass, 74 skip, 0 fail
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent d9d847f commit ff486ea
18 files changed
Lines changed: 344 additions & 86 deletions
File tree
- .github/workflows
- dist
- mcp
- telemetry
- src
- mcp
- telemetry
- tests
- integration/mcp
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
372 | 372 | | |
373 | 373 | | |
374 | 374 | | |
375 | | - | |
| 375 | + | |
376 | 376 | | |
377 | 377 | | |
378 | 378 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
10 | 25 | | |
11 | 26 | | |
12 | 27 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments