How to Fix MCP error -32000: Connection closed on Long-Running stdio Scraper Tools
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:
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.
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
// 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 Strategy | 50-Turn Completion Rate | Total Pipe Desyncs (-32000) | Peak Memory Usage |
|---|---|---|---|
Default (Unfiltered console.log + Default Chrome) | 24% | 38 crashes | 1,420 MB |
| Stderr Redirection Only | 82% | 9 crashes (OOM) | 1,380 MB |
| Stderr Redirection + Chromium Flags + 2GB Heap | 100% | 0 crashes | 840 MB |
Checklist to Eliminate Error -32000 Permanently
- Search your server codebase for
console.logand replace every occurrence withconsole.error. - Configure your headless browser wrapper with
--disable-loggingand--log-level=3. - Set
NODE_OPTIONS="--max-old-space-size=2048"if handling multi-page scraping or large PDF extractions. - Ensure long-running tools send periodic JSON-RPC progress notifications to prevent the host IDE from hitting client-side keepalive timeouts.