Skip to main content
Glama

Claude Desktop Commander MCP

custom-stdio.ts10.7 kB
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import process from "node:process"; interface LogNotification { jsonrpc: "2.0"; method: "notifications/message"; params: { level: "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug"; logger?: string; data: any; }; } /** * Enhanced StdioServerTransport that wraps console output in valid JSON-RPC structures * instead of filtering them out. This prevents crashes while maintaining debug visibility. */ export class FilteredStdioServerTransport extends StdioServerTransport { private originalConsole: { log: typeof console.log; warn: typeof console.warn; error: typeof console.error; debug: typeof console.debug; info: typeof console.info; }; private originalStdoutWrite: typeof process.stdout.write; private isInitialized: boolean = false; private messageBuffer: Array<{ level: "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug"; args: any[]; timestamp: number; }> = []; constructor() { super(); // Store original methods this.originalConsole = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info, }; this.originalStdoutWrite = process.stdout.write; // Setup console redirection this.setupConsoleRedirection(); // Setup stdout filtering for any other output this.setupStdoutFiltering(); // Note: We defer the initialization notification until enableNotifications() is called // to ensure MCP protocol compliance - notifications must not be sent before initialization } /** * Call this method after MCP initialization is complete to enable JSON-RPC notifications */ public enableNotifications() { this.isInitialized = true; // Send the deferred initialization notification first this.sendLogNotification('info', ['Enhanced FilteredStdioServerTransport initialized']); // Replay all buffered messages in chronological order if (this.messageBuffer.length > 0) { this.sendLogNotification('info', [`Replaying ${this.messageBuffer.length} buffered initialization messages`]); this.messageBuffer .sort((a, b) => a.timestamp - b.timestamp) .forEach(msg => { this.sendLogNotification(msg.level, msg.args); }); // Clear the buffer this.messageBuffer = []; } this.sendLogNotification('info', ['JSON-RPC notifications enabled']); } /** * Check if notifications are enabled */ public get isNotificationsEnabled(): boolean { return this.isInitialized; } /** * Get the current count of buffered messages */ public get bufferedMessageCount(): number { return this.messageBuffer.length; } private setupConsoleRedirection() { console.log = (...args: any[]) => { if (this.isInitialized) { this.sendLogNotification("info", args); } else { // Buffer for later replay to client this.messageBuffer.push({ level: "info", args, timestamp: Date.now() }); } }; console.info = (...args: any[]) => { if (this.isInitialized) { this.sendLogNotification("info", args); } else { this.messageBuffer.push({ level: "info", args, timestamp: Date.now() }); } }; console.warn = (...args: any[]) => { if (this.isInitialized) { this.sendLogNotification("warning", args); } else { this.messageBuffer.push({ level: "warning", args, timestamp: Date.now() }); } }; console.error = (...args: any[]) => { if (this.isInitialized) { this.sendLogNotification("error", args); } else { this.messageBuffer.push({ level: "error", args, timestamp: Date.now() }); } }; console.debug = (...args: any[]) => { if (this.isInitialized) { this.sendLogNotification("debug", args); } else { this.messageBuffer.push({ level: "debug", args, timestamp: Date.now() }); } }; } private setupStdoutFiltering() { process.stdout.write = (buffer: any, encoding?: any, callback?: any): boolean => { // Handle different call signatures if (typeof buffer === 'string') { const trimmed = buffer.trim(); // Check if this looks like a valid JSON-RPC message if (trimmed.startsWith('{') && ( trimmed.includes('"jsonrpc"') || trimmed.includes('"method"') || trimmed.includes('"id"') )) { // This looks like a valid JSON-RPC message, allow it return this.originalStdoutWrite.call(process.stdout, buffer, encoding, callback); } else if (trimmed.length > 0) { // Non-JSON-RPC output, wrap it in a log notification if (this.isInitialized) { this.sendLogNotification("info", [buffer.replace(/\n$/, '')]); } else { // Buffer for later replay to client this.messageBuffer.push({ level: "info", args: [buffer.replace(/\n$/, '')], timestamp: Date.now() }); } if (callback) callback(); return true; } } // For non-string buffers or empty strings, let them through return this.originalStdoutWrite.call(process.stdout, buffer, encoding, callback); }; } private sendLogNotification(level: "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug", args: any[]) { try { // For data, we can send structured data or string according to MCP spec let data: any; if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) { // Single object - send as structured data data = args[0]; } else { // Multiple args or primitives - convert to string data = args.map(arg => { if (typeof arg === 'object') { try { return JSON.stringify(arg, null, 2); } catch { return String(arg); } } return String(arg); }).join(' '); } const notification: LogNotification = { jsonrpc: "2.0", method: "notifications/message", params: { level: level, logger: "desktop-commander", data: data } }; // Send as valid JSON-RPC notification this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n'); } catch (error) { // Fallback to a simple JSON-RPC error notification if JSON serialization fails const fallbackNotification = { jsonrpc: "2.0" as const, method: "notifications/message", params: { level: "error", logger: "desktop-commander", data: `Log serialization failed: ${args.join(' ')}` } }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n'); } } /** * Public method to send log notifications from anywhere in the application */ public sendLog(level: "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug", message: string, data?: any) { try { const notification: LogNotification = { jsonrpc: "2.0", method: "notifications/message", params: { level: level, logger: "desktop-commander", data: data ? { message, ...data } : message } }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n'); } catch (error) { // Fallback to basic JSON-RPC notification const fallbackNotification = { jsonrpc: "2.0" as const, method: "notifications/message", params: { level: "error", logger: "desktop-commander", data: `sendLog failed: ${message}` } }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n'); } } /** * Send a progress notification (useful for long-running operations) */ public sendProgress(token: string, value: number, total?: number) { try { const notification = { jsonrpc: "2.0" as const, method: "notifications/progress", params: { progressToken: token, value: value, ...(total && { total }) } }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n'); } catch (error) { // Fallback to basic JSON-RPC notification for progress const fallbackNotification = { jsonrpc: "2.0" as const, method: "notifications/message", params: { level: "info", logger: "desktop-commander", data: `Progress ${token}: ${value}${total ? `/${total}` : ''}` } }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n'); } } /** * Send a custom notification with any method name */ public sendCustomNotification(method: string, params: any) { try { const notification = { jsonrpc: "2.0" as const, method: method, params: params }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n'); } catch (error) { // Fallback to basic JSON-RPC notification for custom notifications const fallbackNotification = { jsonrpc: "2.0" as const, method: "notifications/message", params: { level: "error", logger: "desktop-commander", data: `Custom notification failed: ${method}: ${JSON.stringify(params)}` } }; this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n'); } } /** * Cleanup method to restore original console methods if needed */ public cleanup() { if (this.originalConsole) { console.log = this.originalConsole.log; console.warn = this.originalConsole.warn; console.error = this.originalConsole.error; console.debug = this.originalConsole.debug; console.info = this.originalConsole.info; } if (this.originalStdoutWrite) { process.stdout.write = this.originalStdoutWrite; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wonderwhy-er/DesktopCommanderMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server