How Desktop Commander MCP Buffers and Initializes Messages in FilteredStdioServerTransport

Desktop Commander MCP uses a custom FilteredStdioServerTransport class that intercepts console output and process.stdout writes, storing them in a private messageBuffer array until enableNotifications() is called, then replaying them as JSON-RPC notifications to maintain protocol compliance.

The Model Context Protocol (MCP) strictly requires that servers must not write to stdout before receiving the client's first JSON-RPC request. To solve this, the Desktop Commander MCP repository implements a specialized transport layer that wraps the standard SDK transport. This article examines how FilteredStdioServerTransport in src/custom-stdio.ts captures, buffers, and replays messages to ensure robust stdio communication.

Transport Architecture and Construction

The FilteredStdioServerTransport class extends the base MCP transport functionality with stateful buffering capabilities. During instantiation, it preserves original I/O references and prepares internal storage for pre-initialization messages.

Original Stream Preservation

In the constructor (lines 19-27 of src/custom-stdio.ts), the transport saves unmodified references to avoid losing output capabilities:

  • originalConsole – Stores the native console object methods (log, warn, error, debug, info)
  • originalStdoutWrite – Caches the original process.stdout.write implementation

These references allow the transport to intercept output while maintaining the ability to forward JSON-RPC messages transparently.

Message Buffer Structure

The transport initializes a private messageBuffer array (lines 28-33) to hold objects with the following structure:

{
  level: string,      // 'log', 'warn', 'error', 'debug', 'info'
  args: any[],        // Original arguments passed to console method
  timestamp: number   // Unix timestamp for chronological replay
}

This structure ensures that when enableNotifications() is eventually called, messages replay in the exact order they occurred.

Console Output Interception

The transport overrides standard console methods to implement conditional buffering based on the isInitialized state flag.

Conditional Buffering Logic

For each console method (log, warn, error, debug, info), the transport installs a proxy function (lines 27-38) that performs the following check:

if (!this.isInitialized) {
  // Buffer the message for later replay
  this.messageBuffer.push({ level, args, timestamp: Date.now() });
} else {
  // Send immediately as JSON-RPC notification
  this.sendLog(level, args);
}

Key behavior: Any output generated during server startup—before the MCP client completes its handshake—is safely captured rather than written directly to stdout, preventing protocol violations that would crash the connection.

Stdout Write Filtering

Beyond console methods, the transport must also handle raw writes to process.stdout that might originate from third-party libraries or direct stream access.

JSON-RPC Detection and Buffering

The setupStdoutFiltering() method (starting at line 90) intercepts all process.stdout.write calls and distinguishes between protocol messages and log output:

  1. JSON-RPC Detection: Checks if the written string parses as valid JSON-RPC (lines 94-101)
  2. Log Buffering: Non-JSON strings are converted to log notifications and, if uninitialized, added to messageBuffer (lines 102-115)

This dual-path approach ensures that legitimate protocol traffic passes through unmodified while accidental debug output gets queued for later transmission.

Initialization and Message Replay

The transition from buffering to active communication occurs when the server completes its connection handshake.

Enabling Notifications

The enableNotifications() method (lines 60-94) serves as the critical state transition point:

public enableNotifications(): void {
  this.isInitialized = true;
  
  // Send startup confirmation
  this.sendNotification('notifications/initialized', {});
  
  // Replay buffered messages in chronological order
  this.messageBuffer
    .sort((a, b) => a.timestamp - b.timestamp)
    .forEach(entry => {
      this.sendLog(entry.level, entry.args);
    });
    
  // Clear buffer to free memory
  this.messageBuffer = [];
}

Sequence guarantee: Messages replay sorted by timestamp, ensuring the client receives logs in causal order regardless of which console method generated them.

Manual Log Transmission

The sendLog() method (lines 82-99) provides a programmatic interface for application code:

public sendLog(level: string, ...args: any[]): void {
  if (!this.isInitialized) {
    // Queue if called before initialization
    this.messageBuffer.push({ level, args, timestamp: Date.now() });
    return;
  }
  
  // Emit as JSON-RPC log notification
  this.sendNotification('notifications/log', { level, message: args.join(' ') });
}

This method is exposed globally via global.mcpTransport, allowing any module to log safely without checking initialization state.

Client-Specific Configuration

Different MCP clients handle notifications differently. The transport provides configureForClient() (lines 100-110) to adapt behavior before initialization:

  • Cline/VS Code compatibility: Calling configureForClient('cline') disables notification replay for clients that treat stderr/stdout noise as errors
  • Conditional disabling: Sets internal flags that prevent enableNotifications() from replaying buffered content

This configuration must occur before enableNotifications() is called to effectively suppress early output.

Practical Implementation Examples

Instantiating the Transport

In src/index.ts (lines 53-58), the transport is created and exposed globally:

import { FilteredStdioServerTransport } from "./custom-stdio.js";

const transport = new FilteredStdioServerTransport();
global.mcpTransport = transport;  // Global access for application modules

await server.connect(transport);
transport.enableNotifications();  // Triggers buffer replay

Safe Logging from Application Code

Anywhere in the codebase, use the global transport reference:

// This works safely regardless of initialization state
global.mcpTransport?.sendLog("info", "Cache warmed", { size: 42 });

If called before enableNotifications(), the message enters messageBuffer and automatically emits once the transport initializes.

Disabling for Incompatible Clients

For clients that cannot handle notification traffic:

// Call before enableNotifications()
transport.configureForClient("cline");
await server.connect(transport);
// Buffered messages will be discarded, not replayed
transport.enableNotifications();

Summary

  • FilteredStdioServerTransport in src/custom-stdio.ts wraps standard MCP stdio transport to prevent protocol violations during startup.
  • Pre-initialization buffering captures all console output and process.stdout writes in a messageBuffer array with timestamps.
  • State-driven replay occurs when enableNotifications() sets isInitialized = true, emitting buffered messages as JSON-RPC notifications in chronological order.
  • Global access via global.mcpTransport allows safe logging from any module without initialization checks.
  • Client-specific adaptation via configureForClient() prevents replay for clients like Cline that cannot handle notification traffic.

Frequently Asked Questions

What happens if I call sendLog() before the transport initializes?

The message is pushed onto the messageBuffer array with a timestamp and automatically replayed once enableNotifications() is called. No output is lost, and no protocol errors occur because the transport withholds all stdout writes until initialization completes.

How does the transport distinguish between JSON-RPC protocol messages and log output?

The transport intercepts process.stdout.write calls (lines 90-115 in src/custom-stdio.ts) and attempts to parse the written string as JSON. Valid JSON-RPC payloads pass through to the original stdout.write, while plain text strings are wrapped in log notifications and buffered if the transport hasn't initialized yet.

Can I disable message buffering for specific MCP clients?

Yes. Call transport.configureForClient("cline") (or other client identifiers) before enableNotifications(). This sets internal flags that prevent the buffered message replay, which is necessary for clients that treat unexpected stdout traffic as fatal errors rather than JSON-RPC notifications.

Where is the message buffer stored, and what is its memory impact?

The buffer exists as a private messageBuffer array within the FilteredStdioServerTransport instance, storing objects with level, args, and timestamp properties. Memory usage scales with the verbosity of startup logging; the buffer clears automatically after enableNotifications() runs, so transient high-volume logging during initialization won't persist for the application lifetime.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →