How the DesktopCommanderMCP Configuration Manager Persists Settings Between Server Restarts
The DesktopCommanderMCP server persists settings between restarts by maintaining a singleton ConfigManager that reads from and writes to a JSON file on disk, using synchronous writes for immediate persistence and queued non-blocking writes for high-frequency updates.
The DesktopCommanderMCP MCP server relies on a robust configuration persistence mechanism to maintain user settings across process terminations, container restarts, and system reboots. At the heart of this system is the ConfigManager singleton defined in src/config-manager.ts, which manages a JSON-based configuration file that serves as the single source of truth for all server settings. Understanding how this configuration manager persists settings between server restarts is essential for developers building plugins or debugging stateful behavior in the wonderwhy-er/DesktopCommanderMCP repository.
Configuration File Initialization and Location
The persistence layer targets a specific JSON file determined by the CONFIG_FILE constant exported from src/config.js. When the server launches, the ConfigManager.init() routine checks this location for an existing config.json.
According to lines 70-92 in src/config-manager.ts, the initialization logic follows this path:
- If
config.jsonexists, the manager parses the JSON intothis.config. - If the file is missing, the system generates a default configuration via
getDefaultConfig(), marks the session as a first-run, and immediately persists these defaults to disk.
This initialization pattern guarantees that every server instance starts with a valid, readable configuration state, ensuring that settings persist between server restarts from the very first execution.
Version Stamping on Load
After loading or creating the configuration, the manager injects the current library VERSION into the in-memory object. As implemented in lines 92-94 of src/config-manager.ts, this version stamping ensures that the persisted file always reflects the running code version, which aids in migration debugging and compatibility checking across restarts.
Synchronous Updates for Critical Persistence
When tools require immediate persistence to survive unexpected crashes, the code path uses setValue(). This method updates this.config[key] in memory and then calls saveConfig(), which serializes the data through an internal writeChain mechanism to prevent race conditions.
The implementation in lines 41-50 of src/config-manager.ts handles this process:
- Updates the in-memory
this.configobject. - Chains the write operation via
writeChainto ensure sequential disk access. - Writes the file using
JSON.stringify(this.config, null, 2)to maintain human-readable formatting with two-space indentation.
Because the promise returned by setValue() does not resolve until the file is flushed to disk, this method provides guaranteed persistence for critical settings between server restarts.
Non-Blocking Updates for High-Frequency Operations
Background processes that generate rapid configuration changes—such as usage statistics or telemetry timestamps—use setValueNonBlocking(). Found in lines 82-90 of src/config-manager.ts, this method queues a coalesced write via scheduleSave().
This architectural choice ensures that multiple rapid updates collapse into a single disk write, protecting the libuv thread pool from I/O congestion while still guaranteeing eventual consistency. The configuration manager persists these settings to disk without blocking the main execution thread, making it ideal for high-frequency data that does not require immediate crash survivability.
Stable Client Identification Across Restarts
The system generates a persistent UUID for analytics and A/B testing through getOrCreateClientId(). As implemented in lines 15-24 of src/config-manager.ts, this method:
- Checks for an existing
clientIdfield in the configuration. - Generates a new UUID if absent.
- Stores the identifier via
setValue()to ensure it persists toconfig.json.
Because this value is written to disk immediately upon creation, the same client ID survives across server restarts, system reboots, and container recreations, providing stable telemetry identification.
Bulk Configuration Operations
For complete configuration resets or bulk updates, the manager provides resetConfig() and updateConfig(). Both methods replace the in-memory configuration object and immediately trigger saveConfig() to synchronize the disk state, as shown in lines 92-100 of src/config-manager.ts.
These operations are atomic from the caller's perspective—the file on disk always matches the in-memory state once the method resolves, ensuring that bulk changes to how the configuration manager persists settings between server restarts remain consistent.
Practical Implementation Examples
The following examples demonstrate how to interact with the persistent configuration system in wonderwhy-er/DesktopCommanderMCP.
Retrieve the current configuration from disk:
import { configManager } from './dist/config-manager.js';
async function showConfig() {
const cfg = await configManager.getConfig();
console.log('Current config:', cfg);
}
showConfig();
Update a critical setting with immediate persistence:
await configManager.setValue('telemetryEnabled', false);
// The change is written to config.json before the promise resolves.
Queue a non-blocking update for high-frequency data:
await configManager.setValueNonBlocking('lastSeen', Date.now());
// Returns immediately; the write is queued in the background.
Ensure a stable client ID exists for analytics:
const clientId = await configManager.getOrCreateClientId();
console.log('Client ID for analytics:', clientId);
Summary
- The
ConfigManagersingleton insrc/config-manager.tsmaintains an in-memory copy of settings loaded fromconfig.jsonat startup. - Synchronous writes via
setValue()use a chainedwriteChainmechanism to prevent race conditions and guarantee immediate persistence to disk. - Non-blocking writes via
setValueNonBlocking()coalesce multiple updates into a single disk operation to protect system resources while ensuring eventual consistency. - Version stamping and client ID generation (lines 15-24 and 92-94) ensure the persisted state remains consistent and identifiable across server restarts.
- All configuration changes ultimately serialize to the JSON file defined by
CONFIG_FILEinsrc/config.js, ensuring settings survive process termination, container restarts, and system reboots.
Frequently Asked Questions
Where is the configuration file stored in DesktopCommanderMCP?
The configuration file location is determined by the CONFIG_FILE constant exported from src/config.js. This path points to a config.json file in the user's configuration directory, ensuring that the configuration manager persists settings between server restarts by writing to a stable location outside temporary process memory.
How does the configuration manager handle concurrent write operations?
The manager prevents race conditions by serializing write operations through an internal writeChain promise queue. When saveConfig() is called, it chains the new write operation to the previous one, ensuring that rapid successive calls to setValue() result in sequential—not parallel—disk access, preventing file corruption.
What happens if the config.json file is corrupted or deleted?
If config.json is missing during initialization, the ConfigManager.init() routine automatically generates a default configuration using getDefaultConfig() and writes it to disk. While the provided source analysis does not explicitly detail corrupted JSON parsing error handling, the initialization logic assumes either valid JSON or a first-run scenario where defaults are applied, ensuring the server can always start successfully.
Can I use non-blocking updates for critical configuration changes?
No. According to the source code in src/config-manager.ts, you should use setValue() for critical changes that must survive a crash, as it awaits the write completion before resolving. Reserve setValueNonBlocking() for high-frequency, background-only data like timestamps or usage counters where eventual consistency is acceptable and immediate persistence between server restarts is not required.
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 →