MP-201c · Module 1

Stdio Internals

4 min read

The stdio transport is the simplest MCP transport — and the most misunderstood. When a client launches an MCP server via stdio, it spawns a child process, pipes stdin for writing requests, and reads stdout for responses. Every message is a JSON-RPC 2.0 object delimited by newlines. There is no HTTP, no WebSocket, no port binding. The operating system's pipe buffers are the network layer. This simplicity is why stdio is the default for local MCP servers: zero configuration, zero network exposure, instant startup.

Pipe management matters more than most developers realize. The OS allocates a fixed-size buffer for each pipe — typically 64 KB on Linux, 8 KB on macOS. When the buffer fills because the reader is not consuming data fast enough, the writer blocks. This is backpressure, and it can deadlock your MCP server if both client and server are waiting on each other's pipes simultaneously. The solution is always asynchronous I/O: never block on a write while you have pending reads, and drain stdout/stderr continuously.

Debugging stdio transport issues is notoriously difficult because you cannot inspect the pipe contents from outside the process pair. The most effective technique is a transparent proxy — a wrapper script that tees stdin and stdout to log files while forwarding everything unchanged. This gives you a complete message trace without modifying either the client or server code. Always log stderr separately; many MCP servers emit diagnostics on stderr that never reach the client.

#!/bin/bash
# Transparent stdio proxy for MCP debugging
# Usage: configure client to launch this script instead of the server directly

SERVER_CMD="$@"
LOG_DIR="/tmp/mcp-debug-$(date +%s)"
mkdir -p "$LOG_DIR"

# Tee stdin to log, forward to server stdin
# Tee server stdout to log, forward to our stdout
# Capture stderr separately
$SERVER_CMD \
  < <(tee "$LOG_DIR/client-to-server.jsonl") \
  > >(tee "$LOG_DIR/server-to-client.jsonl") \
  2>"$LOG_DIR/server-stderr.log"

# After exit, inspect:
# client-to-server.jsonl  — every request the client sent
# server-to-client.jsonl  — every response the server returned
# server-stderr.log       — diagnostic output