One stray print breaks an MCP server over stdio
An MCP server running over stdio talks JSON-RPC on stdout. That is the whole transport. Every byte on that stream is supposed to be a protocol message.
So a debugging print("got here") is not a harmless debugging print. It
lands in the middle of the message stream, the client fails to parse it, and
what you see on the other end is a generic "server disconnected" or a JSON
parse error with no obvious relationship to the line you added. I lost real
time to this before it clicked.
Rules that follow from it:
- Log to stderr, always. Clients capture stderr and surface it, so it is genuinely useful, not a black hole.
- Python's
loggingalready defaults to stderr, which is why logging works andprintdoes not.printneedsfile=sys.stderrevery time. - Third-party libraries are the real risk. Progress bars, banner messages on import, a helpful "downloading model weights" line. Any of them can write to stdout inside your process and take the server down with it.
The defensive move, if you are pulling in dependencies you do not control, is
to reassign sys.stdout to sys.stderr at startup and keep a private handle
to the real one for the protocol. Ugly, effective.
Two other things that helped: the failure looks identical to a crash on startup, so check the client's MCP logs before you go debugging your own initialization, and running the server by hand in a terminal shows you immediately whether the first thing it emits is valid JSON.