# FilteredStdioServerTransport Architecture: How DesktopCommander MCP Buffers Logs

> Discover the FilteredStdioServerTransport architecture. Learn how DesktopCommander MCP buffers logs and sends them as JSON-RPC notifications after the handshake.

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

---

**`FilteredStdioServerTransport` extends the MCP SDK's `StdioServerTransport` to intercept non-JSON stdout output, buffering logs until the MCP handshake completes and then emitting them as structured JSON-RPC notifications.**

The DesktopCommander MCP server relies on this custom transport to prevent crashes in MCP clients that expect strict JSON-RPC protocol adherence. By wrapping raw console output and buffering messages during initialization, the transport ensures no invalid stdout data reaches the client while preserving every log for later delivery.

## The Core Architecture

`FilteredStdioServerTransport` replaces the default behavior of discarding non-JSON console output. Instead, it captures all logging activity and wraps it in proper JSON-RPC `notifications/message` payloads.

According to the wonderwhy-er/DesktopCommanderMCP source code in [`src/custom-stdio.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/custom-stdio.ts), the architecture centers on four primary interceptors:

- **Original method capture** — Stores references to native `console` methods and `process.stdout.write` (lines 19‑27) for later restoration
- **Console redirection** — Overrides `console.log`, `info`, `warn`, `error`, and `debug` via `setupConsoleRedirection()` (lines 26‑87)
- **Stdout filtering** — Intercepts direct `stdout.write` calls through `setupStdoutFiltering()` (lines 89‑122), distinguishing between valid JSON-RPC messages and log output
- **Initialization management** — Uses an `isInitialized` flag and `enableNotifications()` method (lines 60‑94) to control when buffering stops and transmission begins

## How Log Buffering Works

The transport implements a two-phase logging strategy that guarantees message preservation while maintaining protocol compliance.

### Pre-Initialization Buffering

Before the MCP client completes its handshake, the transport sets `isInitialized` to `false`. During this phase:

1. Every intercepted console call pushes an entry onto the `messageBuffer` array (declared at lines 28‑33)
2. Each entry stores `level`, `args`, and `timestamp` to preserve chronological order
3. Direct `stdout.write` calls that contain non-JSON data are similarly buffered rather than emitted

This buffering occurs in `setupConsoleRedirection()` (lines 45‑52, 61‑68, 77‑84) and `setupStdoutFiltering()` (lines 93‑100), ensuring that initialization logs never corrupt the JSON-RPC stream.

### Flushing the Buffer

When the host calls `enableNotifications()` after the MCP handshake completes, the transport executes a flush sequence:

1. Emits an initial "Enhanced FilteredStdioServerTransport initialized" notification
2. Sorts buffered entries by `timestamp` (line 84) to maintain chronological integrity
3. Replays each entry through `sendLogNotification()` (lines 85‑87)
4. Clears the `messageBuffer` array (line 90)

The `sendLogNotification()` method (lines 124‑176) constructs the JSON-RPC payload and writes it to the original stdout, with fallback handling for serialization failures.

### Client-Specific Configuration

The `configureForClient()` method (lines 98‑110) allows selective disabling of notifications for clients that cannot handle them (such as Cline). When notifications are disabled:

- The buffer is discarded rather than flushed
- A summary writes to `stderr` (lines 66‑73)
- Subsequent logs are silently dropped rather than wrapped

## Key Implementation Details

### Message Buffer Structure

The in-memory buffer declared at lines 28‑33 holds objects with the following structure:

```typescript
interface BufferedMessage {
  level: string;
  args: any[];
  timestamp: number;
}

```

This structure preserves the original log level and arguments while maintaining sortable timestamps for ordered replay.

### Public API Methods

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

- **`sendLog(level, message, ...args)`** (lines 178‑227) — Emits structured log notifications with optional metadata
- **`sendProgress(token, processed, total)`** (lines 229‑265) — Sends progress notifications for long-running tasks
- **`sendCustomNotification(method, params)`** (lines 267‑295) — Allows arbitrary JSON-RPC notifications

### Cleanup and Restoration

The `cleanup()` method (lines 297‑307) restores the original `console` methods and `process.stdout.write` when the server shuts down, preventing memory leaks and side effects in long-running processes.

## Usage Examples

### Basic Setup in Entry Point

In [`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts), instantiate the transport and configure for the specific MCP client:

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

const stdioTransport = new FilteredStdioServerTransport();

// Detect client capabilities
stdioTransport.configureForClient(process.env.MCP_CLIENT ?? "unknown");

// Start server - enableNotifications() flushes buffered logs
await startMcpServer({ transport: stdioTransport });
stdioTransport.enableNotifications();

```

### Structured Logging with Metadata

Use the public API anywhere in the application for type-safe log emission:

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

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

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

// Progress tracking
stdioTransport.sendProgress("import-users", 42, 100);

```

### Disabling Notifications for Incompatible Clients

For clients like Cline that cannot handle custom notifications:

```typescript
stdioTransport.configureForClient("Cline");
// Buffered logs discarded, future console calls silently suppressed

```

## Summary

- **FilteredStdioServerTransport** extends the MCP SDK's stdio transport to wrap non-JSON output in valid JSON-RPC notifications
- **Pre-initialization buffering** stores logs in `messageBuffer` until `enableNotifications()` flushes them in timestamp order
- **Client detection** via `configureForClient()` allows graceful degradation for incompatible MCP clients
- **Zero data loss** is guaranteed through the buffer-then-flush pattern while maintaining strict protocol compliance
- **Cleanup methods** restore original stdio handles to prevent process contamination

## Frequently Asked Questions

### What happens to logs if the MCP client doesn't support notifications?

If `configureForClient()` identifies an incompatible client (such as Cline), the transport discards the buffer and writes a summary to `stderr`. All subsequent console output is silently intercepted but not emitted as notifications, preventing client crashes while maintaining a clean stdout stream.

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

In `setupStdoutFiltering()` (lines 89‑122), the transport inspects data written to `stdout.write`. If the content parses as valid JSON-RPC, it passes through unchanged. Non-JSON strings are wrapped in `notifications/message` payloads or buffered if initialization is pending.

### Can I access the buffered messages directly?

No, the `messageBuffer` array is private to the transport instance. Applications must use the public `sendLog()` or `sendLogNotification()` methods to emit messages. The buffer only serves as temporary storage during the pre-initialization phase and clears automatically after flushing.

### Where is the transport implementation located?

The core logic resides in [`src/custom-stdio.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/custom-stdio.ts) within the wonderwhy-er/DesktopCommanderMCP repository. Supporting files include [`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts) for instantiation, [`src/utils/logger.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/logger.ts) for application-level wrappers, and [`src/types.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/types.ts) for TypeScript definitions.