# How the featureFlagManager in Desktop Commander MCP Initializes and Controls Experimental Features and A/B Testing

> Discover how Desktop Commander MCP's featureFlagManager initializes and controls experimental features and A/B testing. Learn about its cache-first strategy for non-blocking access.

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

---

**The featureFlagManager in Desktop Commander MCP is a singleton class that uses a cache-first initialization strategy with background network fetching to provide non-blocking access to remote feature flags and experiment configurations.**

Desktop Commander MCP implements a robust feature flag system through the `FeatureFlagManager` class located in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts). This singleton manages the lifecycle of experimental features—from cold-start cache loading to periodic remote refreshes—while providing synchronous APIs for runtime checks. The architecture ensures that A/B testing logic in [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) can query flag values immediately, even before fresh network data arrives.

## Architecture Overview

The flag management system follows a **cache-first, refresh-after** pattern. At startup, the manager reads persisted flags from [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) adjacent to the global configuration, then initiates an asynchronous fetch to update values without blocking the main process. A **fresh-fetch promise** mechanism allows critical paths—such as A/B test assignments—to wait for guaranteed network-fetched data when needed.

## Initialization Flow

### Constructor and Configuration

The manager instantiates as a module-level singleton 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();

```

During construction, the manager establishes three key components:

- **Cache Location**: Resolves [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) beside the global config directory
- **Remote URL**: Uses `process.env.DC_FLAG_URL` or falls back to the default production endpoint ([L29-L31](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts#L29-L31))
- **Fresh-Fetch Promise**: Creates a deferred promise (`freshFetchPromise`) that resolves upon the first successful network fetch ([L33-L36](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts#L33-L36))

### The Initialize Method

The `initialize()` method ([L42-L73](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts#L42-L73)) orchestrates the startup sequence:

1. **Cache Loading**: `loadFromCache()` reads the local JSON file into `this.flags` and sets `loadedFromCache = true`
2. **Background Fetch**: Invokes `fetchFlags()` without awaiting, allowing the process to continue immediately
3. **Periodic Refresh**: Starts a `setInterval` (default every 5 minutes based on `cacheMaxAge`) to re-fetch flags, using `.unref()` to prevent the interval from keeping the process alive ([L61-L66](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts#L61-L66))

## Remote Fetching and Caching Strategy

### Cache-First Loading

The manager prioritizes **immediate availability** over freshness. During `initialize()`, it first hydrates the internal state from disk, ensuring that `get()` calls return values even when offline. This cached data remains available while the background network request proceeds.

### Background Fetch with AbortController

The `fetchFlags()` implementation ([L65-L98](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts#L65-L98)) uses defensive networking patterns:

- **Dual Timeout Protection**: Combines `AbortController` with a secondary `hardTimeout` to guarantee the request cannot hang indefinitely
- **Atomic Updates**: On success, updates `this.flags`, timestamps, and persists to cache via `saveToCache()` in a single flow
- **Promise Resolution**: Calls `resolveFreshFetch()` to settle the fresh-fetch promise, unblocking any waiting consumers

### Periodic Refresh Mechanism

The initialization routine establishes an interval timer that re-runs `fetchFlags()` every 5 minutes. This keeps experiment configurations current without requiring manual restarts, while the `.unref()` method ensures the timer doesn't interfere with graceful process shutdowns.

## Accessing Flags and Waiting for Fresh Data

### Synchronous Flag Accessors

The manager exposes three primary methods for reading state:

- **`get(flagName, defaultValue)`**: Returns `this.flags[flagName]` or the provided default
- **`getAll()`**: Returns a shallow copy of the entire flags object for debugging
- **`wasLoadedFromCache()`**: Boolean indicating whether current values originated from local storage versus a fresh network fetch

### The Fresh-Fetch Promise Pattern

For operations requiring guaranteed fresh data—such as A/B test bucketing—the `waitForFreshFlags()` method ([L20-L34](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts#L20-L34)) races the fresh-fetch promise against a 5-second safety timeout:

```typescript
await featureFlagManager.waitForFreshFlags();
// Now safe to make decisions based on latest remote configuration

```

This pattern prevents indefinite blocking while ensuring experiments use network-fetched weights.

## A/B Testing Integration

### Experiment Definition and Variant Assignment

The [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) module consumes flags through the manager to drive experiments:

- **Experiment Configuration**: Expects an `experiments` object in the remote JSON containing weighted variant definitions
- **Deterministic Assignment**: `getVariant()` uses a hash of `clientId + experimentName` to deterministically select variants, ensuring consistent user experiences across sessions ([L48-L96](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts#L48-L96))
- **Feature Detection**: `hasFeature(featureName)` iterates active experiments, fetches the user's assigned variant, and returns `true` only when the variant matches the requested feature ([L4-L22](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts#L4-L22))

### Consumer Usage Patterns

Applications typically follow this sequence when checking experimental features:

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

// At startup
await featureFlagManager.initialize();

// For critical experiment gates
await featureFlagManager.waitForFreshFlags();
if (await hasFeature('showOnboardingPage')) {
  // Show experimental onboarding UI
}

// For analytics or assignment tracking
const variant = await getABTestVariant('OnboardingPreTool');
console.log(`User assigned to variant: ${variant}`);

```

## Summary

- **Singleton Pattern**: The `featureFlagManager` exports a single instance from [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts) that coordinates all flag operations
- **Cache-First Initialization**: `initialize()` loads local cache immediately, then fetches remotely without blocking startup
- **Non-Blocking Refresh**: A 5-minute interval with `AbortController` timeouts keeps flags current while allowing clean process exits
- **Fresh-Fetch Coordination**: `waitForFreshFlags()` provides a gate for A/B tests that require network-guaranteed configuration
- **Deterministic A/B Tests**: The [`ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/ab-test.ts) utilities use consistent hashing against the manager's `experiments` flag to assign variants

## Frequently Asked Questions

### How does featureFlagManager handle offline startups?

The manager reads [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) from disk during `initialize()` before attempting any network requests. This **cache-first approach** ensures that `get()` calls return the last known values immediately, even without connectivity. The background fetch proceeds asynchronously, updating values only when the network becomes available.

### What happens if the remote flag server is slow or unresponsive?

The `fetchFlags()` method implements **dual timeout protection** using both `AbortController` and a secondary `hardTimeout`. If the fetch hangs, the abort signal triggers after a configured duration, preventing the promise from blocking indefinitely. The manager retains cached values and retries automatically during the next 5-minute refresh interval.

### How do A/B tests ensure consistent variant assignment across sessions?

The `getVariant()` function in [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) uses **deterministic hashing** based on a stable `clientId` concatenated with the experiment name. This hash maps to weighted buckets defined in the remote `experiments` flag. Because the algorithm is deterministic and the weights come from the cached configuration, users receive the same variant assignment every time they launch the application, even offline.

### Can consumers force a fresh flag check before critical decisions?

Yes. While standard flag checks use `get()` for immediate synchronous access, consumers can await `featureFlagManager.waitForFreshFlags()` before making important experiment assignments. This method races the internal fresh-fetch promise against a 5-second timeout, ensuring that code only proceeds after verifying that network-fetched flags are available, or timing out gracefully if the remote source fails.