How DesktopCommanderMCP Detects Client Types (Claude Desktop, Cursor, Windsurf) and Adapts Behavior
The DesktopCommanderMCP server identifies connected clients by extracting the clientInfo object from the MCP protocol Initialize request, storing it in a global currentClient variable, and using this data to conditionally render onboarding pages, filter available tools, and tag analytics events.
DesktopCommanderMCP is a Model Context Protocol (MCP) server that dynamically adjusts its behavior based on the specific AI client connecting to it. According to the wonderwhy-er/DesktopCommanderMCP source code, the server implements client type detection to distinguish between Claude Desktop, Cursor, Windsurf, and other endpoints by inspecting protocol-level initialization parameters. This capability enables the server to suppress redundant onboarding flows for MCP-native clients and hide internal tools when running inside the Desktop Commander UI itself.
Capturing Client Information from Initialize Requests
When a client initiates a connection, the server's Initialize handler extracts the clientInfo object from the request parameters. This object contains a name field (such as claude-code, cursor, or windsurf) and an optional version string.
In src/server.ts, the handler captures this information and forwards it to the client update logic:
// src/server.ts – Initialize handler (lines 12‑15)
const clientInfo = request.params?.clientInfo;
if (clientInfo) {
await updateCurrentClient(clientInfo);
}
The clientInfo object serves as the primary source of truth for all subsequent client-specific adaptations within the server lifecycle.
Storing and Tracking the Current Client
The updateCurrentClient function updates a global currentClient object that persists throughout the connection session. This approach makes the client type accessible to all tool implementations and utility functions without requiring repeated parsing of the initialization request.
As implemented in src/server.ts (lines 86‑94):
// src/server.ts – updateCurrentClient
currentClient = {
name: clientInfo.name || currentClient.name,
version: clientInfo.version || currentClient.version,
};
Once stored, currentClient.name drives conditional logic for feature flags, UI components, and analytics tracking across the entire server architecture.
Detecting Remote and Legacy Clients
Beyond standard MCP clients, the server also identifies connections from remote devices or LLM proxies using the isRemoteClientContext function. This helper checks for the DC_REMOTE_DEVICE environment variable or a legacy client identifier (desktop-commander-client) to distinguish remote contexts from local IDE integrations.
From src/server.ts (lines 79‑81):
function isRemoteClientContext(clientName?: string): boolean {
return process.env.DC_REMOTE_DEVICE === 'true' ||
clientName === 'desktop-commander-client';
}
Remote contexts trigger different security and UI behaviors, such as skipping local onboarding pages that require direct filesystem access.
Client-Specific Adaptations and Feature Flags
The server uses the stored currentClient data to modify its behavior across three primary areas: onboarding flows, tool availability, and analytics tagging.
Conditional Onboarding Exclusions
The server maintains a feature flag welcome_page_excluded_clients that lists clients like claude-code, cursor, and windsurf. When these clients connect, the welcome page is suppressed to avoid redundant setup prompts, as these applications handle their own configuration.
The eligibility logic in src/server.ts (lines 21‑24) evaluates:
const isWelcomePageEligibleClient =
currentClient.name !== 'desktop-commander-app' &&
currentClient.name !== 'desktop-commander' &&
!isRemoteClientContext(currentClient.name) &&
!(global as any).disableOnboarding;
This prevents the Desktop Commander app itself, remote devices, and major MCP clients from receiving the standard onboarding URL.
Tool Visibility Control
Certain internal tools—specifically feedback-related functions like give_feedback_to_desktop_commander and get_prompts—are hidden when the server detects it is running inside the Desktop Commander UI itself. This prevents self-referential tool loops where the app would try to submit feedback to itself.
As shown in src/server.ts (lines 86‑92):
if (currentClient?.name === 'desktop-commander-app') {
if (toolName === 'give_feedback_to_desktop_commander' ||
toolName === 'get_prompts') {
return false;
}
}
Analytics Context Tagging
The server records the client type for telemetry using the capture function, which inspects environment variables including CLAUDE_CODE_ENTRYPOINT, AI_AGENT, and CLAUDE_PLUGIN_DATA to distinguish between CLI, Desktop, and plugin contexts.
From src/server.ts (lines 45‑50):
capture('run_server_mcp_initialized', {
host_entrypoint: process.env.CLAUDE_CODE_ENTRYPOINT?.substring(0, 100),
host_agent: process.env.AI_AGENT?.substring(0, 100),
host_plugin_id: process.env.CLAUDE_PLUGIN_DATA
? path.basename(process.env.CLAUDE_PLUGIN_DATA).substring(0, 100)
: undefined,
});
This tagging enables the maintainers to analyze usage patterns across different client ecosystems.
Practical Implementation Examples
Accessing the Client Name in Custom Tools
Tool implementations can import the currentClient object to adjust behavior for specific clients:
import { currentClient } from '../server';
export async function myTool(_: any) {
const client = currentClient?.name ?? 'unknown';
if (client === 'cursor') {
// Adjust behavior for Cursor
return { result: 'Cursor‑specific response' };
}
if (client === 'windsurf') {
// Adjust behavior for Windsurf
return { result: 'Windsurf‑specific response' };
}
// Default handling for other clients
return { result: 'Generic response' };
}
Skipping Onboarding for Remote Clients
Remote detection logic can gate UI flows that require local filesystem access:
import { isRemoteClientContext, currentClient } from '../server';
if (isRemoteClientContext(currentClient?.name)) {
// Remote client – do not show onboarding UI
await skipWelcomePageOnboarding();
} else {
await handleWelcomePageOnboarding(currentClient?.name);
}
Key Files Involved
| File | Role |
|---|---|
| src/server.ts | Core request handling, client detection via clientInfo, and currentClient state management |
| src/utils/open-browser.ts | Sends onboarding page URLs with utm_source derived from the client name |
| src/utils/welcome-onboarding.ts | Implements onboarding flows and respects the welcome_page_excluded_clients flag |
| src/utils/feature-flags.ts | Provides the welcome_page_excluded_clients flag listing claude-code, cursor, and windsurf |
| src/remote-device/desktop-commander-integration.ts | Sets DC_REMOTE_DEVICE for remote-device wrappers |
| src/utils/capture.ts | Emits analytics events containing client-type identifiers |
Summary
- Client detection relies on the
clientInfoobject supplied in the MCP protocol Initialize request, specifically extracting thenameandversionfields. - Global state stores the client type in
currentClient, updated viaupdateCurrentClientinsrc/server.ts, making it accessible across tool implementations. - Remote identification uses
isRemoteClientContextto check theDC_REMOTE_DEVICEenvironment variable and legacy client identifiers. - Feature adaptation leverages the
welcome_page_excluded_clientsflag to suppress onboarding for Claude Desktop, Cursor, and Windsurf, while tool filtering hides internal utilities when running inside the Desktop Commander app. - Analytics tagging captures client context through environment variables to track usage across different entry points.
Frequently Asked Questions
How does DesktopCommanderMCP identify which AI client is connecting?
The server reads the clientInfo object from the MCP Initialize request parameters, which contains a name field identifying the client (e.g., claude-code, cursor, windsurf). This information is stored in the global currentClient variable and used throughout the server session to adapt behavior.
What is the difference between client detection for local versus remote connections?
Local connections are identified directly from the clientInfo.name field in the Initialize request, while remote connections are detected via the isRemoteClientContext function, which checks for the DC_REMOTE_DEVICE environment variable or the legacy identifier desktop-commander-client. Remote contexts bypass local onboarding flows and may have restricted tool access.
How can I access the client type inside a custom tool implementation?
Import the currentClient object from src/server.ts and access its name property. This allows conditionals like if (currentClient?.name === 'cursor') to provide client-specific responses or adjust tool behavior without modifying the core server initialization logic.
Why are some tools hidden for certain clients?
Tools like give_feedback_to_desktop_commander are explicitly filtered out when currentClient.name === 'desktop-commander-app' to prevent the UI from invoking feedback tools on itself. Similarly, the onboarding welcome page is suppressed for clients listed in welcome_page_excluded_clients (including claude-code, cursor, and windsurf) because these applications handle their own configuration workflows.
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 →