How the DesktopCommanderMCP Configuration Manager Saves and Loads Settings Between Server Restarts
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, 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. 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). This routine performs three critical tasks:
- Directory creation: Creates
~/.claude-server-commander/if it does not exist - File loading: Parses existing
config.jsonor falls back togetDefaultConfig()if absent - First-run detection: Sets the internal
_isFirstRunflag (lines 53-55) totrueonly 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.
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.
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 objectgetValue(key)(lines 44-48): Retrieves a single property valuegetOrCreateClientId()(lines 27-35): Lazily generates a UUID, persists it viasetValue(), and returns it for analytics or A/B testing
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:
await configManager.resetConfig(); // Overwrites config.json with defaults from getDefaultConfig()
Checking first-run status:
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:
writeChainserializes file access to prevent corruption during concurrent updates - Safe reads:
getConfig()andgetValue()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. 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. 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.
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 →