# How the Feature Flag Manager Enables Gradual Feature Rollouts in DesktopCommanderMCP

> Learn how the DesktopCommanderMCP feature flag manager enables instant gradual feature rollouts by updating a remote JSON file without client redeployment.

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

---

**The DesktopCommanderMCP feature flag manager uses a cache-first, background-refresh architecture that allows developers to roll out features to a percentage of users instantly by updating a remote JSON file, without requiring client redeployment.**

The `wonderwhy-er/DesktopCommanderMCP` repository implements a self-contained **Feature Flag Manager** to drive safe, staged rollouts of new functionality. Located in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts), the `FeatureFlagManager` class follows a "cache-first, refresh-in-background" pattern that ensures immediate availability of flags while maintaining synchronization with remote configuration.

## Architecture Overview

The manager balances **availability** and **freshness** by loading persisted flags from disk on startup, then asynchronously fetching updates from a remote source. This design ensures users see functional defaults immediately—even when offline—while gradually adopting new rollout percentages as the network permits.

## The Seven-Step Rollout Mechanism

### 1. Cache-First Initialization (`loadFromCache`)

On application startup, the manager attempts to load previously saved flags from a local JSON cache stored at [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json). If the file is missing or corrupted, the system gracefully degrades to an empty flag set.

In [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts) (lines 39-49), the `loadFromCache()` method reads the file and populates `this.flags`, ensuring the application has working values before any network request completes.

### 2. Background Network Refresh (`fetchFlags`)

Immediately after cache initialization, the manager fires a non-blocking network request via `fetchFlags()`. When the remote data arrives, fresh flags replace the cached values and the local JSON file is rewritten with the new state.

This occurs in the `initialize()` method (lines 42-53), which triggers `fetchFlags()` and tracks the promise in `freshFetchPromise`.

### 3. Periodic Refresh (5-Minute Timer)

To support ongoing rollout adjustments without restarts, the manager establishes a recurring timer that pulls the remote flag file every **5 minutes** (configurable via `cacheMaxAge`).

As implemented in lines 61-66 of [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts), `setInterval(..., this.cacheMaxAge)` ensures that percentage-based rollouts can be increased from 10% to 50% of users simply by updating the remote configuration.

### 4. Synchronous Flag Retrieval (`get`)

Code checks flag values through the `get(name, fallback)` method (lines 78-84), which returns the current in-memory value from `this.flags`. This enables immediate, synchronous decisions in UI rendering paths.

```typescript
// Enable a beta UI component for configured percentage of users
if (featureFlagManager.get('new_sidebar', false)) {
  showNewSidebar();
}

```

### 5. Fresh-Flag Synchronization (`waitForFreshFlags`)

For critical A/B test assignments that require guaranteed fresh data, the manager exposes `waitForFreshFlags()` (lines 20-34). This method races the pending fetch promise against a **5-second safety timeout**, preventing application hangs during network degradation.

```typescript
// Guarantee freshest data before A/B assignment
await featureFlagManager.waitForFreshFlags();
const variant = Math.random() < featureFlagManager.get('beta_search_pct', 0.1)
                ? 'new' : 'old';
runSearchVariant(variant);

```

### 6. Manual Refresh (`refresh`)

In development environments or test suites, you can bypass the timer and force an immediate update using `refresh()` (lines 92-100), which internally calls `fetchFlags()` and returns the resulting promise.

```typescript
// Force immediate flag update in tests
await featureFlagManager.refresh();

```

### 7. Graceful Shutdown (`destroy`)

To prevent stray background work during application exit, the `destroy()` method (lines 38-46) clears the periodic interval timer, ensuring clean process termination.

## Integration Points Across the Codebase

The feature flag system integrates with several modules:

- **[`src/utils/welcome-onboarding.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/welcome-onboarding.ts)**: Uses `waitForFreshFlags` to determine which onboarding UI variant to display
- **[`src/utils/usageTracker.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/usageTracker.ts)**: Conditionally enables user surveys based on flag states
- **[`src/utils/mcp-ui-ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/mcp-ui-ab-test.ts)**: Exposes cache-status helpers for UI A/B testing scenarios
- **[`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts)**: Bootstraps the manager during application startup
- **[`src/tools/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/config.ts)**: Persists current flag snapshots for debugging purposes

## Summary

- **Cache-first startup**: Loads [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) immediately, ensuring offline functionality
- **Background synchronization**: Non-blocking fetch updates flags without freezing the UI
- **Periodic updates**: 5-minute refresh cycle allows real-time rollout percentage adjustments
- **Synchronous access**: `get()` method provides immediate flag checks for UI decisions
- **Fresh data guarantee**: `waitForFreshFlags()` ensures critical paths use the latest remote values
- **Clean lifecycle**: `destroy()` method prevents memory leaks and background process accumulation

## Frequently Asked Questions

### How does the feature flag manager handle offline scenarios?

The **cache-first architecture** ensures that if [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) exists locally, the application initializes with those values regardless of network connectivity. Users see the last known flag states, and the background refresh simply continues retrying on its 5-minute interval until connectivity returns.

### Can I force an immediate flag update without waiting for the 5-minute timer?

Yes. Call `await featureFlagManager.refresh()` to trigger `fetchFlags()` immediately. This is useful in development environments or when you need to verify flag changes during integration testing without restarting the application.

### What happens if the remote flag fetch fails?

The manager silently fails the background fetch and retains the current in-memory flags (either from the initial cache or the last successful refresh). The `get()` method continues returning the cached values, ensuring application stability during network outages.

### How are rollout percentages controlled?

Rollout percentages are stored as numeric values in the remote JSON configuration (e.g., `0.1` for 10%). The consuming code uses `featureFlagManager.get('feature_name', 0)` to retrieve the percentage, then applies standard randomization logic (`Math.random() < percentage`) to determine if the current user receives the new feature. Updating the remote JSON instantly changes the percentage for all clients within their next refresh window.