# How to Extend the DesktopCommander MCP Server with Custom Tools and Schemas

> Extend DesktopCommander MCP server with custom tools and schemas. Follow simple steps: define Zod schemas, register them, implement handlers, and wire requests in server.ts.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: how-to-guide
- Published: 2026-07-25

---

**To extend the DesktopCommander MCP server with custom tools and schemas, define a Zod validation schema in [`src/tools/schemas.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/schemas.ts) and export a new schema object.

```typescript
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)](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.

```typescript
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/`](https://github.com/wonderwhy-er/DesktopCommanderMCP/tree/main/src/tools) (e.g., [`src/tools/my-custom-tool.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/my-custom-tool.ts)). The handler receives a `CallToolRequest` and must return an MCP-compliant result object.

```typescript
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)](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.

```typescript
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)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts) if the tool should be hidden from specific MCP clients.

```typescript
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

```typescript
// src/tools/schemas.ts
export const SayHelloArgsSchema = z.object({
  name: z.string().optional().default('World'),
});

```

### Register the Schema

```typescript
// src/tools/schemas.ts (bottom of file)
export const toolArgSchemas: Record<string, z.ZodTypeAny> = {
  // ... existing entries
  say_hello: SayHelloArgsSchema,
};

```

### Implement the Handler

```typescript
// 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

```typescript
// 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)](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 `toolArgSchemas` record to make it discoverable by the validation layer.
- **Implementation**: Create async handler functions under [`src/tools/`](https://github.com/wonderwhy-er/DesktopCommanderMCP/tree/main/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)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts) and add cases to `handleCallToolRequest` to route requests.
- **Optional Filtering**: Use `shouldIncludeTool` to control client-specific visibility and `buildUiToolMeta` for 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)](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)](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)](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)](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.