# How DesktopCommanderMCP Evaluates Feature Flags and Manages A/B Testing

> DesktopCommanderMCP evaluates feature flags with local caching and remote updates. Discover how its A/B testing utility assigns weighted variants deterministically for efficient experimentation.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: how-to-guide
- Published: 2026-07-19

---

**DesktopCommanderMCP implements a two-layer architecture that combines local flag caching with remote configuration updates, plus a deterministic A/B testing utility that assigns weighted variants based on a persistent client identifier.**

The Model Context Protocol (MCP) server known as DesktopCommanderMCP relies on dynamic configuration to roll out features gradually and run experiments. Its architecture separates flag management from experiment assignment, enabling fast startup times while ensuring consistent user experiences across sessions.

## Feature Flag Manager Architecture

The core flag management logic resides in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts) inside the `FeatureFlagManager` class. This singleton handles loading, caching, and refreshing flag definitions from a remote JSON endpoint.

### Initialization and Cache Loading

When the `featureFlagManager` singleton is constructed, it records the cache path ([`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json)), the remote URL (from the `DC_FLAG_URL` environment variable or a default production endpoint), and prepares a promise that resolves once a fresh network fetch completes.

During application startup, the bootstrap process calls `initialize()`, which performs three critical tasks:

- Loads cached flags from disk via `loadFromCache()` to ensure immediate availability.
- Kicks off a background `fetchFlags()` operation and resolves the initialization promise when fresh data arrives.
- Starts a periodic refresh interval (defaulting to 5 minutes) controlled by the `cacheMaxAge` parameter.

This design guarantees that the application can render using cached values while silently updating definitions in the background.

### Remote Fetching with Timeout Handling

The `fetchFlags()` method contacts the remote endpoint using a hard-coded 3-second timeout (`FETCH_TIMEOUT_MS`). To ensure reliability across different platforms, the implementation combines `AbortController` with a `Promise.race` fallback. This guarantees that the timeout triggers even on platforms where `abort()` might not interrupt an active TCP connection.

Successful responses are persisted to [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) via `saveToCache()`, making them available for subsequent launches without network access.

### The get() API

Once initialized, code can query flag values through the simple `get(name, default)` method. This synchronous API returns the current in-memory value or the supplied default if the flag is undefined, allowing components to make split-second rendering decisions without awaiting network operations.

## A/B Testing Implementation

Built atop the flag manager, the A/B testing utility in [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) manages experiment assignments. It reads the `experiments` section from the flag payload, where each experiment defines weighted variants using objects containing `name` and `weight` properties.

### Deterministic Variant Assignment

The private `getVariant(experimentName)` method implements the assignment logic using a three-tier lookup strategy:

1. Check an in-process `variantCache` for active session consistency.
2. Fall back to persistent storage using keys formatted as `abTest_<experimentName>` in the user configuration managed by `configManager`.
3. If no assignment exists, generate a deterministic hash using `configManager.getOrCreateClientId()` combined with the experiment name.

This hash-based approach ensures that the same client identifier always maps to the same variant, eliminating flickering or reassignment across app restarts.

### Weighted Variant Selection Algorithm

When creating a new assignment, the system calculates the total weight of all variants and computes `hash % totalWeight`. It then iterates through the variant list, accumulating weights until the remainder falls within a specific bucket. If all configured weights are zero, the code automatically falls back to an equal distribution among variants.

The selected variant is immediately persisted to the config store, ensuring stable assignment for future sessions.

### Persistence and Helper Functions

The A/B layer exposes several convenience methods:

- **`hasFeature(featureName)`**: Iterates through all experiments to find a variant matching the requested feature name, returning a boolean indicating assignment status.
- **`getABTestVariant(name)`**: Public wrapper around the private `getVariant()` logic.
- **`getABTestAssignments()`**: Aggregates all stored `abTest_*` entries from the configuration, useful for analytics reporting and telemetry.

## Coordination Between Layers

When code requires up-to-date experiment data, it can await `featureFlagManager.waitForFreshFlags()`. This method waits for the background fetch promise to resolve (or times out after 5 seconds), ensuring decisions use the latest remote configuration rather than potentially stale cached values.

Typical usage follows this pattern:

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

async function renderOnboarding() {
  await featureFlagManager.waitForFreshFlags();
  if (await hasFeature('showOnboardingPage')) {
    // Display experiment variant
  }
}

```

## Practical Code Examples

### Checking a Simple Feature Flag

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

// Returns boolean or default value
if (featureFlagManager.get('enableNewUI', false)) {
  renderNewInterface();
}

```

### Retrieving Current A/B Assignments for Analytics

```typescript
import { getABTestAssignments } from '@/utils/ab-test';

async function sendTelemetry() {
  const assignments = await getABTestAssignments();
  // Structure: { experimentName: variantName, ... }
  analytics.track('experiment_enrollment', assignments);
}

```

### Waiting for Fresh Configuration

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

async function initializeExperiments() {
  // Ensures flags are no older than cacheMaxAge
  await featureFlagManager.waitForFreshFlags();
  console.log('Using latest experiment configuration');
}

```

## Summary

- **DesktopCommanderMCP** uses a two-layer architecture separating flag management from experiment assignment.
- The **FeatureFlagManager** in [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts) handles caching to [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json), remote fetching with 3-second timeout guarantees via `AbortController` and `Promise.race`, and periodic 5-minute refreshes.
- **A/B testing** in [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) uses deterministic hashing based on `configManager.getOrCreateClientId()` to ensure consistent variant assignment across sessions.
- The system supports **weighted variants** with automatic equal-distribution fallback and persists assignments using `abTest_<experimentName>` keys.
- **`waitForFreshFlags()`** provides an optional synchronization point for code requiring the latest remote configuration before making experiment decisions.

## Frequently Asked Questions

### How does DesktopCommanderMCP handle network timeouts when fetching feature flags?

The `fetchFlags()` method implements a 3-second timeout using `FETCH_TIMEOUT_MS` combined with `AbortController` and `Promise.race`. This dual approach ensures the timeout triggers reliably even on platforms where the abort signal might not immediately terminate TCP connections, preventing the application from hanging during startup.

### What happens if the remote flag server is unavailable on startup?

The system first loads cached flags from [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) via `loadFromCache()`, allowing immediate operation. The background fetch proceeds silently, and if it fails, the application continues using cached values. The cache refreshes automatically every 5 minutes when connectivity returns.

### How does the A/B testing system ensure users stay in the same variant across app restarts?

After initial assignment, the selected variant is persisted to the user configuration under keys formatted as `abTest_<experimentName>`. On subsequent launches, `getVariant()` checks this persistent storage before calculating a new assignment, ensuring users maintain consistent experiences unless explicitly reassigned by configuration changes.

### Can experiments use unequal traffic splits between variants?

Yes. The variant selection algorithm supports weighted distributions by reading `weight` values from the experiment definition. It calculates the total weight, generates a hash-based remainder, and walks the variant list until the cumulative weight exceeds the remainder. If all weights are zero, it automatically falls back to equal distribution among all variants.