Skip to main content
RapidDev - Software Development Agency
mcp-tutorial

How to fix MCP JSON-RPC parse errors

JSON-RPC parse errors (-32700) in MCP are almost always caused by non-protocol data written to stdout. The MCP stdio transport uses stdout exclusively for JSON-RPC messages, so any console.log(), print(), or debug output on stdout corrupts the protocol stream. The fix is simple: redirect all logging to stderr using console.error() in Node.js or print(..., file=sys.stderr) in Python. This is the number one bug in custom MCP servers.

What you'll learn

  • Why stdout pollution causes JSON-RPC parse errors
  • How to redirect all logging from stdout to stderr
  • How to find and fix hidden stdout writes in dependencies
  • The cardinal rule of MCP stdio: all logging to stderr
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read10-15 minAll MCP servers using stdio transportMarch 2026RapidDev Engineering Team
TL;DR

JSON-RPC parse errors (-32700) in MCP are almost always caused by non-protocol data written to stdout. The MCP stdio transport uses stdout exclusively for JSON-RPC messages, so any console.log(), print(), or debug output on stdout corrupts the protocol stream. The fix is simple: redirect all logging to stderr using console.error() in Node.js or print(..., file=sys.stderr) in Python. This is the number one bug in custom MCP servers.

Fixing JSON-RPC Parse Errors in MCP Servers

The MCP stdio transport uses stdout for JSON-RPC protocol messages and stderr for logging. When anything other than valid JSON-RPC is written to stdout — a stray console.log, a Python print statement, a library debug message — the host receives corrupted data and reports a parse error. This is the single most common bug in custom MCP servers, and the fix is always the same: move all output to stderr.

Prerequisites

  • An MCP server showing JSON-RPC parse errors
  • Access to the server's source code
  • Basic understanding of stdout vs stderr

Step-by-step guide

1

Identify the JSON-RPC parse error

The parse error appears in your MCP host logs with error code -32700 (parse error) or -32000 (connection closed). The host may also report that the connection was lost unexpectedly. Check Claude Desktop logs at ~/Library/Logs/Claude/mcp*.log or Cursor's MCP output panel. The error often shows the corrupted data that caused the parse failure.

typescript
1# Error messages you will see:
2
3# "JSON-RPC parse error (-32700)"
4# "Parse error: Unexpected token 'D' in JSON"
5# "Connection closed (-32000)"
6# "Failed to parse MCP message"
7# "Unexpected non-whitespace character after JSON"
8
9# The log might show the offending output:
10# "Received invalid data: Debug: connecting to database..."
11# This means someone wrote "Debug: connecting to database..." to stdout
12
13# Check logs:
14tail -n 30 ~/Library/Logs/Claude/mcp*.log

Expected result: You see a parse error in the logs, possibly with the non-JSON data that caused it.

2

Replace all console.log with console.error in Node.js

In Node.js, console.log writes to stdout and console.error writes to stderr. Replace every console.log in your MCP server code with console.error. This includes debug logging, status messages, and informational output. The only thing that should ever go to stdout is the JSON-RPC protocol traffic, which the MCP SDK handles automatically.

typescript
1// WRONG — writes to stdout, corrupts JSON-RPC stream
2console.log("Server starting...");
3console.log("Connected to database");
4console.log(`Processing request: ${id}`);
5
6// RIGHT — writes to stderr, safe for MCP
7console.error("Server starting...");
8console.error("Connected to database");
9console.error(`Processing request: ${id}`);
10
11// Also watch for:
12process.stdout.write("data"); // WRONG
13process.stderr.write("data"); // RIGHT

Expected result: All logging output goes to stderr, leaving stdout clean for JSON-RPC messages.

3

Fix Python print statements

In Python, print() writes to stdout by default. For MCP servers, always use print(..., file=sys.stderr) or the logging module configured to write to stderr. Also check for libraries that may print to stdout — common offenders include ORM query loggers, HTTP client debug modes, and configuration loaders that print warnings.

typescript
1import sys
2import logging
3
4# WRONG writes to stdout
5print("Server starting...")
6print(f"Processing: {request_id}")
7
8# RIGHT writes to stderr
9print("Server starting...", file=sys.stderr)
10print(f"Processing: {request_id}", file=sys.stderr)
11
12# BETTER use logging module configured for stderr
13logging.basicConfig(
14 level=logging.INFO,
15 stream=sys.stderr,
16 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
17)
18logger = logging.getLogger(__name__)
19
20logger.info("Server starting...")
21logger.info(f"Processing: {request_id}")
22logger.error("Something went wrong")

Expected result: All Python output goes to stderr via the logging module or explicit file=sys.stderr.

4

Find hidden stdout writes from dependencies

Even if your code is clean, third-party libraries may write to stdout. Common culprits include database drivers logging queries, HTTP clients in debug mode, and configuration libraries printing warnings. To find the source, temporarily redirect stdout to stderr at the process level and check if the error resolves. If it does, enable libraries one by one to find the offender.

typescript
1// Node.js — redirect stdout to stderr for debugging
2// Add this at the very top of your entry file:
3const originalStdoutWrite = process.stdout.write.bind(process.stdout);
4process.stdout.write = (chunk: any, ...args: any[]) => {
5 // Log what would have gone to stdout
6 console.error(`[STDOUT INTERCEPTED]: ${chunk.toString().trim()}`);
7 // Still write it (remove this line to suppress)
8 return originalStdoutWrite(chunk, ...args);
9};
10
11# Python redirect stdout to stderr
12import sys
13import io
14
15# Capture stdout writes
16class StderrRedirector(io.TextIOBase):
17 def write(self, s):
18 sys.stderr.write(f"[STDOUT INTERCEPTED]: {s}")
19 return len(s)
20
21sys.stdout = StderrRedirector()

Expected result: You identify which library or code path is writing to stdout and can configure or fix it.

5

Verify the fix with MCP Inspector

After fixing all stdout writes, test your server with MCP Inspector to confirm the JSON-RPC stream is clean. MCP Inspector will show any parse errors if stdout is still being polluted. A successful test means the host will also connect without errors. If you are building complex MCP servers with many dependencies and having trouble isolating stdout pollution, the RapidDev team can help audit your server code.

typescript
1# Test with MCP Inspector
2npx -y @modelcontextprotocol/inspector
3
4# Enter your server command when prompted
5# If Inspector connects and shows tools stdout is clean
6# If Inspector shows parse errors stdout still has non-JSON output

Expected result: MCP Inspector connects successfully, lists tools, and no parse errors appear.

Complete working example

src/clean-stdout-server.ts
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3import { z } from "zod";
4
5// THE CARDINAL RULE: All logging goes to stderr, never stdout.
6// stdout is reserved exclusively for JSON-RPC protocol messages.
7
8console.error("Starting MCP server...");
9
10const server = new McpServer({
11 name: "clean-stdout-server",
12 version: "1.0.0",
13});
14
15server.tool(
16 "process-data",
17 "Process some data",
18 { input: z.string() },
19 async ({ input }) => {
20 // ALL logging must use console.error
21 console.error(`Processing input: ${input}`);
22
23 // NEVER use console.log in an MCP server
24 // console.log("this would break the JSON-RPC stream");
25
26 const result = input.toUpperCase();
27 console.error(`Result computed: ${result}`);
28
29 return {
30 content: [{ type: "text", text: result }],
31 };
32 }
33);
34
35const transport = new StdioServerTransport();
36await server.connect(transport);
37console.error("MCP server connected and ready.");

Common mistakes when fixing MCP JSON-RPC parse errors

Why it's a problem: Using console.log() instead of console.error() for debug output

How to avoid: Replace every console.log with console.error in your MCP server code. This is the most common cause of parse errors.

Why it's a problem: Python print() without file=sys.stderr

How to avoid: Always use print(..., file=sys.stderr) or configure the logging module to write to stderr.

Why it's a problem: Third-party library writing debug output to stdout

How to avoid: Configure the library to suppress stdout output or redirect it to a file. Use the stdout interception technique to find the offending library.

Why it's a problem: Writing startup banners or ASCII art to stdout

How to avoid: Move all startup messages to stderr. Nothing except JSON-RPC should ever appear on stdout.

Best practices

  • Treat stdout as sacred in MCP stdio servers — only JSON-RPC protocol messages belong there
  • Use console.error() for ALL logging in Node.js MCP servers
  • Configure Python logging to use sys.stderr as the output stream
  • Add a stdout interception check during development to catch accidental writes
  • Review third-party dependencies for stdout writes before integrating them
  • Test with MCP Inspector after any code changes that add new logging or dependencies
  • Consider creating a project linting rule that flags console.log usage in MCP server code

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

My MCP server shows 'JSON-RPC parse error (-32700)' when connecting. I think something is writing to stdout that should not be. Help me find all stdout writes in my [TypeScript/Python] code and redirect them to stderr.

MCP Prompt

Audit my MCP server code for stdout pollution. Find all console.log, process.stdout.write, and print() calls and replace them with stderr equivalents. Also check if any imported libraries might write to stdout.

Frequently asked questions

Why does MCP use stdout for protocol messages?

The stdio transport is modeled after the Language Server Protocol (LSP) which also uses stdout for protocol messages. It allows simple subprocess communication without network setup. Stderr is available for human-readable logging since it is not part of the protocol stream.

Can I use a logging library like Winston or Pino?

Yes, but configure it to write to stderr, not stdout. Most logging libraries default to stdout. For Winston: new winston.transports.Stream({ stream: process.stderr }). For Pino: pino({ transport: { target: 'pino/file', options: { destination: 2 } } }).

Does this apply to HTTP transport too?

No, the stdout issue is specific to stdio transport. With HTTP transport, the protocol uses HTTP request/response, so console.log does not corrupt the communication. However, it is still good practice to use console.error for logging consistency.

How can I tell if the error is stdout pollution vs a server bug?

If the error message includes unexpected text like 'Parse error: Unexpected token D' where D is the start of a debug message, it is stdout pollution. If the error is about malformed JSON-RPC structure, it may be a server or SDK bug.

My server has no console.log but I still get parse errors. What else could it be?

Check for: 1) Third-party libraries printing to stdout, 2) process.stdout.write calls, 3) Child processes that inherit stdout, 4) Uncaught exception handlers that write to stdout. Use the stdout interception technique to find the source.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.