Desktop Commander MCP Error Handling Strategy for Uncaught Exceptions and Unhandled Rejections
Desktop Commander MCP implements a layered, fail‑fast error handling strategy that captures uncaught exceptions and unhandled promise rejections through process‑level listeners, suppresses benign JSON‑parsing errors, records telemetry, and terminates the process for non‑recoverable failures.
The error handling approach in wonderwhy‑er/DesktopCommanderMCP balances operational resilience with strict failure boundaries. Rather than allowing silent failures or zombie processes, the codebase actively monitors for runtime errors and makes deliberate decisions about recovery versus termination. This pattern is particularly important for Model Context Protocol (MCP) servers, where stability directly impacts client experience.
Process-Level Exception and Rejection Listeners
The foundation of Desktop Commander MCP error handling sits in src/index.ts, where the runServer() function registers two critical process event handlers around line 77.
// src/index.ts (lines 77-110)
process.on('uncaughtException', async (error) => { … });
process.on('unhandledRejection', async (reason) => { … });
These listeners intercept:
- Synchronous errors thrown from any execution context
- Asynchronous promise rejections that lack
.catch()handlers
Both handlers follow identical logic paths: classification, logging, telemetry capture, and conditional termination.
Selective Suppression for JSON-Parsing Errors
Not all errors warrant process termination. Desktop Commander MCP specifically exempts malformed JSON payloads from fatal handling.
The detection logic checks error messages for "JSON" and "Unexpected token". When matched:
- The error flows through
logger.errorfor visibility - Telemetry is captured via
capture('run_server_uncaught_exception', …) - The process continues running
This prevents transient configuration issues from crashing long‑lived server instances.
// Pseudocode representation of the selective suppression logic
if (error.message.includes('JSON') && error.message.includes('Unexpected token')) {
logger.error('Non-fatal JSON parse error', { error });
capture('run_server_uncaught_exception', { error: error.message, fatal: false });
return; // Skip process.exit(1)
}
Telemetry and Observability Integration
Every intercepted error triggers structured telemetry collection. The capture utility—imported and invoked within the exception handlers—records:
- Error type and message
- Stack traces when available
- Contextual metadata (timestamp, server state)
// From src/index.ts exception handler
capture('run_server_uncaught_exception', {
error: error.message,
stack: error.stack,
fatal: isFatalError(error)
});
// From src/index.ts rejection handler
capture('run_server_unhandled_rejection', {
reason: String(reason),
fatal: true
});
This data feeds into production analytics pipelines, enabling proactive issue detection without relying solely on log aggregation.
Centralized Logging Architecture
Error messages route through src/utils/logger.ts, which abstracts transport complexity.
The logger.error helper chooses between:
- MCP transport when the
FilteredStdioServerTransportis initialized - Raw JSON‑RPC notifications on
stdoutduring early startup before transport readiness
// src/utils/logger.ts implementation pattern
export function loggerError(level: 'error' | 'warn', message: string, meta?: object) {
if (transportReady) {
transport.send({ jsonrpc: '2.0', method: 'log', params: { level, message, ...meta } });
} else {
logToStderr(level, message); // Fallback JSON‑RPC notification
}
}
This dual‑mode design ensures no error is silently dropped, even during initialization races.
Process Termination Behavior
For errors not classified as benign JSON issues, Desktop Commander MCP enforces strict termination:
- Log the error with full context
- Capture telemetry
process.exit(1)
This fail‑fast philosophy prevents undefined state propagation. An MCP server in an unknown error condition cannot reliably serve client requests; termination allows orchestration layers (systemd, Docker, process managers) to restart a clean instance.
Top-Level Fatal Guard for Initialization Failures
The runServer() function wraps its entire body in a try…catch block. If server initialization itself throws:
- A structured JSON‑RPC error notification emits to
stderr - Telemetry captures the failure context
- Stack traces are preserved and logged
- The process exits immediately
// Pattern from src/index.ts
async function runServer() {
try {
// ... server initialization and main loop
} catch (fatalError) {
logger.error('Fatal initialization error', { stack: fatalError.stack });
capture('run_server_fatal_init', { error: fatalError.message });
process.stderr.write(JSON.stringify({
jsonrpc: '2.0',
error: { code: -32603, message: fatalError.message }
}) + '\n');
process.exit(1);
}
}
Catch-All for Asynchronous Post-Startup Failures
The final safety layer attaches to the promise returned by runServer():
runServer().catch((err) => {
console.error('Unhandled error in runServer:', err);
process.stderr.write(/* JSON‑RPC error */);
capture('run_server_async_fatal', { error: String(err) });
process.exit(1);
});
This captures any rejection escaping the main try…catch, including unanticipated async failures after successful initialization.
Practical Examples
Example 1: Custom Error Handler for Extension Code
When building on top of Desktop Commander MCP, register targeted listeners before the server starts:
import { logger } from './src/utils/logger.js';
process.once('uncaughtException', (err) => {
if (err.message.includes('MySpecialError')) {
logger.error('Gracefully handled extension error', { detail: err });
// Perform cleanup, then controlled exit
await gracefulShutdown();
process.exit(0);
}
});
Example 2: Triggering Handled Rejection Path
Test the unhandled rejection handler directly:
async function simulateFailure() {
Promise.reject(new Error('Simulated unhandled rejection'));
}
simulateFailure();
// Output: "Unhandled rejection: Simulated unhandled rejection" → telemetry → exit 1
Example 3: Pre-Transport Logging
Emit structured logs before the MCP transport initializes:
import { logToStderr } from './src/utils/logger.js';
logToStderr('warning', 'Configuration file not found, using defaults');
Key Source Files
| File | Responsibility |
|---|---|
src/index.ts |
Process-level exception/rejection handlers, fatal guards, telemetry triggers |
src/utils/logger.ts |
Centralized logging with transport fallback |
src/custom-stdio.ts |
FilteredStdioServerTransport implementation for structured output |
src/utils/capture.ts |
Telemetry collection utility (imported by handlers) |
Summary
- Dual process listeners in
src/index.tscatchuncaughtExceptionandunhandledRejectionsynchronously - JSON-parsing errors are suppressed to allow recovery from malformed configuration
- Every error generates telemetry via the
captureutility for production observability - Centralized logging in
src/utils/logger.tsroutes through MCP transport or JSON‑RPC fallback - Non-recoverable errors trigger
process.exit(1)to enforce clean state via external orchestration - Multiple defensive layers (try…catch, promise catch, selective suppression) prevent silent failures
Frequently Asked Questions
What happens when Desktop Commander MCP encounters a malformed JSON payload?
The error handler in src/index.ts detects JSON‑parsing failures by matching "JSON" and "Unexpected token" in the error message. These errors are logged and telemetered but do not terminate the process, allowing the server to continue operating despite configuration issues.
How does Desktop Commander MCP ensure errors are visible during early startup?
The logger.ts utility implements a fallback mechanism. Before the FilteredStdioServerTransport initializes, errors emit as raw JSON‑RPC notifications on stderr. Once the transport is ready, messages flow through the proper MCP channel. This guarantees no error is lost to initialization timing.
Can Desktop Commander MCP recover from unhandled promise rejections?
No—the current implementation treats unhandled rejections as fatal. The unhandledRejection listener captures telemetry, logs the rejection reason, and calls process.exit(1). This aligns with Node.js best practices: unhandled rejections indicate programming errors that invalidate process state.
Where is telemetry for errors actually sent?
The capture function invoked in exception handlers originates from src/utils/capture.ts (imported into src/index.ts). While the exact destination depends on build configuration, the integration pattern suggests external analytics services. The telemetry includes error messages, stack traces, and fatality classification.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →