How DesktopCommanderMCP's New User Onboarding System Guides First-Time Users
DesktopCommanderMCP uses a three-stage pipeline—Config Manager, A/B test engine, and Welcome-Onboarding Logic—to show the welcome page exactly once to 80% of new installations while skipping existing users entirely.
The onboarding system in wonderwhy-er/DesktopCommanderMCP ensures first-time users receive a guided introduction without ever interrupting returning users. By combining persistent configuration flags, deterministic A/B assignment, and conditional page launching, the system balances user experience with data-driven iteration.
Core Components of the Onboarding Pipeline
The onboarding flow is implemented across three coordinated modules. Each handles a distinct responsibility: eligibility detection, experiment assignment, and final orchestration.
Config Manager: Flagging Fresh Installations
In src/config-manager.ts, the ConfigManager class determines whether the current session qualifies as a first run. When ConfigManager.init() executes, it checks for an existing config file at this.configPath.
If no config exists, the system creates a new one with default flags:
// Inside ConfigManager.init()
if (await fs.access(this.configPath).catch(() => false)) {
// Existing config → no onboarding
this._isFirstRun = false;
} else {
// First run → enable onboarding flags
this.config = this.getDefaultConfig(); // pendingWelcomeOnboarding: true
this._isFirstRun = true;
await this.saveConfig();
}
The default configuration sets two critical flags:
welcomeOnboardingEligible: true— marks this installation as qualifying for onboardingpendingWelcomeOnboarding: true— indicates the welcome page has not yet been shown
Existing configs are migrated with both flags set to false, ensuring retroactive onboarding never occurs.
A/B Test Engine: Deterministic Variant Assignment
The hasFeature() function in src/utils/ab-test.ts implements deterministic experiment assignment. For the OnboardingPreTool experiment, the remote feature-flag JSON defines two variants:
| Variant | Weight | Description |
|---|---|---|
noOnboardingPage |
20% | Control group — onboarding is skipped |
showOnboardingPage |
80% | Treatment group — welcome page is displayed |
The assignment is deterministic per user based on a stable identifier, and the result is cached under abTest_<experiment> in the config. This guarantees the same user receives consistent treatment across restarts.
Welcome-Onboarding Logic: Orchestrating the Flow
The handleWelcomePageOnboarding() function in src/utils/welcome-onboarding.ts runs at client startup and executes an eight-step verification sequence:
- Eligibility check — verifies
welcomeOnboardingEligibleistrue - Pending status — confirms
pendingWelcomeOnboardingremainstrue - Feature flag readiness — waits for fresh flags if not yet cached
- Global toggle — honors the
welcome_page_enabledsetting - Exclusion list — skips users in
welcome_page_excluded_clients - A/B decision — calls
hasFeature('showOnboardingPage')for variant assignment - Page launch — if treatment applies, calls
openWelcomePage()and recordsserver_welcome_page_opened - Flag cleanup — clears
pendingWelcomeOnboardingregardless of outcome
import { handleWelcomePageOnboarding } from './utils/welcome-onboarding.js';
async function startClient(clientName?: string) {
// … other startup logic …
await handleWelcomePageOnboarding(clientName);
}
Opening the Welcome Page
The final step uses openWelcomePage() from src/utils/open-browser.ts. This function constructs a parameterized URL and invokes the OS-appropriate command:
export async function openWelcomePage(clientName?: string) {
const url = `https://github.com/wonderwhy-er/DesktopCommanderMCP/wiki/Welcome?client=${encodeURIComponent(clientName ?? '')}`;
await openBrowser(url); // OS‑specific command (xdg‑open, open, start)
}
The openBrowser() helper selects between xdg-open (Linux), open (macOS), or start (Windows). Success and failure cases are logged via logToStderr, and analytics events are captured through capture().
Key Implementation Files
| File | Purpose |
|---|---|
src/config-manager.ts |
Persistent config with onboarding flags |
src/utils/welcome-onboarding.ts |
Core orchestration logic |
src/utils/ab-test.ts |
Deterministic A/B variant assignment |
src/utils/open-browser.ts |
Cross-platform browser launching |
src/utils/feature-flags.ts |
Remote flag fetching and caching |
Summary
- Fresh installations are flagged automatically in
config-manager.tsviawelcomeOnboardingEligibleandpendingWelcomeOnboarding - Existing users are excluded through config migration that sets both flags to
false - A/B testing assigns 80% of eligible users to the treatment group using deterministic hashing in
ab-test.ts - Conditional execution in
welcome-onboarding.tsruns eight verification checks before launching the page - Exactly-once guarantee is enforced by clearing
pendingWelcomeOnboardingimmediately after the decision
Frequently Asked Questions
What prevents the welcome page from showing multiple times?
The pendingWelcomeOnboarding flag is cleared immediately after handleWelcomePageOnboarding() completes, regardless of whether the page was actually opened. This state change is persisted through configManager, making subsequent calls no-ops.
Can existing users ever see the onboarding page?
No. During config initialization, existing configuration files are migrated with welcomeOnboardingEligible: false. Only brand-new installations where no prior config existed receive the eligible flag.
How does the A/B test maintain consistent assignment across restarts?
The hasFeature() function in ab-test.ts uses deterministic hashing of a stable user identifier combined with the experiment name. The result is stored in config under abTest_<experiment>, ensuring the same variant is returned on every call.
What happens if feature flags haven't loaded when onboarding runs?
handleWelcomePageOnboarding() explicitly waits for fresh feature flags if they aren't cached yet. This prevents premature decisions based on stale or missing experiment definitions.
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 →