The mechanism
A stdio client launches or connects to a child process and exchanges newline-delimited UTF-8 JSON messages over standard input and output. Standard output belongs to the protocol. Diagnostic text belongs on standard error. A harmless-looking startup banner on stdout can corrupt the first exchange.
The child process still has operating-system permissions. MCP does not automatically isolate its filesystem, network, environment or subprocesses. A package launched for convenience may read everything the launching account can read unless another boundary restricts it.
For the handbook, keep the server read-only and in-memory. Review its source and dependencies before enabling it in a host. Do not pass a broad home directory, Docker socket or inherited secret collection merely because the command is labelled local.
Worked example
This Python fragment demonstrates stream discipline only, not a complete MCP server. The application logger writes to stderr. JSON serialization escapes embedded newlines so each message occupies one physical line. The SDK should normally own framing and protocol handling in the finished implementation.
import json
import sys
def diagnostic(message):
print(message, file=sys.stderr, flush=True)
def emit_fixture(message):
sys.stdout.write(json.dumps(message, separators=(",", ":")) + "\n")
sys.stdout.flush()
# Do not call emit_fixture alongside a running SDK transport.
diagnostic("Synthetic fixture loaded; no external data accessed")
Practice: predict, inspect, explain
Offline exercise. Inspect the code rather than launching an installed MCP host. Predict the destination of the diagnostic. Create a string containing a newline, serialize it with json.dumps, and count the physical lines in the serialized output. Then inventory the environment variables and directories your proposed server actually needs, without reading their values.
Expected observation: message framing can be correct while the process remains overprivileged. Write separate acceptance criteria for protocol cleanliness and runtime permissions. Both must pass before a useful local integration is considered ready.
Troubleshooting and trade-offs
A hanging startup may be a buffered stdout write, a child waiting for input or a host that expects a different era. A parse error often points to a print statement or dependency banner. When shutting down, use the documented transport lifecycle and bounded termination behavior. Closing stdin supports graceful completion; killing a process cannot reverse an already committed external effect.
Interview practice
Why is stderr allowed for logs?
It is outside the JSON message stream, so diagnostic output does not corrupt protocol framing. Logs still require redaction because hosts may collect or display them.
What does local execution authorize?
Only the explicitly intended operation. The process may technically inherit wider permissions, so constrain its runtime and capability surface independently of the transport.
Completion check
Explain two independent failures: a clean protocol with excessive privileges, and a narrow process with invalid stdout framing.
Sources and version notes
This edition targets MCP 2026-07-28, checked 6 October 2026. SDK examples are version-sensitive and labelled when not executed. Synthetic fixtures are learning material, not protocol conformance evidence.
- Official documentation: Stdio
- Official documentation: Local server security
- Official documentation: Debugging
Make the understanding yours.
Use the completion check above. Mark this chapter when you can explain the mechanism and its limits.
Self-assessed reading progress. This does not certify that a lab ran or a system is secure.