# Handling JSON Errors and Corruption in MCP Configuration Persistence: How DesktopCommanderMCP Implements Bulletproof Config Storage

> Learn how DesktopCommanderMCP prevents JSON errors and corruption in config persistence using try-catch blocks and a promise chain for bulletproof storage. Avoid configuration crashes.

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

---

**DesktopCommanderMCP prevents configuration crashes by wrapping `JSON.parse` in try-catch blocks that fall back to default values on corruption, while serializing all disk writes through a single promise chain to eliminate race conditions.**

DesktopCommanderMCP stores its runtime settings in a JSON file that must survive crashes, power failures, and concurrent modifications. The `ConfigManager` class in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) implements a defensive persistence strategy that handles JSON errors and corruption in MCP configuration persistence through graceful recovery mechanisms and atomic write operations.

## Detecting and Recovering from Corrupted Configuration Files

The `ConfigManager` class treats every configuration load as a potentially unsafe operation. Rather than assuming the JSON file is valid, it implements multiple layers of error detection that ensure the application never enters an undefined state.

### Graceful Initialization with Automatic Fallback

When the configuration manager initializes via the `init()` method, it attempts to read and parse the existing [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) file. If `JSON.parse` throws due to truncation, syntax errors, or incomplete writes, the system immediately falls back to a fresh in-memory default configuration.

```typescript
try {
  await fs.access(this.configPath);
  const configData = await fs.readFile(this.configPath, 'utf8');
  this.config = JSON.parse(configData);          // ← may throw
  this._isFirstRun = false;
} catch (error) {
  // Config file missing or corrupted → create fresh defaults
  this.config = this.getDefaultConfig();         // ← default config
  this._isFirstRun = true;
  await this.saveConfig();
}

```

*See the implementation in* [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) *[lines 73‑88](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts#L73-L88).*

This pattern ensures that even if the physical file contains malformed JSON or is missing entirely, the application continues running with sensible defaults. The `_isFirstRun` flag allows the system to detect fresh installations versus recoveries from corruption.

### Comprehensive Error Logging and Safe State Guarantees

Beyond parsing errors, the initialization logic includes a secondary catch block that handles unexpected filesystem or permission errors. In all failure scenarios, the manager logs the specific error and guarantees that `this.config` contains a valid object before proceeding.

```typescript
} catch (error) {
  console.error('Failed to initialize config:', error);
  this.config = this.getDefaultConfig();   // ← safe fallback
}

```

*See the implementation in* [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) *[lines 94‑99](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts#L94-L99).*

## Preventing Corruption During Write Operations

Reading safely is only half the solution. DesktopCommanderMCP also prevents JSON corruption during writes by eliminating race conditions and batching high-frequency updates.

### Serialized Write Queues

All configuration writes flow through a **single promise chain** (`writeChain`) managed by the `saveConfig()` method. This architecture ensures that concurrent calls to `setValue()` cannot interleave their `fs.writeFile` operations, which would otherwise result in partial writes or corrupted JSON structures.

```typescript
private async saveConfig(): Promise<void> {
  const write = this.writeChain.then(() => this.writeConfigToDisk());
  this.writeChain = write.catch(() => {});   // keep chain alive
  return write;
}

```

*See the implementation in* [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) *[lines 94‑99](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts#L94-L99).*

By chaining writes sequentially, the system guarantees that only one write operation touches the disk at a time, even when multiple configuration changes occur simultaneously.

### Coalesced Background Persistence

For high-frequency, non-critical updates such as usage statistics or telemetry toggles, the `scheduleSave()` method batches pending writes into a single background operation. This reduces disk I/O and minimizes the window where corruption could occur during unexpected shutdowns.

```typescript
scheduleSave(): void {
  if (this.saveScheduled) return;
  this.saveScheduled = true;
  this.writeChain = this.writeChain.then(async () => {
    this.saveScheduled = false;
    await this.writeConfigToDisk();
  });
}

```

*See the implementation in* [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) *[lines 109‑119](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts#L109-L119).*

## Working with the ConfigManager API

The `ConfigManager` exposes a clean API that abstracts these resilience mechanisms from the rest of the application. Tools throughout the codebase interact with configuration values without worrying about file corruption or serialization logic.

### Loading Configuration Values Safely

Components retrieve settings through the `getValue()` method, which always returns a valid value or `undefined` rather than throwing:

```typescript
// Example: a tool that needs the current configuration
import { configManager } from '../config-manager.js';

async function getShell() {
  const shell = await configManager.getValue('defaultShell');
  return shell ?? '/bin/sh';
}

```

### Non-Blocking Updates for UI Responsiveness

When updating values that don't require immediate durability guarantees, use `setValueNonBlocking()` to persist changes in the background without blocking the caller:

```typescript
import { configManager } from '../config-manager.js';

async function disableTelemetry() {
  await configManager.setValueNonBlocking('telemetryEnabled', false);
  // The change is persisted in the background; the UI can continue immediately.
}

```

### Manual Reset to Defaults

Users can trigger a complete reset to factory defaults when they suspect configuration corruption:

```typescript
import { configManager } from '../config-manager.js';

async function resetConfig() {
  const freshConfig = await configManager.resetConfig();
  console.log('Config reset to defaults:', freshConfig);
}

```

## Supporting Configuration Architecture

The persistence layer relies on several supporting components:

- **[`src/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts)** – Defines the `CONFIG_FILE` constant that specifies the JSON file location used by `ConfigManager`.
- **[`src/utils/capture.js`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/capture.js)** – Handles telemetry events, including opt-out notifications that trigger non-blocking configuration updates.
- **[`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts)** – Demonstrates JSON fetch-and-cache logic for remote feature-flag payloads, mirroring the same error-tolerant patterns used for local configuration.

## Summary

DesktopCommanderMCP implements a multi-layered defense against JSON errors and corruption in MCP configuration persistence:

- **Graceful initialization** detects malformed JSON during startup and falls back to default configurations rather than crashing.
- **Comprehensive error handling** ensures the application never operates with an undefined configuration state.
- **Serialized write chains** prevent race conditions by guaranteeing only one write operation executes at a time.
- **Coalesced background saves** batch high-frequency updates to reduce disk wear and corruption risk.

Together, these mechanisms ensure that the [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) file can be recovered from corruption, protected from concurrent access bugs, and maintained reliably across application sessions.

## Frequently Asked Questions

### What happens if the config.json file is corrupted during a power outage?

DesktopCommanderMCP detects the corruption during the next startup when `JSON.parse` throws an error. The `ConfigManager` catches this exception, logs the error, and initializes the application with a fresh default configuration from `getDefaultConfig()`. The system then writes these clean defaults back to disk, effectively resetting the file to a valid state.

### How does DesktopCommanderMCP prevent concurrent writes from corrupting the JSON file?

All write operations flow through a single promise chain (`writeChain`) in the `saveConfig()` method. This serialization ensures that even if multiple `setValue()` calls occur simultaneously, they execute sequentially rather than interleaving their `fs.writeFile` operations, which would otherwise result in partial JSON structures.

### Can users manually reset their configuration if they suspect data corruption?

Yes. The `ConfigManager` exposes a `resetConfig()` method that immediately replaces the in-memory configuration with defaults and persists them to disk. This provides a programmatic way to recover from corruption without manually deleting files or editing JSON.

### Where does DesktopCommanderMCP store its configuration file?

The configuration file location is defined in [`src/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts) as the `CONFIG_FILE` constant, which `ConfigManager` references to determine where to read and write the [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) file on the local filesystem.