Build a local TypeScript server
Log without breaking stdio
Keep stdout reserved for protocol messages and send safe diagnostics to stderr.
On a stdio server, one console.log() can take the whole thing down. Let’s see why, and what to use instead.
A stdio client launches our server as a child process. It writes protocol messages to the server’s stdin and reads protocol messages from its stdout. Nothing else is supposed to travel on those two streams.
That makes stdout part of the wire contract. If we print Server started! there, the client tries to parse it as a protocol message, fails, and usually drops the connection. From the outside it looks like the server is broken. It’s not. It just talked on the wrong channel.
Use stderr for diagnostics
stderr is free. Use console.error() for anything you want to see while developing:
console.error(JSON.stringify({
event: 'server_start',
server: 'project-notes'
}))
I log JSON objects rather than sentences. They’re easier to search and to filter later.
Don’t rely on anyone reading these messages. A client may display stderr, capture it to a file, or throw it away. Treat it as a diagnostic channel, not as a way to talk to the user.
Log the event, not the data
Be deliberate about what goes into a log line. Never log note bodies, authorization headers, tokens, or complete tool arguments. Record the event name, safe identifiers such as a note ID, and an incident ID when something failed. That’s enough to debug and too little to leak.
Watch your dependencies
Your own code can be clean and the stream still gets corrupted. A library that prints a startup banner or a deprecation notice to stdout breaks stdio just the same. When you add a dependency to a stdio server, check what it prints.
Test both channels
You can split the two streams into files and look at each one:
npm run dev 1>protocol.log 2>diagnostics.log
The 1> sends stdout to protocol.log, the 2> sends stderr to diagnostics.log. With a real client connected, protocol.log must contain only protocol messages. diagnostics.log may contain our server_start event and other safe operational lines.
If you see a plain sentence in protocol.log, you’ve found the bug. Search the project for console.log before testing the stdio server:
grep -rn "console.log" src
There should be none in the server’s execution path. If you want a console.log for a quick experiment, switch it to console.error and it stops being dangerous.
Lesson completed