How Desktop Commander MCP Handles Tool Registration and Routing
Desktop Commander MCP implements a declarative JSON-RPC pipeline where tools are registered in src/server.ts as a master allTools array with Zod schemas, then routed through a CallToolRequestSchema handler that validates arguments and dispatches to concrete implementations in src/tools/.
Desktop Commander MCP is a Model Context Protocol server that exposes filesystem and process management capabilities to AI clients. Understanding how it handles tool registration and routing reveals the architecture behind its robust JSON-RPC interface. This guide examines the actual source code from wonderwhy-er/DesktopCommanderMCP to explain the three-stage lifecycle from schema definition to execution.
Tool Registration and Schema Definition
Defining Tools with Zod Schemas
Every tool in Desktop Commander MCP starts with a strict Zod schema that defines its arguments. These schemas live in src/tools/schemas.ts and provide runtime validation alongside TypeScript type safety. For example, a directory listing tool defines its parameters as follows:
// src/tools/schemas.ts
export const ListDirectoryArgsSchema = z.object({
path: z.string(),
depth: z.number().int().min(1).max(5).optional(),
});
The server converts these Zod schemas to JSON-Schema using zodToJsonSchema when exposing tools to clients. This ensures that any MCP-compatible client can understand the expected parameters without executing TypeScript.
Building the Master Tool Registry
When the server initializes, it constructs an allTools array in src/server.ts (approximately lines 3005–3120) that aggregates every available tool. Each entry includes the tool name, description, input schema, UI metadata, and annotations:
// src/server.ts
{
name: "list_directory",
description: `Get a detailed listing of all files and directories in a specified path…`,
inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews),
annotations: { title: "List Directory Contents", readOnlyHint: true },
},
The server exposes this registry through the list_tools JSON-RPC method. The list is dynamically filtered based on the current client context—for instance, the Desktop Commander app hides meta-tools from the standard view while keeping them available for internal use.
JSON-RPC Routing and Request Handling
Handling the tools/call Method
Incoming execution requests arrive via the tools/call JSON-RPC method. The server registers a handler for CallToolRequestSchema in src/server.ts (approximately lines 1500–1700) that extracts the tool name and arguments from the request params:
// src/server.ts
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args } = req.params;
await trackToolCall(name, args); // Logging
setCurrentCallIsRemote(!!req._meta?.remote); // Context tracking
// Dispatch to concrete implementation
switch (name) {
case "read_file": return server_read_file(args);
case "write_file": return server_write_file(args);
case "list_directory": return server_list_directory(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
});
The handler validates the payload against the tool's Zod schema before dispatching. This centralizes error handling and ensures that only type-safe arguments reach the implementation functions.
Remote Context and Call Tracking
Before execution, the router captures telemetry through trackToolCall in src/utils/trackTools.ts. This function persists a log line for every invocation, enabling audit trails and usage analytics. The handler also determines execution context via setCurrentCallIsRemote, which checks req._meta?.remote to distinguish between local UI calls and remote device requests.
Concrete Tool Implementations
Each tool's business logic resides in modular files under src/tools/. For example, src/tools/filesystem.ts exports server_read_file and server_write_file, while src/tools/improved-process-tools.ts handles server_start_process and interact_with_process.
These implementation functions receive validated arguments and return results that the JSON-RPC handler wraps into the response payload. Error handling occurs through capture_call_tool in src/utils/capture.ts, which centralizes telemetry emission and error reporting without polluting the business logic.
UI Fallback Bridge
When the primary MCP host cannot execute a tool directly, Desktop Commander MCP provides a JSON-RPC bridge in src/ui/shared/tool-bridge.ts (lines 69–95). This fallback mechanism posts a tools/call message to the parent frame and awaits the response:
// src/ui/shared/tool-bridge.ts
export function createToolBridge(options = {}) {
const requestId = generateId();
parent.postMessage(
{
jsonrpc: "2.0",
id: requestId,
method: "tools/call",
params: { name, arguments: args }
},
targetOrigin
);
// Wait for response or timeout...
}
This bridge ensures that tools remain accessible even when running in sandboxed UI contexts or across iframe boundaries.
Summary
- Schema-first registration: Tools are defined via Zod schemas in
src/tools/schemas.tsand registered in theallToolsarray withinsrc/server.ts, exposing them through thelist_toolsJSON-RPC method. - Centralized routing: The
CallToolRequestSchemahandler insrc/server.ts(lines 1500–1700) validates incomingtools/callrequests and dispatches to concrete implementations based on the tool name. - Execution context: Each call is logged via
trackToolCalland tagged with remote status throughsetCurrentCallIsRemotebefore reaching the implementation insrc/tools/modules. - Fallback support: The
src/ui/shared/tool-bridge.tsprovides a JSON-RPC bridge for environments where direct tool execution is unavailable.
Frequently Asked Questions
How does Desktop Commander MCP validate tool arguments?
The server validates tool arguments using Zod schemas defined in src/tools/schemas.ts. When a tools/call request arrives, the CallToolRequestSchema handler validates the payload against the specific tool's schema before dispatching to the implementation function. This ensures type safety and provides clear error messages for malformed requests.
What happens if a tool is called that doesn't exist?
If the CallToolRequestSchema handler in src/server.ts receives a request for an unknown tool name, it throws an Error with the message Unknown tool: ${name}. This error propagates through the JSON-RPC response, allowing the client to handle the failure gracefully without crashing the server process.
Where are tool usage statistics stored?
Tool usage statistics are persisted through trackToolCall in src/utils/trackTools.ts, which writes a log line for every invocation. Additionally, historical tool calls are stored in src/utils/toolHistory.ts for recent history display in the UI. These utilities enable both telemetry analysis and debugging capabilities.
Can tools be called remotely, and how does the server distinguish remote from local calls?
Yes, tools support remote execution. The server distinguishes contexts by checking req._meta?.remote in the CallToolRequestSchema handler and setting the state via setCurrentCallIsRemote. This flag allows implementations to adjust behavior—for example, applying stricter security policies or different path resolution rules—when executing commands from remote devices versus the local Desktop Commander UI.
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 →