How to Fix 'MCP Connection Closed' and Stdio EOF Errors in Claude and Cursor

The Quick Answer: The 3 Root Causes and Fixes

When an MCP client reports Connection closed: stdio process exited with code 1 or Unexpected end of JSON input, your server process crashed before establishing the JSON-RPC handshake.

Apply these three immediate fixes in your configuration file:

{
  "mcpServers": {
    "my-server": {
      "command": "/home/beomjin/envs/mcp/bin/python3",
      "args": ["-u", "/home/beomjin/mcp-server/server.py"],
      "env": {
        "PYTHONUNBUFFERED": "1",
        "PATH": "/home/beomjin/envs/mcp/bin:/usr/local/bin:/usr/bin:/bin"
      }
    }
  }
}
  1. Use Absolute Binary Paths: GUI applications do not inherit your terminal shell PATH (~/.zshrc or ~/.bashrc). Never use generic python3 or npx.
  2. Force Unbuffered I/O: Add Python flag -u and PYTHONUNBUFFERED=1. Buffered output stalls the handshake.
  3. Redirect Print Statements: Never write plain print("debug message") inside tool handlers. Anything written to stdout corrupts JSON-RPC payloads. Send logs to sys.stderr instead.

Diagnostic Matrix: Error Symptoms & Exact Resolutions

Use this reference table to match the error displayed in Claude Desktop or Cursor logs with its direct remedy.

Error Message in Logs Primary Root Cause Exact Resolution
stdio process exited with code 1 GUI missing environment PATH or missing virtualenv dependency Replace command with full path to venv binary (/home/user/venv/bin/python3).
SyntaxError: Unexpected token in JSON at position 0 Python print() or Node console.log() polluting stdout Divert all debugging logs to sys.stderr.write() or standard logging.
spawn npx ENOENT Node.js and NPM not located in system default /usr/bin Run which npx in terminal and place full binary path (/usr/local/bin/npx).
Connection timed out after 10000ms Heavy model initialization blocking the startup handshake Defer model weight loading until the first tool execution call.
Broken pipe (os error 32) Client sent SIGPIPE after server failed schema validation Verify that tool schema conforms to JSON Schema Draft 7 without invalid types.

Step 1: Fix GUI Environment PATH Disconnects

This is the most common pitfall for both macOS and Linux developers.

When you launch Claude Desktop or Cursor from an application launcher (such as GNOME, macOS Spotlight, or systemd), the app starts with a minimal system environment. It lacks your customized $PATH, nvm paths, pyenv shims, and API keys defined in ~/.bashrc or ~/.zshrc.

The Failure Case:

{
  "mcpServers": {
    "sqlite": {
      "command": "uvx",
      "args": ["mcp-server-sqlite", "--db-path", "~/test.db"]
    }
  }
}

If uvx is installed inside ~/.local/bin, the desktop client fails with spawn uvx ENOENT.

The Verified Solution:

Find the real absolute location of your tools:

which uvx
# Output: /home/beomjin/.local/bin/uvx

which npx
# Output: /usr/bin/npx

Expand the tilde ~ and provide explicit absolute paths:

{
  "mcpServers": {
    "sqlite": {
      "command": "/home/beomjin/.local/bin/uvx",
      "args": ["mcp-server-sqlite", "--db-path", "/home/beomjin/test.db"],
      "env": {
        "PATH": "/home/beomjin/.local/bin:/usr/local/bin:/usr/bin:/bin"
      }
    }
  }
}

Step 2: Stop Corrupting Stdout with Debug Prints

The MCP protocol uses standard input and standard output for framing JSON-RPC 2.0 messages.

If your code prints even a single line like Connecting to database... to standard output, the client parser chokes. It expects { "jsonrpc": "2.0", ... }, but receives plain text instead. The client abruptly drops the connection with an EOF error.

Bad Implementation:

# BROKEN: Will crash the MCP client immediately
@mcp.tool()
def fetch_user_data(user_id: int):
    print(f"Fetching record for {user_id}")  # Pollutes stdout!
    return db.query(user_id)

Clean Implementation:

Always route diagnostics to stderr or configure the Python logging module to output to standard error:

import sys
import logging

# Direct Python logging exclusively to standard error
logging.basicConfig(
    stream=sys.stderr,
    level=logging.INFO,
    format="[%(asctime)s] %(levelname)s: %(message)s"
)
logger = logging.getLogger("mcp-worker")

@mcp.tool()
def fetch_user_data(user_id: int):
    logger.info(f"Fetching record for {user_id}")  # Safe: travels on stderr
    return db.query(user_id)

Claude Desktop and Cursor capture standard error separately and print it safely into their application developer logs without touching the communication channel.


Step 3: Defer Heavy Cold Starts

MCP clients enforce strict initial handshake timeouts, often between 5 and 10 seconds.

If your MCP server imports heavy machine learning frameworks like torch, transformers, or connects to a remote database with slow round-trip latency at top-level module load, the client assumes the server hung and terminates the subprocess.

# SLOW: Causes timeout during server startup
import torch
from transformers import AutoModelForCausalLM

# Loading weights at module root blocks the MCP handshake
model = AutoModelForCausalLM.from_pretrained("heavy-weights-path")

Move resource-heavy initialization into a lazy cache pattern:

# FAST: Instant initial handshake, loads weights on demand
_model_instance = None

def get_model():
    global _model_instance
    if _model_instance is None:
        import torch
        from transformers import AutoModelForCausalLM
        _model_instance = AutoModelForCausalLM.from_pretrained("heavy-weights-path")
    return _model_instance

@mcp.tool()
def infer_code(prompt: str) -> str:
    model = get_model()
    # Execute inference
    return "completed"

Step 4: Inspect Client Logs Directly

Do not guess why your server terminated. Check the raw logs generated by your client.

Claude Desktop Log Locations:

  • Linux: ~/.config/Claude/logs/mcp*.log
  • macOS: ~/Library/Logs/Claude/mcp*.log
  • Windows: %APPDATA%\Claude\logs\mcp*.log

Monitor logs in real time while clicking retry in your client:

tail -f ~/.config/Claude/logs/mcp*.log

Cursor Log Locations:

  1. Open Cursor.
  2. Press Ctrl+Shift+U (or Cmd+Shift+U on macOS) to reveal the Output panel.
  3. Select MCP Log or Cursor Tab from the drop-down menu on the top right.

You will see the exact traceback and exit code emitted by your subprocess within milliseconds of failure.