You have built a local Model Context Protocol (MCP) server. It compiles without errors. It runs perfectly in your terminal. Yet, the moment you attempt to connect it to Claude Desktop, you are met with a persistent "Disconnected" status or a generic connection error.
If you dig into the logs, you likely see Error -32000 or Connection closed immediately.
For AI engineers and backend developers, this is often a transport layer mismatch rather than a logic error in your code. Because MCP relies heavily on stdio (Standard Input/Output) for communication, the protocol is extremely sensitive to process environments and stream pollution.
This guide dissects the root causes of these disconnections—specifically focusing on JSON-RPC stream corruption and Windows PATH resolution—and provides architectural fixes to stabilize your local MCP integration.
The Anatomy of the Error: JSON-RPC over Stdio
To fix the connection, you must first understand the transport mechanism. Claude Desktop communicates with your local server by spawning it as a subprocess.
Unlike HTTP, which has separate channels for headers and body, MCP often runs over stdio.
- stdin: Claude sends requests to your server.
- stdout: Your server sends JSON responses back to Claude.
- stderr: A channel reserved for logs and errors (ignored by the protocol parser).
The Root Cause: The error -32000 usually occurs when Claude's JSON-RPC parser reads data from stdout that is not valid JSON.
If your application prints a single line of plain text to stdout (e.g., Server listening on port 3000), the handshake fails immediately. The connection closes because the protocol parser encounters an unexpected token.
Solution 1: Eliminating Console Pollution
The most common culprit for immediate disconnection is the accidental usage of console.log.
In Node.js and many other runtime environments, console.log writes directly to stdout. Since stdout is exclusively reserved for MCP protocol messages, any debugging information sent here corrupts the data stream.
The Fix: Standardize Logging to Stderr
You must strictly enforce that all logging happens on stderr. Below is a TypeScript implementation of a logger utility that is safe for MCP servers.
// utils/logger.ts
/**
* A safe logger for MCP servers that guarantees writes to stderr.
* This prevents corruption of the JSON-RPC stdout stream.
*/
export const logger = {
info: (message: string, meta?: unknown) => {
const timestamp = new Date().toISOString();
console.error(`[INFO] ${timestamp}: ${message}`, meta ? JSON.stringify(meta) : '');
},
error: (message: string, error?: unknown) => {
const timestamp = new Date().toISOString();
console.error(`[ERROR] ${timestamp}: ${message}`, error);
},
debug: (message: string) => {
if (process.env.DEBUG) {
console.error(`[DEBUG]: ${message}`);
}
}
};
Global Override (The "Nuclear" Option)
If you are using third-party libraries that might be logging to stdout without your control, you should override the global console method at the entry point of your server.
Place this at the very top of your index.ts or main server file:
// index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// CRITICAL: Redirect console.log to console.error
// This ensures library logs do not crash the JSON-RPC connection
console.log = console.error;
const server = new Server(
{
name: "my-local-mcp-server",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
// ... rest of your server code
By reassigning console.log to console.error, you ensure that even if a dependency tries to print "Job Complete" to standard out, it will be redirected to standard error, preserving the integrity of the JSON-RPC stream.
Solution 2: Resolving Windows PATH and npx Issues
If you are on Windows and your logs show spawn ENOENT or "Executable not found," the issue lies in how Electron (the framework running Claude Desktop) handles environment variables.
When Claude Desktop spawns a subprocess, it does not necessarily inherit the full PATH configuration of your PowerShell or Command Prompt user profile. Consequently, commands like npx, npm, or even node might not be resolvable.
The Fix: Use Absolute Paths
Do not rely on the system PATH for local development. You should explicitly tell Claude where the Node.js executable is located.
Open your terminal and find your node path:
where.exe nodeOutput example:
C:\Program Files\nodejs\node.exeLocate your MCP script build artifact (usually in
./dist/index.jsor similar).Update your Claude configuration file (
%APPDATA%\Claude\claude_desktop_config.json) to use the absolute path to the executable and the script.
Incorrect Configuration (Prone to Failure):
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["-y", "ts-node", "src/index.ts"]
}
}
}
Correct Configuration (Robust):
{
"mcpServers": {
"my-server": {
"command": "C:\\Program Files\\nodejs\\node.exe",
"args": [
"C:\\Users\\DevUser\\Projects\\mcp-server\\dist\\index.js"
]
}
}
}
Note: Remember to escape backslashes in JSON strings (\\).
Why npx is Dangerous Here
npx is a package runner that performs network lookups and cache checks before execution. It often outputs status messages (e.g., "Need to install the following packages...") to stdout.
As discussed in Solution 1, any text sent to stdout that is not a JSON-RPC message will terminate the connection. Using the direct node executable against a compiled JavaScript file removes this risk entirely.
Advanced Troubleshooting: Inspecting the Logs
If the connection still fails, you need to verify what Claude is actually seeing. Claude Desktop maintains internal logs that record the stderr output from your MCP server.
Locating the Logs
- Windows:
%APPDATA%\Claude\logs - macOS:
~/Library/Logs/Claude
Look for the mcp-server-my-server.log (or similar naming convention).
What to Look For
- Syntax Errors: Did the script crash before starting?
- Silent Exits: Did the process start and immediately exit with code 0 or 1?
- JSON Partial Chunks: Sometimes the buffer flushes partially.
If you see your own log messages in this file, your console.error redirection is working. If the file is empty, the process likely never started due to a PATH issue.
Summary
The "Connection Closed" error in MCP is rarely a mystery; it is a violation of strict transport rules.
- Protocol Hygiene: MCP over Stdio requires
stdoutto be pure JSON. Never useconsole.log. - Environment Isolation: Electron apps on Windows do not see your terminal's PATH. Always use absolute paths to executables.
- Process Stability: Avoid intermediate runners like
npxorts-nodein the final configuration. Compile your TypeScript to JavaScript and run it directly withnode.
By strictly separating your data transport (stdout) from your logging (stderr) and defining explicit execution paths, you ensure a stable, production-grade connection between your local tooling and Claude Desktop.