From fb2d306dc3722d0fcacdb09e0c937580ba0b1d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romuald=20Cz=C5=82onkowski?= <56956555+czlonkowski@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:26:43 +0100 Subject: [PATCH] fix: intercept stdout writes to prevent JSON-RPC corruption in stdio mode (#673) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: intercept process.stdout.write to prevent JSON-RPC corruption in stdio mode (#628, #627, #567) Console method suppression alone was insufficient — native modules, n8n packages, and third-party code can call process.stdout.write() directly, leaking debug output (refCount, dbPath, clientVersion, protocolVersion, etc.) into the MCP JSON-RPC stream. Added stdout write interceptor that only allows JSON-RPC messages through (objects containing "jsonrpc" field). All other writes are redirected to stderr. This fixes the flood of "Unexpected token is not valid JSON" warnings on every new Claude Desktop chat. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 (1M context) * ci: add Docker Hub login to fix buildx bootstrap rate limiting GitHub-hosted runners hit Docker Hub anonymous pull limits when setup-buildx-action pulls moby/buildkit. Add docker/login-action for Docker Hub before setup-buildx-action in all 4 workflows: docker-build.yml, docker-build-fast.yml, docker-build-n8n.yml, release.yml. Uses DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/docker-build-fast.yml | 8 +++++++- .github/workflows/docker-build-n8n.yml | 6 ++++++ .github/workflows/docker-build.yml | 22 +++++++++++++++++----- .github/workflows/release.yml | 10 ++++++++-- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- src/mcp/stdio-wrapper.ts | 19 +++++++++++++++++++ 8 files changed, 68 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker-build-fast.yml b/.github/workflows/docker-build-fast.yml index 71cefd9..be13fa3 100644 --- a/.github/workflows/docker-build-fast.yml +++ b/.github/workflows/docker-build-fast.yml @@ -29,9 +29,15 @@ jobs: with: lfs: true + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - + - name: Log in to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 diff --git a/.github/workflows/docker-build-n8n.yml b/.github/workflows/docker-build-n8n.yml index d7c4b95..36a3953 100644 --- a/.github/workflows/docker-build-n8n.yml +++ b/.github/workflows/docker-build-n8n.yml @@ -55,6 +55,12 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 70a58ad..40f026e 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -71,13 +71,19 @@ jobs: " echo "✅ Synced package.runtime.json to version $VERSION" + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 - + - name: Log in to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 @@ -85,7 +91,7 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Extract metadata id: meta uses: docker/metadata-action@v5 @@ -173,13 +179,19 @@ jobs: " echo "✅ Synced package.runtime.json to version $VERSION" + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 - + - name: Log in to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89890a4..92557d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -441,12 +441,18 @@ jobs: " echo "✅ Synced package.runtime.json to version $VERSION" + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d915dd..4472fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.41.2] - 2026-03-27 + +### Fixed + +- **MCP initialization floods Claude Desktop with JSON parse errors** (Issues #628, #627, #567): Intercept `process.stdout.write` in stdio mode to redirect non-JSON-RPC output to stderr. Console method suppression alone was insufficient — native modules (better-sqlite3), n8n packages, and third-party code can call `process.stdout.write()` directly, corrupting the JSON-RPC stream. Only writes containing valid JSON-RPC messages (`{"jsonrpc":...}`) are now allowed through stdout; everything else is redirected to stderr. This fixes the flood of "Unexpected token is not valid JSON" warnings on every new chat in Claude Desktop, including leaked `refCount`, `dbPath`, `clientVersion`, `protocolVersion`, and other debug strings. + +Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en + ## [2.41.1] - 2026-03-27 ### Fixed diff --git a/package-lock.json b/package-lock.json index dd33495..8edc6b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n-mcp", - "version": "2.41.1", + "version": "2.41.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "n8n-mcp", - "version": "2.41.1", + "version": "2.41.2", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "1.28.0", diff --git a/package.json b/package.json index 4bf2eb4..6489cc5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp", - "version": "2.41.1", + "version": "2.41.2", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/mcp/stdio-wrapper.ts b/src/mcp/stdio-wrapper.ts index 8dac848..7f578c1 100644 --- a/src/mcp/stdio-wrapper.ts +++ b/src/mcp/stdio-wrapper.ts @@ -39,6 +39,25 @@ console.clear = () => {}; console.count = () => {}; console.countReset = () => {}; +// CRITICAL: Intercept process.stdout.write to prevent non-JSON-RPC output (#628, #627, #567) +// Console suppression alone is insufficient — native modules (better-sqlite3), n8n packages, +// and third-party code can call process.stdout.write() directly, corrupting the JSON-RPC stream. +// Only allow writes that look like JSON-RPC messages; redirect everything else to stderr. +const originalStdoutWrite = process.stdout.write.bind(process.stdout); +const stderrWrite = process.stderr.write.bind(process.stderr); + +process.stdout.write = function (chunk: any, encodingOrCallback?: any, callback?: any): boolean { + const str = typeof chunk === 'string' ? chunk : chunk.toString(); + // JSON-RPC messages are JSON objects with "jsonrpc" field — let those through + // The MCP SDK sends one JSON object per write call + const trimmed = str.trimStart(); + if (trimmed.startsWith('{') && trimmed.includes('"jsonrpc"')) { + return originalStdoutWrite(chunk, encodingOrCallback, callback); + } + // Redirect everything else to stderr so it doesn't corrupt the protocol + return stderrWrite(chunk, encodingOrCallback, callback); +} as typeof process.stdout.write; + // Import and run the server AFTER suppressing output import { N8NDocumentationMCPServer } from './server';