Skip to main content
← Back to Articles
mcptroubleshootingerrorstdiodebugging2026

Fix MCP Error -32000: Connection Closed

MCP error -32000 (Connection closed) means the server process already died. The real causes: stdout pollution, Windows .cmd shims, missing env vars, and transport mismatches — with fixes for each.

By Web MCP Guide•September 12, 2026•12 min read

What does MCP error -32000 mean? It's the JSON-RPC code for ConnectionClosed — the client's transport onclose handler fired. For a stdio server (the default for local MCP servers in Claude Desktop, Cursor, Claude Code, and most other hosts), that means the child process your client spawned has already exited. It is not a timeout and it is not "the server is slow" — by the time you see -32000, the process is dead and the client is just reporting that the pipe closed.

This error shows up across clients — Claude Desktop, Cursor, Claude Code, Windsurf — because it's a transport-level condition, not a client bug. The fix is almost always in the server process or its spawn command, not in the client. This guide covers the actual causes in the order you're likely to hit them, not a generic "restart the app" checklist.

If you're still setting up your first server and haven't hit this yet, How to Build Your First MCP Server covers the working baseline this error usually deviates from. If your specific symptom is "Claude Desktop doesn't see my server at all" rather than a -32000 in the logs, that's a different failure mode — see Claude Desktop Not Recognizing MCP Server instead.

Quick Reference

CauseHow commonFix
Stdout pollution (logging to stdout)Most commonRoute all logs to stderr
Windows .cmd shim (npx/npm/uvx/pnpm)Very common on WindowsUse cmd /c wrapper or full .cmd path
Missing environment variable read at startupCommonConfirm the exact env vars the client passes to spawned processes
Transport mismatch (client expects stdio, server started in HTTP mode)CommonMatch transport flag to what the client config expects
Git Bash path rewriting on WindowsWindows + Git Bash onlyUse a native path format, not a Git Bash-mangled one

Cause 1: Stdout Pollution (the Most Common One)

MCP's stdio transport uses stdout exclusively for JSON-RPC protocol messages. If anything else writes to stdout — a console.log() debug line you forgot to remove, a startup banner from a framework, a dependency that logs to stdout by default — it corrupts the message stream. The client can't parse a log line as a JSON-RPC message, the framing breaks, and the connection drops. This is functionally indistinguishable from a crash from the client's side, even though your server process might technically still be running.

The fix: every log line, print statement, and startup banner in a stdio server has to go to stderr, not stdout. In Node.js:

// Wrong — this corrupts the stdio protocol stream
console.log("Server starting...");

// Right — stderr is safe for logging
console.error("Server starting...");

In Python, the same principle applies — print() defaults to stdout, so route logging through sys.stderr or the standard logging module configured with a StreamHandler(sys.stderr) rather than plain print() calls left in for debugging.

This is also why a server that works perfectly when you run it directly in a terminal can fail the moment an MCP host spawns it — running it manually, you don't notice a stray print statement mixed into stdout, because you're not parsing it as a protocol stream. The client is.

Cause 2: Windows .cmd Shims

If you're on Windows and your mcp.json command is npx, npm, uvx, or pnpm, this is worth checking before anything else. On Windows, these tools aren't real executables — they're .cmd batch file shims that wrap the actual binary. Node's child_process.spawn() (which is what MCP clients use under the hood to launch stdio servers) cannot execute a .cmd file directly without going through a shell. Without that, the spawn call fails immediately, and the transport reports -32000 before the server ever gets a chance to start.

The fix: point the command at the actual executable path, or wrap it so it runs through a shell. A common working pattern:

{
  "mcpServers": {
    "my-server": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "my-mcp-server-package"]
    }
  }
}

Some clients handle this automatically and some don't — if a config that works fine on macOS or Linux fails with -32000 specifically on Windows and nowhere else, this shim issue is the first thing to check, not the last.

Cause 3: Missing Environment Variables

Stdio servers only inherit a platform-dependent subset of the parent process's environment — not automatically your full shell environment, and not necessarily the same set across macOS, Linux, and Windows. If your server reads a required environment variable (an API key, a config path) during its startup sequence and that variable isn't present, most servers crash immediately rather than falling back to a default. An immediate crash on startup looks exactly like -32000 from the client's side, because it is one — the process exited before it could establish the connection.

The fix: explicitly set every environment variable your server needs inside the env block of its mcpServers entry, rather than assuming it'll inherit something from your shell profile:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server-package"],
      "env": {
        "REQUIRED_API_KEY": "your-actual-key-here"
      }
    }
  }
}

If you're not sure whether a missing variable is the cause, run the exact same command from a fresh terminal with a minimal environment (not your normal dev shell, which likely has everything already exported) and see if it crashes the same way.

Cause 4: Transport Mismatch

Most MCP clients default to spawning a server over stdio — they launch your command as a subprocess and talk to it over stdin/stdout. But plenty of example servers and copy-pasted tutorial commands start a server in HTTP or SSE mode by default, or require an explicit flag to run in stdio mode instead. If the client spawns a process expecting a stdio handshake and the process instead tries to bind an HTTP port and waits for incoming requests, the stdio side sees no valid handshake, times out or closes, and -32000 is what gets reported.

The fix: check the server's own documentation for how to force stdio mode explicitly — commonly a --transport stdio flag or equivalent — and confirm your mcp.json command matches it. Don't assume a server defaults to the transport your client expects just because it's the more common choice for local setups.

Cause 5: Git Bash Path Rewriting (Windows-Specific)

This one is narrow but has burned enough people to be worth naming: Git Bash on Windows automatically rewrites Windows-style drive paths. A path like D:\projects\server can get silently rewritten to something like /d/projects/server before it reaches your server process, and depending on how the server parses that argument, it can produce a startup error like "is a directory" instead of a clean file path — which crashes the process before it connects, and again surfaces as -32000.

The fix: if you're configuring MCP from a Git Bash shell on Windows and hitting this, test the same command from a plain Windows terminal (Command Prompt or PowerShell) instead. If the config works there and fails only from Git Bash, the path rewriting is the cause, and the fix is to avoid constructing the path inside Git Bash for this specific purpose.

How to Actually Debug This Instead of Guessing

Don't cycle through the five causes above blind. Two steps narrow it down fast:

1. Run the exact server command outside your MCP client, in a plain terminal. Copy the command and args from your mcp.json and run them directly. If the process crashes immediately, you'll see the real error on stderr — a missing module, a thrown exception, an unhandled promise rejection — instead of the generic -32000 the client reports. This single step resolves most cases, because the client's -32000 message tells you that the process died, not why.

2. Test with the MCP Inspector. The official inspector runs your server in isolation and shows you the raw protocol exchange:

npx @modelcontextprotocol/inspector node your-server.js

If the Inspector connects cleanly but your actual client still shows -32000, the problem is specific to how that client is spawning the process (an environment or shim issue, per causes 2–4 above) rather than a bug in the server itself. If the Inspector also fails to connect, the problem is in the server, and you're back to reading stderr from step 1.

A Crash That Only Happens Sometimes

If -32000 is intermittent rather than every time, look for a race condition in your server's startup sequence rather than any of the five causes above — those tend to be deterministic (they fail the same way every time). An intermittent -32000 usually means the server is doing asynchronous setup (a database connection, an async import, a network call) before it's ready to handle the initial handshake, and the client's request arrives before that setup finishes. Make sure your server doesn't start listening on stdio until everything it depends on has actually finished initializing, not just been kicked off.

Frequently Asked Questions

Q: What does MCP error -32000 actually mean?
A: It's the JSON-RPC error code MCP clients report for ConnectionClosed — the transport's onclose event fired, which for a stdio server means the child process already exited. It doesn't mean the server is slow or unresponsive; it means the process is no longer running by the time the client checks.

Q: My server works fine when I run it manually but fails with -32000 through my MCP client — why?
A: The most common cause is stdout pollution: a console.log() or print() statement that's harmless in a normal terminal but corrupts the JSON-RPC message stream when the client is parsing stdout as protocol data. Move all logging to stderr.

Q: I'm on Windows and get -32000 with an npx-based server. What should I check first?
A: Whether your command is trying to run npx, npm, uvx, or pnpm directly. These are .cmd shims on Windows, not real executables, and Node's spawn call can't execute them without a shell wrapper. Point the command at cmd /c npx ... (or the equivalent for your tool) instead of npx directly.

Q: How do I see the real error behind a -32000?
A: Run the exact command and arguments from your mcp.json directly in a terminal, outside the MCP client. Whatever exception or crash message appears on stderr there is the actual cause; -32000 is just the client noticing the process is gone.

Q: Does -32000 ever mean a timeout instead of a crash?
A: No. -32000 specifically corresponds to the connection closing, which for stdio means the process exited. A hung-but-alive process produces a different symptom (the client waiting indefinitely, not a -32000), so if you're seeing this exact code, the process has already terminated.

Q: Can a missing API key cause a -32000 instead of a clear "missing credential" error?
A: Yes, if your server reads that key during startup and throws an unhandled exception when it's absent rather than catching the error and logging a clear message. From the client's side, an unhandled startup crash and a deliberate process.exit(1) look the same — the process is gone, and -32000 is what gets reported either way.

Related Guides


  • How to Build Your First MCP Server

  • How to Debug MCP Server Issues

  • Claude Desktop Not Recognizing MCP Server

  • Local vs. Remote MCP Servers

  • MCP Roots, Sampling, and Elicitation Explained

  • Related guides