How DesktopCommanderMCP Evaluates Feature Flags and Manages A/B Testing

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 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), 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 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 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:

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

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

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

Retrieving Current A/B Assignments for Analytics

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

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 handles caching to 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →