DesktopCommanderMCP Feature Flag Architecture: How A/B Tests Are Implemented
DesktopCommanderMCP uses a centralized FeatureFlagManager singleton that fetches remote JSON configurations, caches them locally to 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 and the A/B testing logic in src/utils/ab-test.ts.
Core Architecture Components
FeatureFlagManager Singleton
Located in 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 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.jsonto ensure offline availability - Cache tracking: The boolean
loadedFromCacheproperty distinguishes between stale disk data and fresh network payloads - Background refresh: A
setInterval(default 5 minutes) periodically callsfetchFlags(), withunref()applied to prevent the interval from blocking clean process exit
A/B Test Helper Module
The 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 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 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:
{
"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:
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)
Executing A/B Test Branches
For experiment-dependent rendering, use the hasFeature helper which automatically awaits fresh flags:
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)
Blocking for Fresh Configuration
Critical paths like onboarding should await fresh flags before proceeding:
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)
Summary
- DesktopCommanderMCP implements a singleton FeatureFlagManager in
src/utils/feature-flags.tsthat synchronizes remote JSON configurations with local caching tofeature-flags.json. - The v2 schema separates simple flags from A/B test definitions under the
experimentsfield, where each experiment specifies weighted variants and deterministic user assignments. - A/B tests use the
hasFeature()helper insrc/utils/ab-test.tsto 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →