# How Configuration Is Managed in Desktop Commander MCP: A Deep Dive into the Singleton ConfigManager

> Discover how Desktop Commander MCP manages its configuration using the Singleton ConfigManager. Learn about JSON storage, RPC tools, UI editing, and non-blocking persistence.

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

---

**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](https://github.com/wonderwhy-er/DesktopCommanderMCP) 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts) through three exported constants:

```typescript
// 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) provides these defaults:

- **`blockedCommands`** – Shell commands the server refuses to execute (e.g., `rm`, `dd`)
- **`defaultShell`** – `/bin/sh`, `/bin/zsh`, or `powershell.exe` on Windows
- **`allowedDirectories`** – Optional whitelist for filesystem access
- **`telemetryEnabled`** – Boolean for analytics collection
- **`fileWriteLineLimit`** / **`fileReadLineLimit`** – Safety caps on I/O operations
- **`clientId`** – Anonymous identifier for telemetry

## The ConfigManager Singleton: Loading and Initialization

The `ConfigManager` class in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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:

1. **Directory creation** – Uses `fs.mkdirSync` with `recursive: true` to ensure `~/.claude-server-commander` exists
2. **File reading** – Attempts `fs.readFile` on `CONFIG_FILE`; falls back to defaults on `ENOENT` or JSON parse errors
3. **Version stamping** – Adds a `VERSION` field and writes the file if newly created (first-run detection)
4. **Serialization setup** – Initializes the `writeChain` Promise queue and `scheduleSave` timer for background persistence

```typescript
// 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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:

```typescript
// 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:

```typescript
// 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`:

```typescript
// 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:

```typescript
// 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json). The `FeatureFlagManager` in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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

```typescript
// 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

```typescript
// 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

```typescript
// 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/plugin.json) and [`.mcp.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/.mcp.json):

- **[`plugin.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/plugin.json)** – Plugin manifest pointing to [`.mcp.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/.mcp.json)
- **[`.mcp.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/.mcp.json)** – Registers `mcpServers.desktop-commander` for 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.json` stores 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_config` and `set_config_value` RPC 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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.