# How DesktopCommanderMCP Persists Configuration Settings Between Server Restarts

> DesktopCommanderMCP persists configuration settings using a JSON file and a ConfigManager. Learn how changes are saved to disk to survive server restarts.

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

---

**DesktopCommanderMCP persists settings by storing them in a JSON file on disk that is loaded into memory by a singleton `ConfigManager` on startup, with all changes synchronized back to disk through a serialized write queue or coalesced background writes.**

The DesktopCommanderMCP server, an open-source Model Context Protocol (MCP) implementation, maintains stateful configuration across process lifecycles using a file-based persistence layer. Understanding how this **configuration system persists settings between server restarts** is essential for developers integrating custom tools, managing containerized deployments, or ensuring consistent behavior across system reboots.

## Configuration File Location and Structure

The system stores all user-editable settings in a file named [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json). The exact path is determined by the `CONFIG_FILE` export in [`src/config.js`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.js), which resolves to a location in the user's configuration directory. Because the file resides on the local filesystem rather than in ephemeral memory, settings survive process termination, container restarts, and system reboots.

The JSON structure is human-readable, written with `JSON.stringify(..., null, 2)` formatting to preserve indentation and support manual editing when necessary.

## The ConfigManager Singleton Pattern

### Initialization and First-Run Handling

When the server launches, the `ConfigManager` singleton executes its `init()` method in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) (lines 70-92). This routine checks for the existence of [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json). If the file exists, it parses the JSON into `this.config`; otherwise, it generates a default configuration via `getDefaultConfig()`, marks the session as *first-run*, and immediately writes the defaults to disk.

This initialization sequence ensures that every server restart begins with a valid, fully-populated configuration object held in memory, regardless of whether the persisted file existed previously.

### In-Memory State Management

After initialization, all configuration reads serve from the in-memory `this.config` object. This guarantees that every tool and plugin sees a consistent view of settings without repeated disk I/O, while the persistence layer handles background synchronization to maintain durability.

## Persistence Mechanisms and Write Safety

### Synchronous Updates for Critical Changes

User-driven configuration changes flow through `setValue(key, value)`, implemented in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) (lines 41-50). This method updates `this.config[key]` and immediately invokes `saveConfig()`. 

To prevent race conditions during concurrent writes, the implementation serializes all disk operations through a `writeChain` promise queue. Each write uses `JSON.stringify(..., null, 2)` for readable formatting, and the promise returned by `setValue()` does not resolve until the data is safely persisted to [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json).

```typescript
// Blocking write: ensures data is on disk before continuing
await configManager.setValue('telemetryEnabled', false);
console.log('Setting persisted safely');

```

### Non-Blocking Updates for High-Frequency Operations

Background processes and high-frequency updates use `setValueNonBlocking()` (lines 82-90), which delegates to `scheduleSave()`. This method queues a *coalesced* write via a debounce mechanism, ensuring that many rapid updates collapse into a single disk operation. This pattern protects the libuv thread pool from I/O congestion while still guaranteeing eventual persistence.

```typescript
// Non-blocking: returns immediately, writes coalesced in background
await configManager.setValueNonBlocking('lastSeen', Date.now());
await configManager.setValueNonBlocking('sessionCount', 42);
// Both values written to disk in a single operation

```

## Special Persistence Cases

### Stable Client ID Generation

The `getOrCreateClientId()` method (lines 15-24) ensures analytics identifiers survive restarts. It checks the `clientId` field in the configuration; if absent, it generates a UUID, stores it via `setValue('clientId', ...)`, and returns the identifier. This creates a stable fingerprint for telemetry and A/B testing across server lifecycles.

```typescript
const clientId = await configManager.getOrCreateClientId();
console.log('Persistent client ID:', clientId);

```

### Version Stamping and Migration

After loading the configuration, the manager injects the current library `VERSION` into the in-memory object. This version stamp is written to [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) on the next save, allowing the system to detect outdated configuration formats and trigger migration logic on subsequent restarts.

## Bulk Configuration Operations

For atomic configuration changes, `resetConfig()` and `updateConfig()` replace the entire in-memory `this.config` object and immediately invoke `saveConfig()`. These operations ensure that bulk updates—such as restoring defaults or importing new settings—are persisted atomically without partial write states.

```typescript
// Reset to factory defaults and persist immediately
await configManager.resetConfig();

```

## Summary

- DesktopCommanderMCP uses a JSON file ([`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json)) located via `CONFIG_FILE` in [`src/config.js`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.js) to store settings durably.
- The `ConfigManager` singleton loads this file during `init()`, defaulting to `getDefaultConfig()` if missing, and maintains state in memory for performance.
- **Synchronous writes** via `setValue()` use a serialized `writeChain` to prevent race conditions and guarantee durability before promise resolution.
- **Non-blocking writes** via `setValueNonBlocking()` and `scheduleSave()` coalesce rapid updates into single disk operations to protect system resources.
- Special fields like `clientId` and `VERSION` are automatically managed to ensure stable identifiers and version tracking across restarts.

## Frequently Asked Questions

### Where does DesktopCommanderMCP store its configuration file?

The server stores settings in [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json), with the path defined by the `CONFIG_FILE` constant in [`src/config.js`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.js). This typically resolves to a user-specific configuration directory, ensuring that settings persist across system reboots and container restarts without requiring environment variables or external databases.

### How does the server prevent configuration corruption during concurrent writes?

All write operations serialize through a `writeChain` promise queue in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts). When `setValue()` is called, the update joins this chain, ensuring that even if multiple tools attempt simultaneous updates, each write completes before the next begins. The file is written atomically using `JSON.stringify`, preventing partial JSON corruption.

### What is the difference between `setValue()` and `setValueNonBlocking()`?

`setValue()` performs a **synchronous, blocking write** that returns only after the configuration is saved to disk, making it suitable for critical user preferences. `setValueNonBlocking()` queues the change via `scheduleSave()` for a **coalesced background write**, allowing high-frequency updates (like usage statistics) to batch into a single I/O operation rather than hammering the disk.

### How does the server maintain a stable client ID across restarts?

The `getOrCreateClientId()` method in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) checks for an existing `clientId` field in the configuration. If none exists, it generates a UUID, persists it immediately via `setValue()`, and returns the value. This ensures analytics and telemetry systems can track the same installation across multiple server lifecycles.