How Configuration Is Managed in Desktop Commander MCP: A Deep Dive into the Singleton ConfigManager
Desktop Commander MCP stores its runtime configuration in a JSON file at ~/.claude-server-commander/config.json, managed by a thread-safe ConfigManager singleton that provides RPC tools, UI editing, and non-blocking persistence.
In the Desktop Commander MCP repository, configuration management is centralized around a single JSON file that serves as the source of truth for all runtime behavior. This article examines how the server loads defaults, persists changes, exposes configuration to clients, and integrates with both programmatic tools and a React-based UI editor.
Where Configuration Lives: File Path and Structure
The configuration file path is hardcoded in src/config.ts through three exported constants:
// src/config.ts
export const USER_HOME = os.homedir();
export const CONFIG_DIR = path.join(USER_HOME, '.claude-server-commander');
export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
This places the configuration in a hidden directory within the user's home folder, following Unix conventions for dotfile storage.
Default Configuration Values
When no config file exists, ConfigManager#getDefaultConfig() in src/config-manager.ts provides these defaults:
blockedCommands– Shell commands the server refuses to execute (e.g.,rm,dd)defaultShell–/bin/sh,/bin/zsh, orpowershell.exeon WindowsallowedDirectories– Optional whitelist for filesystem accesstelemetryEnabled– Boolean for analytics collectionfileWriteLineLimit/fileReadLineLimit– Safety caps on I/O operationsclientId– Anonymous identifier for telemetry
The ConfigManager Singleton: Loading and Initialization
The ConfigManager class in src/config-manager.ts implements a singleton pattern with explicit initialization via ConfigManager.init(). This design prevents race conditions during startup and guarantees exactly one configuration instance exists.
The initialization sequence performs four critical steps:
- Directory creation – Uses
fs.mkdirSyncwithrecursive: trueto ensure~/.claude-server-commanderexists - File reading – Attempts
fs.readFileonCONFIG_FILE; falls back to defaults onENOENTor JSON parse errors - Version stamping – Adds a
VERSIONfield and writes the file if newly created (first-run detection) - Serialization setup – Initializes the
writeChainPromise queue andscheduleSavetimer for background persistence
// src/config-manager.ts (simplified structure)
export class ConfigManager {
private static instance: ConfigManager;
private writeChain: Promise<void> = Promise.resolve();
private saveTimeout: NodeJS.Timeout | null = null;
async init(): Promise<void> {
// Steps 1-4 above
}
static getInstance(): ConfigManager {
if (!this.instance) this.instance = new ConfigManager();
return this.instance;
}
}
RPC Tools for Runtime Configuration Access
The MCP server exposes two tools defined in src/server.ts that allow clients to read and modify configuration without direct filesystem access.
get_config Tool
Returns the complete configuration object. Implemented around line 1329:
// src/server.ts
case "get_config":
return {
content: [{
type: "text",
text: JSON.stringify(configManager.getConfig(), null, 2)
}]
};
set_config_value Tool
Updates a single key and persists to disk. Handles special key __reset__ to restore defaults:
// src/server.ts
case "set_config_value":
const { key, value } = request.params as { key: string; value: unknown };
if (key === '__reset__') {
await configManager.resetConfig();
} else {
await configManager.setValue(key, value);
}
return { content: [{ type: "text", text: "OK" }] };
Thread-Safe Writes: Blocking vs. Non-Blocking Updates
The ConfigManager distinguishes between critical and non-critical configuration changes through two write paths.
setValue (Blocking)
Waits for disk persistence before returning. Suitable for security-critical updates like blockedCommands:
// Guaranteed durable before the RPC response returns
await configManager.setValue('blockedCommands', ['curl', 'wget']);
setValueNonBlocking (Background)
Updates memory immediately and schedules a coalesced disk write. Used for telemetry flags and high-frequency updates:
// Returns instantly; write happens within SCHEDULE_SAVE_DELAY (typically 5s)
await configManager.setValueNonBlocking('telemetryEnabled', false);
The background save mechanism uses a debounced timer (scheduleSave) that resets on each call, collapsing rapid changes into a single disk write.
Feature Flags and Extended Configuration
The configuration directory also caches remote feature flags in feature-flags.json. The FeatureFlagManager in src/utils/feature-flags.ts reads and writes this file using the same CONFIG_DIR path, keeping related state colocated:
~/.claude-server-commander/
├── config.json # Primary user configuration
└── feature-flags.json # Cached remote feature flags
UI Editor Integration
Desktop Commander MCP includes a React-based configuration editor at src/ui/config-editor/src/app.ts. This UI calls the same get_config and set_config_value RPC tools, providing a friendly interface for editing blockedCommands, allowedDirectories, and other settings without manual JSON editing.
The UI editor demonstrates that the configuration management system is protocol-agnostic—any MCP client, whether programmatic or graphical, interacts through identical tool interfaces.
Practical Code Examples
Reading Configuration in a Custom Handler
// src/handlers/example.ts
import { configManager } from '../config-manager.js';
export async function exampleHandler() {
const shell = await configManager.getValue('defaultShell');
const blocked = await configManager.getValue('blockedCommands');
// Block execution if command matches blocked list
if (blocked.includes(proposedCommand)) {
throw new Error(`Command '${proposedCommand}' is blocked by configuration`);
}
}
Adding a Command to the Blocked List
// Via MCP tool call
const current = await callTool('get_config', {});
await callTool('set_config_value', {
key: 'blockedCommands',
value: [...current.blockedCommands, 'curl']
});
Non-Blocking Telemetry Toggle
// Inside server-side code during request handling
await configManager.setValueNonBlocking('telemetryEnabled', false);
// Response continues immediately; disk write happens in background
MCP Server Registration
The configuration system connects to the broader MCP ecosystem through plugin.json and .mcp.json:
plugin.json– Plugin manifest pointing to.mcp.json.mcp.json– RegistersmcpServers.desktop-commanderfor discovery
These files enable Claude Desktop and other MCP clients to locate and launch the server, which then initializes its ConfigManager on startup.
Summary
- Single source of truth:
~/.claude-server-commander/config.jsonstores all runtime configuration. - Safe initialization:
ConfigManager.init()creates directories, applies defaults, and handles first-run detection. - Dual write paths:
setValue()for durability guarantees,setValueNonBlocking()for performance. - Tool-based access:
get_configandset_config_valueRPC tools provide consistent interfaces for all clients. - UI integration: The React config editor uses the same tools as programmatic clients.
- Extensible design: Feature flags and future extensions colocate in the config directory.
Frequently Asked Questions
Where is the Desktop Commander MCP configuration file stored?
The configuration file is stored at ~/.claude-server-commander/config.json (or equivalent on Windows). This path is constructed in src/config.ts using os.homedir() and is consistent across all platforms.
What happens if the configuration file is missing or corrupted?
On first run, or if the file is missing or contains invalid JSON, ConfigManager automatically creates the directory structure and writes a fresh configuration with sensible defaults from getDefaultConfig(). A VERSION field is added to mark the file as initialized.
Can I edit the configuration without using the command line?
Yes. Desktop Commander MCP includes a React-based configuration editor in src/ui/config-editor/src/app.ts that provides a graphical interface for modifying settings. This editor communicates through the same get_config and set_config_value RPC tools used by programmatic clients.
How does the server prevent configuration writes from blocking requests?
For non-critical updates like telemetry flags, the server uses configManager.setValueNonBlocking(), which updates memory immediately and schedules a debounced background write. This prevents the libuv thread pool from starving during heavy tool-call traffic while still ensuring eventual persistence.
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 →