Logging: A Huge MCP Footgun
There's a really easy foot gun that you can trigger when you're using the standard io transport with an MCP server.
Here it is: console logs don't work.
Why console.log
Doesn't Work
An MCP server is really just a standard Node application. The client runs the MCP server, and monitors the stdout
of the server for any MCP messages. It then sends back MCP messages to the server's stdin
.
However, Node apps use stdio for another purpose: logging. When you log to the console, you're writing to the same stream that the MCP server is using to communicate with the client.
This means that if you log to the console in your server, you're going to be sending messages to the client that aren't MCP-formatted. Depending on the client, this could cause it to crash. And at the very least, your logs will be swallowed by the program consuming them.
Why Doesn't sse
Have This Problem?
The sse
transport doesn't have this problem because it doesn't use stdio
for transporting messages:
This means that it's free to use stdout
for logging.
It's perfectly possible to use sse
locally, so you may prefer using that method if accessing the console is important to you.
How to Log
My preferred solution is to create a custom logger that writes to a local file - mcp-server.log
. Here's my implementation:
// 1. appendFileSyncimport { appendFileSync } from "fs";import { join } from "path";// 2. LOG_FILEconst LOG_FILE = join(import.meta.dirname,"mcp-server.log",);// 3. formatMessagefunction formatMessage(level: string,message: string,data?: unknown,): string {const timestamp = new Date().toISOString();const dataStr = data? `\n${JSON.stringify(data, null, 2)}`: "";return `[${timestamp}] [${level}] ${message}${dataStr}\n`;}// 4. loggerexport const logger = {log(message: string, data?: unknown) {const logMessage = formatMessage("INFO",message,data,);appendFileSync(LOG_FILE, logMessage);},// ...};
- We use
appendFileSync
to write to a file synchronously. - We define the path to the log file.
- We define a function to format the message.
- We export a logger object with a
log
method that writes to the log file. You can export other methods likeerror
,warn
, etc.
This way, you can log to a file without interfering with the MCP protocol.
A Possible Alternative: Notifications
A reader alerted me to the fact that MCP does have a concept of 'notifications', albeit somewhat buried in the documentation.
In theory, this would allow the server to send a message to the client.
However, the implementation of this would be up to the client - so whether you could use this mechanism for logging is an open question.
If anyone has use notifications for logging, I'd love to hear about it in the Discord.
Conclusion
You've got two choices when you're considering logging in an MCP server:
- Use a transport that doesn't interfere with
stdout
, likesse
. - Write to a
.log
file.