What this is
This newsletter covers one thing: production patterns for Claude Code and MCP workflows. Not links to other people's takes. Not "AI is eating software" hot takes. The specific, unglamorous discipline that separates an MCP server that passes a demo from one that runs unattended for thirty days.
One issue every two weeks. One pattern per issue. If there is nothing worth writing, I do not send.
To start: a production problem that every MCP author hits eventually, usually at 11pm, usually the day before a deadline.
The stdout corruption story
Six months of shipping MCP servers against real Claude clients — not tutorial fixtures, live ones with timeouts, retries, and customers downstream — and the same shape of bug keeps showing up.
You build an MCP server. It works. You demo it. You ship it. A week later, a user runs the same tool while a background log writer flushes to stdout. The frame stream desynchronizes mid-call. The client throws a parse error. The agent retries on a connection that is already poisoned. Your handler runs twice and creates two records.
Nothing you wrote is wrong on its own. The shape of the protocol — newline-delimited JSON-RPC over a process pipe, no recovery semantics on the wire — does not forgive any of it.
The MCP stdio transport uses stdout to carry protocol messages. Every console.log call, every dependency that writes to stdout, every unhandled rejection that Node prints to stdout by default will corrupt the protocol stream and silently break the connection from the Claude side.
The production rule is simple: all diagnostic output goes to stderr. All protocol output goes to stdout. Nothing else touches stdout.
This breaks in practice in several ways. A transitive dependency you do not control calls console.log on startup. A JSON parse error in a request handler causes Node to print to stdout. A process.stdout.write call somewhere in a utility library lands in the middle of a protocol frame.
The fix is four lines, and it must run before any module imports:
// Redirect console.log to stderr for MCP stdio servers.
// Do this before any module imports that might call console.log.
const originalLog = console.log.bind(console);
console.log = (...args: unknown[]) => {
process.stderr.write(args.map(String).join(" ") + "\n");
};
console.info = console.log;
// console.error already writes to stderr — no override needed.The originalLog assignment is there so you can call it in tests or debugging scenarios without silently swallowing output. In production, you never use it. The point is that every path through your server that might emit diagnostic output — yours or a transitive dependency's — now lands on stderr instead of corrupting the protocol stream.
Test it explicitly during development: pipe your server's stdout through a JSON parser. If the parser chokes on non-protocol bytes, you have a leak. Catching this before you ship is cheap. Debugging it at 11pm with a poisoned connection is not.
What the full Playbook covers
The stdout story is §1 of the Playbook. The full table of contents:
§1 — Tutorial gap vs production reality. The chasm between the hello-world MCP server and one that survives thirty days of unattended operation. Stdout/stderr discipline, concurrency ordering guarantees, auth failure windows.
§2 — Secrets management and audit trails. How tokens leak. Separate credentials per server, blast-radius containment, credential rotation without process restart, the minimum viable audit log (four fields, written before and after each tool call).
§3 — Error handling and graceful degradation. Transient vs permanent error classification. The mistake that causes the most production pain is retrying permanent errors — a 401 retried ten times with exponential backoff introduces a five-minute delay before surfacing an error that was knowable on the first call. Fallback chains. Circuit breakers per upstream, not per server.
§4 — Concurrency patterns and rate limiting. Running five parallel subagents without melting your API quota or corrupting shared state. Semaphore patterns for outbound I/O. The worktree discipline that turns concurrent write problems into sequential merge problems.
§5 — Production gates and validation. What gets tested before you ship. The pre-release gate checklist — eight items, each one a class of production failure. What breaks a prod deploy if missed.
§6 — Deployment and observability. The deployment story that does not depend on anyone running tail -f in a tmux pane. Structured JSON logs. Health endpoints. Supervisor configuration (systemd, PM2, container restart policies). The difference between "the process is up" and "the server is operational."
§7 — Incident response runbook. What to do when it breaks at 2am. The four-question audit log. How to contain blast radius when a tool call mutates state it should not have.
The Playbook is 7,000 words. It is designed to be read once and referenced repeatedly. Not a narrative — a field guide.
Why lump-sum, no retainers
Most consulting in this space is sold as ongoing access. A retainer, a Slack channel, a standing call. I do not sell that.
The reason is honest: ongoing access means the incentive is to keep you dependent, not to get you to a point where you do not need me. A guide that you read once and apply is more valuable to you than a relationship you maintain indefinitely.
The Playbook is $29 at intro price. You get a PDF and a raw Markdown source file. No DRM. No "member portal." No follow-up drip campaign. If it answers your question, you are done. If it does not, email me and I will tell you where the answer is or tell you straight that it is outside scope.
If you want a faster path — two hours of focused work on your specific server — that is the time-block install track. Same operator, same patterns, applied directly to your codebase. Priced at $500 for two hours. No retainer.
Get it
Buy the MCP Production Playbook — $29 intro →
If you want the time-block track instead: Book a 2-hour install →
If you are not ready to buy and want the next issue when it ships: you are already here. Fill in your email below and I will send it when there is something worth sending.