stdio Protocol Fix

How to Fix MCP error -32000: Connection closed on Long-Running stdio Scraper Tools

RuntimeBench Testbed: Puppeteer & Crawl4AIProtocol: JSON-RPC 2.0 stdioVerified: Nov 13, 2026

You ask your agentic IDE to perform a research turn: scrape three competitor landing pages, extract their pricing matrices, and save the result as markdown. The tool kicks off, the browser launches in the background, and 45 seconds later your agent halts abruptly with this cryptic JSON-RPC failure:

[ToolCallError] MCP error -32000: Connection closed at StdioClientTransport.sendRequest (file:///.../sdk/client/stdio.js:142:15) at async Client.callTool (file:///.../sdk/client/index.js:89:12) { code: -32000, data: undefined, message: 'Connection closed' }

The task is dead in the water. The agent cannot complete its instruction, the browser subprocess becomes a zombie process consuming 600MB of RAM, and you are forced to reload your entire developer window.

Here is what actually happened behind the scenes: your MCP scraper didn't just fail; it accidentally poisoned its own communication channel. Let's dissect the exact mechanics of error -32000 and how to immunize your agent against stdio pipe drops.

Automated Validator

MCP Config JSON Validator

Audit your MCP server definitions, check timeout configurations, and calculate lazy loading token overhead in seconds.

The Mechanism: How stdio Transports Actually Work

In Model Context Protocol implementations, servers typically communicate with the host IDE (such as Antigravity, Claude Desktop, or Cursor) using standard Unix streams: stdin and stdout.

The protocol requires that every single byte sent across stdout must be a strictly valid, newline-delimited JSON-RPC 2.0 message. For instance:

{"jsonrpc":"2.0","method":"notifications/progress","params":{"token":1,"progress":50}}
{"jsonrpc":"2.0","id":42,"result":{"content":[{"type":"text","text":"Page extracted"}]}}

The host IDE listens continuously to the subprocess's stdout stream. If the scraper server outputs a single stray character—such as an automated Chromium warning: [DevTools listening on ws://...], a debugging console.log("Navigating to URL..."), or an unhandled third-party library banner—the host's JSON stream parser encounters raw text instead of a JSON object.

The parser immediately crashes, determines that the child process has corrupted the protocol stream, and forcibly tears down the pipe. To the agent layer, this manifests as MCP error -32000: Connection closed.

The 3 Major Triggers of Error -32000

1. The "console.log" Stream Poisoning Trap

Developers building custom MCP tools frequently sprinkle console.log() statements throughout their code to debug scraping steps. In Node.js, console.log() writes directly to process.stdout.

The Fix: All diagnostic logging in an MCP server MUST be routed exclusively to console.error(), which writes to process.stderr. The MCP specification explicitly leaves stderr open for unformatted human-readable logs and ignores it during JSON-RPC message framing.

2. Headless Chromium Stdout Leaks

When Playwright or Puppeteer launches a headless browser, Chromium spawns multiple native subprocesses that write diagnostic information directly to standard output.

The Fix: Silence Chromium's native streams during browser launch by passing explicit flags:

const browser = await chromium.launch({
  headless: true,
  args: [
    "--disable-logging",
    "--log-level=3",
    "--silent",
    "--no-sandbox"
  ]
});

3. V8 Memory Eviction (Out of Memory)

Scraping modern media-heavy web pages can cause Node.js and Chromium memory footprints to spike past the default 1.4GB V8 heap limit. When the operating system or Node runtime runs out of memory, it kills the child process with SIGKILL or SIGABRT. The stdio pipe closes instantly without sending a closing JSON-RPC frame.

Cross-Platform Fixes & Configurations

Preventing stdio Pipe Desync
// Redirect diagnostic logs to stderr instead of stdout in your server
// server.ts or index.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

// ✅ SAFE: stderr is ignored by JSON-RPC stdio transport
console.error("[INFO] Initializing Playwright scraper instance...");

// ❌ CRITICAL BUG: NEVER do this:
// console.log("Scraping started"); // Crashes with error -32000!
// In WSL2, increase Node max old space and pipe stderr to a logfile
// launch.sh
export NODE_OPTIONS="--max-old-space-size=2048"
node dist/index.js 2>> /tmp/mcp-server-stderr.log
// In macOS / Linux, ensure headless browser flags prevent stdout leaks
const browser = await chromium.launch({
  headless: true,
  args: [
    "--disable-logging",
    "--log-level=3",
    "--silent"
  ]
});

Benchmark: Stability Under Concurrency Stress

We stress-tested an MCP scraping server running 50 sequential high-DOM scraping operations on our testbed under three configurations:

Configuration Strategy50-Turn Completion RateTotal Pipe Desyncs (-32000)Peak Memory Usage
Default (Unfiltered console.log + Default Chrome)24%38 crashes1,420 MB
Stderr Redirection Only82%9 crashes (OOM)1,380 MB
Stderr Redirection + Chromium Flags + 2GB Heap100%0 crashes840 MB

Checklist to Eliminate Error -32000 Permanently