# How DesktopCommanderMCP Enables Dynamic Feature Toggles at Runtime

> Discover how DesktopCommanderMCP's featureFlagManager enables dynamic feature toggles at runtime. Update functionality instantly without restarts using a cache-first, async mechanism.

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

---

**The FeatureFlagManager provides a cache-first, asynchronous mechanism that updates feature flags in-place every 5 minutes, allowing the application to toggle functionality instantly without restarts.**

DesktopCommanderMCP implements a robust **FeatureFlagManager** (located in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts)) to control feature availability dynamically. This system enables runtime configuration changes without requiring application redeployment or process restarts. The manager follows a singleton pattern, ensuring consistent flag state across all modules while maintaining graceful degradation when network connectivity is unavailable.

## Architecture of the Feature Flag Manager

The `FeatureFlagManager` class implements a seven-stage lifecycle that balances immediate availability with fresh data:

### Cache-First Initialization

On startup, the manager prioritizes speed over freshness. The `loadFromCache()` method (lines 40-55) synchronously reads from a local [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) file, populating `this.flags`—a plain JavaScript object that serves as the single source of truth.

```typescript
// Inside FeatureFlagManager
private async loadFromCache(): Promise<void> {
  // Reads feature-flags.json immediately on startup
  // Populates this.flags without blocking the event loop
}

```

This cache-first approach ensures the application boots instantly, even if the remote flag service is unreachable.

### Background Refresh and Polling

After the cache loads, `initialize()` (lines 42-61) triggers an asynchronous `fetchFlags()` call to retrieve the latest configuration from `DC_FLAG_URL` (or a default production endpoint). A `setInterval` timer then repeats this fetch every 5 minutes (controlled by `cacheMaxAge`), ensuring flags remain fresh throughout the process lifetime.

```typescript
// From src/utils/feature-flags.ts
public async initialize(): Promise<void> {
  await this.loadFromCache();
  this.fetchFlags(); // Non-blocking initial fetch
  
  // Periodic polling every 5 minutes
  this.intervalId = setInterval(() => {
    this.fetchFlags();
  }, this.cacheMaxAge);
}

```

### Safe Timeout Handling

Network requests include defensive programming to prevent hanging connections. The `fetchFlags()` method (lines 71-89) wraps the fetch in an `AbortController` and races it against a hard timeout using `Promise.race`, guaranteeing resolution even on platforms where TCP aborts fail.

```typescript
private async fetchFlags(): Promise<void> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);
  
  try {
    const response = await fetch(this.url, { signal: controller.signal });
    // Update this.flags in-place
  } catch (error) {
    // Graceful degradation handled here
  } finally {
    clearTimeout(timeout);
  }
}

```

## Core API for Dynamic Runtime Toggles

The manager exposes a minimal public API that enables synchronous reads of asynchronously updated data:

- **`get(name, default)`**: Returns the current flag value from `this.flags`, or the default if undefined.
- **`waitForFreshFlags()`**: Returns a Promise that resolves once the current network fetch completes—critical for first-run scenarios where no cache exists.
- **`wasLoadedFromCache()`**: Boolean indicating whether the current flags came from disk or network.
- **`getAll()`**: Returns the entire flag map for debugging purposes.

Because `this.flags` is updated in-place when `fetchFlags()` succeeds, all subsequent `get()` calls automatically receive the new values without requiring module reloads or state propagation.

## Implementation in Source Files

The singleton instance is exported at the bottom of [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts):

```typescript
export const featureFlagManager = new FeatureFlagManager();

```

### Onboarding Flow Integration

The [`src/utils/welcome-onboarding.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/welcome-onboarding.ts) file demonstrates waiting for fresh flags before making UI decisions:

```typescript
import { featureFlagManager } from './utils/feature-flags.js';
import { hasFeature } from './utils/ab-test.js';

export async function handleWelcomePageOnboarding() {
  const pending = await configManager.getValue('pendingWelcomeOnboarding');
  if (!pending) return;

  // Block only for first-time users without cache
  if (!featureFlagManager.wasLoadedFromCache()) {
    await featureFlagManager.waitForFreshFlags();
  }

  const shouldShow = await hasFeature('showOnboardingPage');
  if (shouldShow) {
    // Render onboarding UI
  }
}

```

### A/B Testing and Telemetry

In [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) (line 41), the `experiments` flag drives test assignments:

```typescript
const experiments = featureFlagManager.get('experiments', {});

```

Similarly, [`src/utils/usageTracker.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/usageTracker.ts) (lines 224-226) conditionally sends surveys based on the `user_surveys` flag:

```typescript
if (featureFlagManager.get('user_surveys', false)) {
  // Trigger survey telemetry
}

```

### Server Bootstrap Logging

The main entry point ([`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts), line 111) logs the entire flag map during startup for operational visibility:

```typescript
console.log('Feature flags:', featureFlagManager.getAll());

```

## Graceful Degradation and Error Handling

When `fetchFlags()` encounters network errors (lines 110-112), the catch block silently preserves the existing cached flags. This ensures that temporary connectivity issues never break functionality—the application simply continues operating with the last known good configuration.

The `destroy()` method (lines 40-45) clears the polling interval during shutdown, preventing memory leaks and stray handles in long-running processes.

## Summary

- **Cache-first loading** delivers immediate availability via [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) on startup, stored in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts).
- **Background polling** updates flags every 5 minutes without blocking the main thread, using `initialize()` and `fetchFlags()`.
- **Zero-downtime toggles** are achieved by updating `this.flags` in-place, making changes instantly visible to all `get()` callers.
- **Defensive timeouts** via `AbortController` and `Promise.race` prevent network hangs in `fetchFlags()`.
- **Graceful degradation** ensures the application remains functional even when the remote flag service is unreachable.
- **Singleton pattern** guarantees consistent flag state across [`welcome-onboarding.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/welcome-onboarding.ts), [`ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/ab-test.ts), and [`usageTracker.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/usageTracker.ts).

## Frequently Asked Questions

### How does the FeatureFlagManager handle the first application launch when no cache exists?

On first launch, `wasLoadedFromCache()` returns `false` because [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) does not exist. Code paths that require flags immediately—such as the onboarding flow in [`src/utils/welcome-onboarding.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/welcome-onboarding.ts)—can await `waitForFreshFlags()`, which blocks only until the initial network fetch completes. This ensures fresh configuration is available before rendering critical UI, while the rest of the application continues initialization unblocked.

### What happens if the remote flag server becomes unreachable?

The manager implements graceful degradation in the `fetchFlags()` catch block (lines 110-112). If the network request fails—whether due to timeout, DNS failure, or HTTP errors—the manager retains the existing cached flags in `this.flags`. The application continues operating normally without throwing errors, ensuring stability during network outages.

### Can features be toggled without restarting the DesktopCommanderMCP server?

Yes. The `FeatureFlagManager` updates the `this.flags` object in-place when `fetchFlags()` receives a successful response. Because all consumers read from this same object via `get()` calls, changes to remote flags become effective immediately upon the next polling interval (every 5 minutes) without requiring process restarts or module reloads.

### How do I force a manual refresh of feature flags outside the polling cycle?

While the manager automatically polls every 5 minutes, you can trigger an immediate refresh by calling `await featureFlagManager.refresh()`. This method returns `true` on successful fetch and update, allowing test suites or administrative commands to bypass the normal polling interval when immediate propagation is required.