MCP Server Disconnects After console.log: Keep stdout Clean
Your MCP server starts, prints a friendly ready message, and disappears from the client. That ready message is the failure. A stdio server uses stdout for protocol messages, not logs.
Bottom line
- A stdio MCP server reserves
stdoutfor JSON-RPC. - One
console.log()can corrupt the protocol stream. - Send diagnostics to
stderrwithconsole.error(). - Test the server through an MCP client or the MCP Inspector.
The MCP transport specification says a stdio server reads messages from stdin and writes messages to stdout. It permits logging on stderr.
The official MCP TypeScript server guide makes the consequence explicit: one console.log corrupts the JSON-RPC stream. Both sources were checked on August 20, 2026.
The symptom
The server appears to start correctly:
Starting filesystem server...
Then the client reports a closed connection, invalid JSON, or a server that has no tools. Some clients hide the parser error and only show that the server disconnected.
The failure often appears after you add a startup banner:
console.log('MCP server ready');
await server.connect(transport);
The process did start. The client received the banner where it expected a JSON-RPC message.
The cause
Stdio MCP does not use stdout as a terminal display. It uses stdout as a wire.
Every line on that wire must be a valid protocol message. Your banner is plain text, so the client cannot decode it as JSON-RPC. The same failure occurs with debug objects, progress messages, and library logs.
This is why the server can work in a direct unit test and fail inside Codex, Claude Code, Cursor, or another MCP client. The unit test can call your functions without parsing the stdio stream.
The fix
Change all diagnostic output to stderr.
Node.js:
// Wrong: corrupts the MCP stdio stream.
console.log('MCP server ready');
// Correct: visible to the operator, separate from the protocol.
console.error('MCP server ready');
Python:
import sys
print("MCP server ready", file=sys.stderr)
Also check dependencies that log during import or startup. Your code can keep stdout clean while a framework banner still writes to it.
Search the server before retesting:
rg "console\.log|print\(" src
Review each match. Tool output returned through the MCP SDK is valid. A raw print to stdout is not.
Verify the repair
Run the server through the MCP Inspector:
npx @modelcontextprotocol/inspector node dist/index.js
Then verify three things:
- The client connects.
- The tool list loads.
- One tool call returns a valid result.
Keep the startup banner on stderr during this test. If the banner appears in the terminal and the client stays connected, the channels are separated correctly.
Do not hide the failure with retries
A restart loop does not repair a corrupted stream. It repeats the same invalid first line and creates a noisy connect-disconnect cycle.
Fix the channel first. Add restart policy only after one clean process can connect and serve a tool call.
Related agent-tooling guides
Your agent can see the server but not its tools?
Book an agent rescue session. We trace the transport, configuration, and tool handshake live.
Need OpenClaw fixed live?
Remote rescue sessions for gateway, auth, tunnel, VPS, and model access problems.
See Rescue Session