How Desktop Commander Server Handles Client Requests and Tool Call Schemas

The Desktop Commander MCP server uses the Model Context Protocol (MCP) Server class to register typed request handlers, validates incoming tool arguments against Zod schemas in src/tools/schemas.ts, and dispatches execution through a central handleCallToolRequest function that separates local, remote, and UI-origin contexts.

The Desktop Commander MCP server, available at wonderwhy-er/DesktopCommanderMCP, provides a production-ready example of handling client requests and enforcing tool call schemas in TypeScript. Built on the Model Context Protocol (MCP), this implementation demonstrates robust request validation, telemetry separation, and secure command execution across local and remote device contexts.

Server Bootstrap and MCP Foundation

The server initializes in src/server.ts by creating an MCP Server instance with explicit capabilities:

export const server = new Server(
  { name: "desktop-commander", version: VERSION },
  { capabilities: { tools: {}, resources: {}, prompts: {}, logging: {} } },
);

This instance exports the server for other modules to register handlers. The capabilities object declares support for tools, resources, prompts, and logging, allowing MCP clients to discover available functionality during initialization.

Registering Static Request Handlers

The server registers handlers for standard MCP method schemas to serve UI resources and prompts:

List Resources returns the available UI resources via listUiResources():

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: listUiResources(),
}));

(lines 14-17 of src/server.ts)

Read Resource loads UI-specific files by URI, throwing errors for unknown resources (lines 20-27). List Prompts currently returns an empty array (lines 31-36). These static handlers operate independently of the tool execution pipeline.

Handling Tool Call Requests

The core request processing logic resides in the CallToolRequestSchema handler (lines 44-57 of src/server.ts):

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const args = request.params.arguments;
  const isUiOriginCall = !!(args && typeof args === 'object' && (args as any).origin === 'ui');
  if (isUiOriginCall) {
    return runInUiOriginCallContext(() => handleCallToolRequest(request));
  }
  return handleCallToolRequest(request);
});

This handler detects UI-origin calls (where origin: 'ui') and wraps them in runInUiOriginCallContext, which suppresses telemetry tracking. All other calls proceed directly to handleCallToolRequest.

The Request Processing Pipeline

Inside handleCallToolRequest (starting line 60), the server executes a strict validation and dispatch sequence:

  1. Extract parameters: const { name, arguments: args } = request.params;
  2. Remote attribution: Detects _meta.remote and _meta.clientInfo to set currentCallIsRemote and currentRemoteClient (lines 70-92)
  3. Tool-specific telemetry: For example, logging set_config_value_key_name when processing the set_config_value tool (lines 99-102)
  4. History tracking: trackToolCall(name, args) persists the call to the tool-history store (lines 114-115)
  5. Dispatch: A switch (name) statement routes to concrete implementations like getConfig() or start_process (lines 120-130)

If any handler throws, the server captures the error via capture('server_request_error', ...) and returns a structured error response, ensuring the MCP client always receives a well-formed ServerResult.

Tool Call Schema Validation

All tool arguments are strictly validated using Zod schemas defined in src/tools/schemas.ts. Each schema describes the shape, defaults, and allowed values for a specific tool.

For example, the StartProcess tool schema (lines 27-35):

export const StartProcessArgsSchema = z.object({
  command: z.string(),
  timeout_ms: z.number(),
  shell: z.string().optional(),
  verbose_timing: z.boolean().optional(),
  origin: z.enum(['ui', 'llm']).optional(),
});

The server maintains a toolArgSchemas lookup map (lines 49-78) that associates each tool name with its validator:

export const toolArgSchemas: Record<string, z.ZodTypeAny> = {
  get_config: GetConfigArgsSchema,
  set_config_value: SetConfigValueArgsSchema,
  start_process: StartProcessArgsSchema,
  // ... all other tools
};

When processing a CallTool request, the server looks up the schema by name, parses request.params.arguments against it, and either returns a validation error or passes the parsed arguments to the tool implementation.

Remote Device Context and Telemetry Separation

The server supports remote device operation via the DC_REMOTE_DEVICE environment variable. When a tool call contains _meta.remote, the handler marks the call as remote using setCurrentCallIsRemote(true) and stores originating client information via setCurrentRemoteClient. This separation ensures that telemetry for remote clients does not contaminate the local device's currentClient state.

Complete Request Flow

The request lifecycle follows this architecture:


Client → (MCP) → Server
   │
   ├─ InitializeRequest → server sets clientInfo, returns capabilities
   ├─ ListResources/ReadResource/ListPrompts → static UI handlers
   └─ CallToolRequest
        │
        ├─ UI-origin? → runInUiOriginCallContext (telemetry suppressed)
        │
        └─ Remote? → setCurrentCallIsRemote / setCurrentRemoteClient
        │
        ├─ Validate args against toolArgSchemas
        ├─ Track tool call (toolHistory)
        └─ Dispatch via switch → concrete implementation
            → result (or captured error) returned to client

Summary

  • The server instantiates an MCP Server in src/server.ts with capabilities for tools, resources, prompts, and logging
  • Static handlers manage UI resources and prompts, while dynamic tool calls route through handleCallToolRequest
  • Zod schemas in src/tools/schemas.ts provide strict validation via the toolArgSchemas map
  • UI-origin calls (marked with origin: 'ui') execute inside runInUiOriginCallContext to suppress telemetry
  • Remote calls identified via _meta.remote trigger separate state tracking to prevent client information bleeding
  • All errors are captured and returned as well-formed ServerResult objects to maintain protocol compliance

Frequently Asked Questions

What is the Model Context Protocol (MCP) used in Desktop Commander?

The Model Context Protocol (MCP) is an open protocol that enables AI systems to interact with external tools and resources through a standardized interface. Desktop Commander implements an MCP server that exposes filesystem operations, process management, and configuration tools, allowing AI clients to discover and invoke these capabilities via JSON-RPC messages.

How does Desktop Commander validate tool arguments before execution?

The server validates all tool arguments against Zod schemas defined in src/tools/schemas.ts. Each tool has a corresponding entry in the toolArgSchemas record map. When a CallTool request arrives, the server looks up the schema by tool name and parses the arguments; if validation fails, it returns an error before the tool implementation executes.

What is the difference between UI-origin and LLM-origin tool calls?

UI-origin calls include origin: 'ui' in their arguments and are wrapped in runInUiOriginCallContext, which suppresses telemetry tracking to avoid logging internal UI operations. LLM-origin calls (or calls without an origin flag) proceed through standard telemetry and tracking via trackToolCall, allowing the server to maintain a history of AI-initiated actions for the get_recent_tool_calls feature.

How does the server handle errors from tool execution?

Errors are captured in the handleCallToolRequest function using capture('server_request_error', ...) and converted into structured error responses. This ensures that even when a tool handler throws, the MCP client receives a valid ServerResult object rather than an unhandled exception, maintaining protocol stability and providing actionable error information.

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 →