# How the DesktopCommanderMCP Configuration Manager Saves and Loads Settings Between Server Restarts

> Learn how the DesktopCommanderMCP configuration manager saves and loads settings between server restarts by atomically writing JSON to config.json, ensuring data integrity.

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

---

**The DesktopCommanderMCP configuration manager persists settings by atomically writing JSON to `~/.claude-server-commander/config.json`, using a serialized write chain to prevent corruption while providing both blocking and non-blocking update APIs.**

The configuration manager in wonderwhy-er/DesktopCommanderMCP ensures that user preferences, telemetry flags, and onboarding state survive process restarts through a robust file-based persistence layer. Implemented as a singleton in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts), it coordinates all disk I/O through a centralized queue that guarantees data integrity even during rapid successive updates. Understanding its dual-path persistence strategy—synchronous for critical changes and deferred for high-frequency metrics—enables reliable integration of custom configuration options.

## Configuration File Location and Structure

All persistent data resides in a single JSON file defined in [`src/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts). The path `CONFIG_FILE` resolves to `~/.claude-server-commander/config.json` using the user’s home directory as the base. This location remains constant across server restarts, ensuring the configuration manager always reads from and writes to the same file.

## Initialization and Loading on Server Startup

When the server boots, the configuration manager executes the `init()` method (lines 69-102 in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts)). This routine performs three critical tasks:

- **Directory creation**: Creates `~/.claude-server-commander/` if it does not exist
- **File loading**: Parses existing [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) or falls back to `getDefaultConfig()` if absent
- **First-run detection**: Sets the internal `_isFirstRun` flag (lines 53-55) to `true` only when the file must be created, enabling onboarding UI flows

The `loadConfig()` helper reads the file synchronously during initialization, populating an in-memory configuration object that serves as the working state for the server’s lifetime.

## Updating Configuration Values

The manager exposes two distinct persistence paths to accommodate different latency requirements.

### Blocking Updates with setValue()

The `setValue(key, value)` method (lines 52-82) updates the in-memory configuration immediately, normalizes telemetry flags, and awaits `saveConfig()` before returning. This guarantees that the file system reflects the change before the caller continues execution, making it ideal for critical settings like shell paths or feature toggles.

```typescript
await configManager.setValue('defaultShell', '/usr/bin/zsh');
// File is guaranteed written when the promise resolves

```

### Non-Blocking Updates with setValueNonBlocking()

For high-frequency updates—such as usage statistics or session metrics—the `setValueNonBlocking(key, value)` method (lines 85-98) updates memory state instantly but defers disk I/O via `scheduleSave()`. This prevents thread-pool saturation and keeps response times low while still ensuring eventual consistency.

```typescript
await configManager.setValueNonBlocking('telemetryEnabled', false);
// Returns immediately; write happens in background

```

## Write Serialization and Conflict Prevention

To prevent concurrent writes from corrupting the JSON file, the manager maintains a `writeChain` promise queue. Every call to `saveConfig()` appends its write operation to this chain, ensuring each file operation completes before the next begins. Additionally, `scheduleSave()` (lines 72-81) coalesces rapid successive updates into a single disk write, resetting the `saveScheduled` sentinel only after the pending operation finishes.

## Reading Configuration Values

Accessors guarantee that `init()` has completed before returning data:

- **`getConfig()`** (lines 36-40): Returns a shallow copy of the entire configuration object
- **`getValue(key)`** (lines 44-48): Retrieves a single property value
- **`getOrCreateClientId()`** (lines 27-35): Lazily generates a UUID, persists it via `setValue()`, and returns it for analytics or A/B testing

```typescript
const cfg = await configManager.getConfig();      // Full object
const shell = await configManager.getValue('defaultShell'); // Single value
const clientId = await configManager.getOrCreateClientId(); // Persistent UUID

```

## Practical Code Examples

**Resetting to factory defaults:**

```typescript
await configManager.resetConfig();   // Overwrites config.json with defaults from getDefaultConfig()

```

**Checking first-run status:**

```typescript
if (configManager.isFirstRun()) {
  console.log('Showing onboarding flow...');
}

```

## Summary

- **Persistent storage**: Settings survive restarts via JSON at `~/.claude-server-commander/config.json`
- **Initialization**: `init()` loads existing config or creates defaults on server startup, tracking first-run state
- **Update patterns**: `setValue()` for immediate durability; `setValueNonBlocking()` for background persistence
- **Data integrity**: `writeChain` serializes file access to prevent corruption during concurrent updates
- **Safe reads**: `getConfig()` and `getValue()` provide read-only views after initialization completes

## Frequently Asked Questions

### Where is the configuration file stored on disk?

The configuration manager writes to `~/.claude-server-commander/config.json`, as defined in [`src/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts). This path is resolved relative to the user’s home directory, ensuring consistent access across server restarts regardless of the working directory from which the server is launched.

### What is the difference between setValue and setValueNonBlocking?

`setValue()` performs a synchronous file write and awaits completion before returning, guaranteeing durability for critical settings. `setValueNonBlocking()` updates memory immediately but queues the disk write via `scheduleSave()`, making it suitable for high-frequency updates like telemetry counters that would otherwise saturate the libuv thread pool.

### How does the configuration manager prevent file corruption during concurrent writes?

The manager implements a `writeChain` promise queue in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts). Each save operation appends to this chain, ensuring that only one write executes at a time. This serialized access pattern prevents race conditions where overlapping writes could produce malformed JSON.

### How can I detect if the server is running for the first time?

After initialization, check the `isFirstRun()` method (backed by the internal `_isFirstRun` flag set during `init()`). This flag is set to `true` only when the configuration file did not exist and had to be created, allowing the server to trigger onboarding flows or initial setup routines.