# FilteredStdioServerTransport Architecture: Log Buffering in DesktopCommanderMCP

> Explore the FilteredStdioServerTransport architecture and its log buffering mechanism. Learn how it captures and processes console output for DesktopCommanderMCP.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: architecture
- Published: 2026-08-01

---

**FilteredStdioServerTransport extends the MCP SDK's StdioServerTransport to intercept console output and raw stdout writes, buffering log messages before initialization and converting them into JSON-RPC notifications once the MCP handshake completes.**

The DesktopCommanderMCP repository implements a custom transport layer to handle the strict JSON-RPC protocol requirements of the Model Context Protocol (MCP). The `FilteredStdioServerTransport` class ensures that debug logging and stdout operations don't corrupt the communication stream by intercepting all output and managing a sophisticated message buffer until the transport is fully initialized.

## Core Architecture Components

### Transport Extension and Method Preservation

At initialization, the constructor captures references to the original `console` methods and `process.stdout.write` (lines 19-27 in [`src/custom-stdio.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/custom-stdio.ts)). This preservation allows the transport to restore native behavior during cleanup while temporarily overriding these globals to intercept output.

The class stores these references in `originalConsole` and `originalStdoutWrite` variables, ensuring that the transport can both intercept and later restore standard I/O behavior.

### Console Redirection Layer

The `setupConsoleRedirection()` method (lines 26-87) overrides `console.log`, `console.info`, `console.warn`, `console.error`, and `console.debug`. Instead of allowing direct stdout writes, these methods now route through `sendLogNotification()` or buffer messages when `isInitialized` remains false.

This redirection ensures that no application logging accidentally emits raw text to stdout, which would violate the MCP protocol's expectation of valid JSON-RPC messages exclusively.

### Stdout Filtering Pipeline

Direct `stdout.write` calls are intercepted via `setupStdoutFiltering()` (lines 89-122). The transport inspects each write to determine if it constitutes valid JSON-RPC traffic. Valid messages pass through unchanged, while non-protocol output is wrapped in notifications or buffered for later delivery.

This filtering mechanism is critical for handling third-party libraries that might write to stdout directly, capturing their output before it can corrupt the protocol stream.

## The Logging Buffer Mechanism

### Pre-Initialization Buffering

Before the MCP handshake completes, the transport cannot safely emit notifications. During this window, intercepted log calls push entries onto an in-memory `messageBuffer` array (declared lines 28-33). Each entry preserves the log level, arguments, and timestamp, ensuring chronological integrity.

The buffer stores objects with the structure `{level, args, timestamp}`, maintaining the exact order of log calls made during the bootstrap phase.

### Flush and Replay Strategy

When the host invokes `enableNotifications()` (lines 60-94), typically after the MCP server initializes, the transport executes a sequential flush process:

1. **Sets the initialization flag** – `isInitialized` becomes `true`, signaling that JSON-RPC communication is established
2. **Sorts buffered entries** – Sorts by captured `timestamp` (line 84) to maintain strict chronological order
3. **Replays notifications** – Calls `sendLogNotification()` for each buffered entry (lines 85-87)
4. **Clears the buffer** – Empties the `messageBuffer` array (line 90) to free memory

This replay mechanism ensures that early bootstrap logs are not lost but are instead delivered as soon as the protocol permits.

### Client-Specific Configuration

The `configureForClient()` method (lines 98-110) detects incompatible clients (such as Cline) and disables notifications accordingly. When disabled, the buffer discards accumulated messages and writes a summary to stderr (lines 66-73), preventing protocol violations for clients that cannot handle log notifications.

This client-aware configuration prevents crashes in MCP clients that expect silent stdout or lack support for the `notifications/message` method.

## Notification Delivery System

### Log Notification Construction

The `sendLogNotification()` method (lines 124-176) constructs proper JSON-RPC notification payloads following the `notifications/message` schema. It includes graceful fallbacks for serialization failures, ensuring that malformed log data never crashes the transport.

The method writes to the original stdout (preserved at initialization) to avoid recursive interception while maintaining protocol compliance.

### Public API Methods

The transport exposes stable helper methods for application-wide use:

- **`sendLog()`** (lines 178-227): General purpose logging with structured data support
- **`sendProgress()`** (lines 229-265): Progress notifications for long-running tasks with numerator/denominator tracking
- **`sendCustomNotification()`** (lines 267-295): Extensible notification channel for application-specific events

These methods provide a type-safe interface for the rest of the DesktopCommanderMCP codebase to emit structured logs and progress updates.

## Resource Cleanup

The `cleanup()` method (lines 297-307) restores the original `console` and `stdout.write` implementations when the server shuts down. This prevents memory leaks and ensures that subsequent process operations return to standard behavior.

Proper cleanup is essential for test environments and hot-reloading scenarios where the transport might be re-instantiated multiple times within the same process.

## Implementation in DesktopCommanderMCP

### Basic Setup

In [`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts), the transport is instantiated and wired into the MCP server:

```typescript
// src/index.ts
import { FilteredStdioServerTransport } from "./custom-stdio.js";

const stdioTransport = new FilteredStdioServerTransport();

// Identify the client (e.g., "Claude-Dev", "Cline", "VSCode")
stdioTransport.configureForClient(process.env.MCP_CLIENT ?? "unknown");

// Start the MCP server (the SDK will call enableNotifications() when ready)
await startMcpServer({ transport: stdioTransport });
stdioTransport.enableNotifications();   // Flushes any buffered logs

```

### Application Logging

Applications use the public API for structured logging:

```typescript
import { stdioTransport } from "./index.js";

// Simple info log
stdioTransport.sendLog("info", "Application started");

// Log with extra structured data
stdioTransport.sendLog("debug", "Cache miss", { key: "user:1234" });

// Progress notification for a long task
stdioTransport.sendProgress("import-users", 42, 100);

```

### Client Compatibility Handling

Disable notifications for clients that cannot handle them:

```typescript
stdioTransport.configureForClient("Cline"); // disables notifications
// All subsequent console.log calls are still captured but silently dropped

```

## Key Source Files

The architecture spans four main files in the wonderwhy-er/DesktopCommanderMCP repository:

- **[`src/custom-stdio.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/custom-stdio.ts)**: Core `FilteredStdioServerTransport` implementation with buffering logic
- **[`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts)**: Transport instantiation and server wiring
- **[`src/utils/logger.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/logger.ts)**: Convenience wrapper forwarding logs to the transport
- **[`src/types.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/types.ts)**: Type exports for module consumption

## Summary

- **FilteredStdioServerTransport** extends `StdioServerTransport` to prevent non-JSON-RPC output from crashing MCP clients
- Original console and stdout methods are captured in the constructor (lines 19-27) for later restoration via `cleanup()`
- The message buffer stores pre-initialization logs as `{level, args, timestamp}` objects (lines 28-33)
- `enableNotifications()` triggers chronological flush of buffered messages once the MCP handshake completes (lines 60-94)
- `configureForClient()` (lines 98-110) disables notifications for incompatible clients like Cline, discarding buffers to prevent protocol violations
- Public methods `sendLog()`, `sendProgress()`, and `sendCustomNotification()` provide structured alternatives to raw console output

## Frequently Asked Questions

### Why does FilteredStdioServerTransport buffer messages instead of sending them immediately?

The MCP protocol requires strict JSON-RPC formatting from the first byte of communication. Before the initialization handshake completes, sending log notifications would violate protocol expectations and potentially crash clients. The buffer preserves log integrity until `enableNotifications()` confirms the transport is ready, at which point messages are replayed in chronological order.

### How does the transport distinguish between valid JSON-RPC and log output?

The `setupStdoutFiltering()` method (lines 89-122) inspects each `stdout.write` call to detect valid JSON-RPC message structures. Valid protocol traffic passes through unchanged, while unstructured output is captured and wrapped in `notifications/message` payloads. This inspection happens at the byte level, ensuring that binary data and JSON-RPC headers are never corrupted by log injection.

### What happens to buffered logs if the client cannot handle notifications?

When `configureForClient()` detects an incompatible client (such as Cline), it disables the notification system. In this mode, `enableNotifications()` discards the `messageBuffer` contents and writes a summary to stderr (lines 66-73), preventing protocol violations while signaling the data loss. The transport continues capturing console output but silently drops it rather than emitting non-compliant JSON-RPC.

### Can applications use standard console.log with this transport?

Yes. The `setupConsoleRedirection()` method (lines 26-87) intercepts all `console.*` methods and routes them through the buffered notification system. Applications can use standard console logging, and the transport automatically converts these calls into compliant JSON-RPC notifications or buffers them as appropriate. This allows existing codebases to integrate without refactoring every log statement.