# DesktopCommanderMCP Feature Flag Architecture: How A/B Tests Are Implemented

> Explore the DesktopCommanderMCP feature flag architecture. Learn how weighted variants in remote JSON drive deterministic A/B test assignments and are cached locally.

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

---

**DesktopCommanderMCP uses a centralized FeatureFlagManager singleton that fetches remote JSON configurations, caches them locally to [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json), and exposes a v2 schema where weighted variant distributions in the `experiments` field drive deterministic A/B test assignments.**

The DesktopCommanderMCP repository implements a production-grade feature flag system to enable gradual rollouts and controlled experimentation without redeploying the application. At its core, the architecture separates flag management into a singleton manager handling remote synchronization while maintaining offline capability through local JSON caching. Understanding how the system processes both simple boolean toggles and complex experiment assignments requires examining the core components in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts) and the A/B testing logic in [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts).

## Core Architecture Components

### FeatureFlagManager Singleton

Located in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts), the **FeatureFlagManager** class serves as the central authority for all feature flag operations. It maintains an internal cache of flag values loaded from both local storage ([`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) in the config directory) and remote endpoints defined by the `flagUrl` parameter. The manager exposes critical methods including `initialize()` for non-blocking setup, `get(name, default?)` for value retrieval, and `waitForFreshFlags()` to block execution until the first successful network synchronization completes.

Key implementation details include:

- **Local persistence**: Remote payloads are written to [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) to ensure offline availability
- **Cache tracking**: The boolean `loadedFromCache` property distinguishes between stale disk data and fresh network payloads
- **Background refresh**: A `setInterval` (default 5 minutes) periodically calls `fetchFlags()`, with `unref()` applied to prevent the interval from blocking clean process exit

### A/B Test Helper Module

The [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) module interprets the `experiments` flag structure to determine user variant assignments. It exports three primary functions: `getExperiments()` retrieves the experiments map from the feature flag manager, `getVariant(experimentName)` returns the assigned variant string (defaulting to `'control'`), and `hasFeature(featureName)` checks if the current user qualifies for a specific experimental feature by parsing the feature name to extract the experiment context.

### UI-A/B Bridge

For interface components requiring guaranteed fresh data, [`src/utils/mcp-ui-ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/mcp-ui-ab-test.ts) re-exports `wasLoadedFromCache()` and `waitForFreshFlags()` from the main manager. This allows onboarding flows and critical UI paths to await network confirmation before rendering experiment-specific content, preventing flickering between control and variant experiences.

## Initialization and Data Flow

### Startup Sequence

The initialization sequence begins in [`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts) where the application creates the singleton `featureFlagManager` instance and invokes `initialize()`. This method operates non-blocking: it immediately loads cached values from disk via `loadFromCache()` while simultaneously initiating a background network request to the configured `flagUrl` via `fetchFlags()`.

When the remote fetch succeeds, the internal `freshFetchPromise` resolves, unblocking any callers awaiting `waitForFreshFlags()`. This dual-phase approach ensures the application remains responsive during startup while guaranteeing eventual consistency with remote configuration.

### Remote Flag Schema (v2)

The system expects a JSON payload conforming to the **v2 schema**:

```json
{
  "version": "2",
  "flags": {
    "welcome_page_enabled": true,
    "experiments": {
      "new_ui": {
        "weights": { "control": 50, "variantA": 50 },
        "assignment": "variantA"
      }
    }
  }
}

```

The `flags` object contains simple boolean or string values, while the `experiments` object defines A/B tests through weighted distributions and pre-calculated assignments. The client respects the `assignment` field as the source of truth, while `weights` documents the intended distribution logic used by the server to calculate that assignment.

## A/B Test Implementation Details

### Variant Assignment Logic

A/B tests rely entirely on the `experiments` flag structure. When `hasFeature('new_ui_variantA')` is called, the helper extracts the experiment name (`new_ui`) from the feature string, retrieves the user's assigned variant from the flag payload, and returns `true` only if the assignment matches the requested feature variant.

This design centralizes assignment logic on the server side, allowing the client to remain a simple stateless consumer. The deterministic assignment ensures users see consistent experiences across sessions without requiring client-side randomization or user ID hashing.

### Synchronization Guarantees

UI components that must avoid flickering or incorrect experiment rendering call `await featureFlagManager.waitForFreshFlags()` before checking experiment status. This ensures the decision reflects the latest remote configuration rather than potentially stale cached data from previous sessions. The `wasLoadedFromCache()` method allows components to display loading spinners when fresh data is pending.

## Practical Implementation Examples

### Reading Simple Feature Flags

To check a standard boolean flag without waiting for network synchronization:

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

const welcomeEnabled = featureFlagManager.get('welcome_page_enabled', true);
if (welcomeEnabled) {
  // Render welcome page
}

```

*Source:* [[`src/utils/welcome-onboarding.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/welcome-onboarding.ts)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/welcome-onboarding.ts)

### Executing A/B Test Branches

For experiment-dependent rendering, use the `hasFeature` helper which automatically awaits fresh flags:

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

async function renderDashboard() {
  if (await hasFeature('new_ui_variantA')) {
    renderVariantA();
  } else {
    renderControl();
  }
}

```

*Source:* [[`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts)

### Blocking for Fresh Configuration

Critical paths like onboarding should await fresh flags before proceeding:

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

async function initOnboarding() {
  await featureFlagManager.waitForFreshFlags();
  const onboardingEnabled = featureFlagManager.get('onboarding_injection', false);
  if (onboardingEnabled) {
    startOnboardingFlow();
  }
}

```

*Source:* [[`src/utils/mcp-ui-ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/mcp-ui-ab-test.ts)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/mcp-ui-ab-test.ts)

## Summary

- **DesktopCommanderMCP** implements a singleton **FeatureFlagManager** in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts) that synchronizes remote JSON configurations with local caching to [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json).
- The **v2 schema** separates simple flags from A/B test definitions under the `experiments` field, where each experiment specifies weighted variants and deterministic user assignments.
- **A/B tests** use the `hasFeature()` helper in [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) to match feature names against pre-calculated variant assignments, ensuring consistent behavior per user.
- The **initialization flow** loads cached values immediately while fetching fresh data in the background, with `waitForFreshFlags()` providing a synchronization point for critical UI components.
- Background refresh occurs every 5 minutes via an `unref()`-ed interval, allowing clean process termination while maintaining flag freshness.

## Frequently Asked Questions

### How does DesktopCommanderMCP handle offline scenarios when feature flags cannot be fetched?

The **FeatureFlagManager** persists the last successful remote payload to [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) in the configuration directory. During initialization, it loads these cached values immediately so the application functions offline. The `wasLoadedFromCache()` method indicates whether current values came from disk rather than the network, allowing UI to display appropriate loading states or fallback behaviors while awaiting fresh data.

### What determines which variant a user sees in an A/B test?

Variant assignment is **server-side deterministic**. The remote JSON payload includes an `assignment` field within each experiment object (e.g., `"assignment": "variantA"`), which the client reads but does not calculate. While the `weights` field documents the intended distribution logic used by the server, the client simply respects the pre-computed assignment, ensuring consistent experiences across sessions and devices for the same user.

### Can feature flags be updated without restarting the application?

Yes. The system implements **hot reloading** through a background refresh mechanism. The `FeatureFlagManager` sets a 5-minute interval that re-fetches the remote JSON and updates the in-memory cache. New flag values take effect immediately for subsequent `get()` calls without requiring application restart. For immediate updates, call `await featureFlagManager.refresh()` to force a synchronous network fetch.

### How do UI components prevent showing the wrong A/B test variant during initial load?

Components call `await featureFlagManager.waitForFreshFlags()` before rendering experiment-dependent content. This blocks execution until the first successful network fetch completes, ensuring the component uses the latest experiment assignments rather than potentially stale cached values. The `wasLoadedFromCache()` check allows components to show loading states when flags haven't been refreshed from the network yet.