How to Extend the DesktopCommander MCP Server with Custom Tools and Schemas
To extend the DesktopCommander MCP server with custom tools and schemas, define a Zod validation schema in src/tools/schemas.ts, register it in the toolArgSchemas dispatcher map, implement the handler under src/tools/, and wire the request handler into the handleCallToolRequest function in src/server.ts.
DesktopCommanderMCP is a Model Context Protocol (MCP) implementation that exposes filesystem and shell utilities to LLM clients. Extending the server requires following a modular, type-safe pattern where Zod schemas validate incoming JSON-RPC parameters before the dispatcher routes calls to your implementation.
Step 1: Define the Argument Schema
Every tool requires a Zod schema that describes its expected parameters. Open [src/tools/schemas.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/schemas.ts) and export a new schema object.
import { z } from 'zod';
export const MyCustomToolArgsSchema = z.object({
targetPath: z.string().describe('Absolute path to the target file'),
overwrite: z.boolean().optional().default(false),
});
This schema automatically generates TypeScript types and JSON-Schema metadata for the MCP client.
Step 2: Register the Schema in toolArgSchemas
At the bottom of [src/tools/schemas.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/schemas.ts), the toolArgSchemas record maps tool names to their validators. Add your entry to expose the tool to the server's validation layer.
export const toolArgSchemas: Record<string, z.ZodTypeAny> = {
read_file: ReadFileArgsSchema,
edit_block: EditBlockArgsSchema,
// ... existing tools
my_custom_tool: MyCustomToolArgsSchema,
};
Step 3: Implement the Tool Handler
Create a new file under src/tools/ (e.g., src/tools/my-custom-tool.ts). The handler receives a CallToolRequest and must return an MCP-compliant result object.
import type { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
export async function myCustomTool(request: CallToolRequest) {
// Arguments are pre-validated against MyCustomToolArgsSchema by the server
const args = request.params as { targetPath: string; overwrite?: boolean };
// Implementation logic here
const result = await performOperation(args.targetPath, args.overwrite);
return {
content: [{ type: 'text', text: JSON.stringify(result) }],
};
}
Step 4: Wire the Handler into the Server
In [src/server.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts), locate the handleCallToolRequest function (around line 1250). Import your handler and add a case to the dispatch logic.
import { myCustomTool } from './tools/my-custom-tool.js';
// Inside handleCallToolRequest...
case 'my_custom_tool':
return myCustomTool(request);
The server validates arguments using toolArgSchemas before reaching this switch, ensuring type safety.
Step 5: Configure UI Visibility and Filtering
If your tool should appear in the DesktopCommander UI, ensure it is included in the ListTools response with appropriate metadata. The server uses buildUiToolMeta to annotate tools with UI hints.
Additionally, modify shouldIncludeTool in [src/server.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts) if the tool should be hidden from specific MCP clients.
function shouldIncludeTool(toolName: string, clientInfo: ClientInfo): boolean {
if (toolName === 'my_custom_tool' && clientInfo.name === 'restricted-client') {
return false;
}
return true;
}
Complete Working Example: Building a "say_hello" Tool
The following example adds a simple greeting tool end-to-end.
Define the Schema
// src/tools/schemas.ts
export const SayHelloArgsSchema = z.object({
name: z.string().optional().default('World'),
});
Register the Schema
// src/tools/schemas.ts (bottom of file)
export const toolArgSchemas: Record<string, z.ZodTypeAny> = {
// ... existing entries
say_hello: SayHelloArgsSchema,
};
Implement the Handler
// src/tools/say-hello.ts
import type { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
export async function sayHello(request: CallToolRequest) {
const { name } = request.params as { name?: string };
return {
content: [{ type: 'text', text: `Hello, ${name}!` }],
};
}
Register in the Server
// src/server.ts
import { sayHello } from './tools/say-hello.js';
// Inside handleCallToolRequest...
case 'say_hello':
return sayHello(request);
Summary
- Schema Definition: Add Zod schemas to [
src/tools/schemas.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/schemas.ts) to enforce type safety and generate JSON-Schema for clients. - Registration: Export the schema in the
toolArgSchemasrecord to make it discoverable by the validation layer. - Implementation: Create async handler functions under
src/tools/that return MCP-compliant content objects. - Dispatch: Import handlers into [
src/server.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts) and add cases tohandleCallToolRequestto route requests. - Optional Filtering: Use
shouldIncludeToolto control client-specific visibility andbuildUiToolMetafor UI integration.
Frequently Asked Questions
How does the server validate tool arguments before execution?
The MCP SDK uses the toolArgSchemas map exported from [src/tools/schemas.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/schemas.ts). When handleCallToolRequest receives a CallTool request, it looks up the tool name in this record and validates the params object against the Zod schema before dispatching to your handler, ensuring only well-formed data reaches your implementation.
Can I reuse existing schemas when building custom tools?
Yes. Import and extend existing schemas from [src/tools/schemas.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/schemas.ts) using Zod's .extend() or .merge() methods. This is useful when creating variants of file operations that share base arguments like file_path or encoding with the existing ReadFileArgsSchema.
Where should I add validation logic for unsupported parameters?
The project includes a utility in [src/utils/unsupportedParams.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/unsupportedParams.ts) that detects extra parameters not defined in the schema. Import warnUnsupportedParams in your handler to log or reject unexpected fields beyond the standard Zod validation.
Do I need to rebuild the project after adding a new tool?
Yes. After creating new TypeScript files or modifying [src/server.ts](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts), run npm run build to compile the changes into the dist/ directory. The compiled JavaScript is what the MCP client executes when launching the server.
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 →