# How to Build Custom File Handlers for Unsupported File Types in Desktop Commander MCP

> Learn to build custom file handlers for unsupported file types in Desktop Commander MCP. Extend functionality by implementing the FileHandler interface and registering your custom handler.

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

---

**You can extend Desktop Commander MCP to support any file type by implementing the `FileHandler` interface defined in [`src/utils/files/base.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/base.ts), registering your handler in the factory at [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts), and optionally adding a UI preview component in [`src/ui/file-preview/src/file-type-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/file-type-handlers.ts).**

Desktop Commander MCP processes files through a plugin-style handler system that abstracts all file-type-specific logic behind a common interface. This architecture means that adding support for new formats—whether proprietary data files, custom configuration formats, or specialized media types—requires no changes to the core filesystem tools in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts) or editing logic in [`src/tools/edit.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/edit.ts). Instead, you implement a single class and hook it into the factory pattern.

## Understanding the FileHandler Interface

All file handlers must implement the `FileHandler` interface exported from [`src/utils/files/base.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/base.ts). This contract ensures that every handler can read, write, and provide metadata for its supported file types.

```typescript
export interface FileHandler {
    read(path: string, options?: ReadOptions): Promise<FileResult>;
    write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise<void>;
    getInfo(path: string): Promise<FileInfo>;
    canHandle(path: string): boolean | Promise<boolean>;
    editRange?(path: string, range: string, content: any, options?: Record<string, any>): Promise<EditResult>;
}

```

The `canHandle()` method determines whether a handler accepts a given file path, typically by checking file extensions or performing content-based detection. The `read()` and `write()` methods handle the actual I/O operations, while `getInfo()` returns filesystem metadata. The optional `editRange()` method enables partial file editing capabilities for supported formats.

## The Factory Pattern and Handler Priority

The [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts) file contains a factory function `getFileHandler()` that lazily instantiates handler singletons and selects the appropriate one for each file operation. Handlers are checked in a fixed priority order, allowing specific handlers to take precedence over generic fallbacks like the text handler.

When you add a custom handler, you must insert it into this priority chain. The factory uses lazy initialization functions (e.g., `getImageHandler()`, `getExcelHandler()`) to avoid instantiating handlers until they are first needed, improving startup performance.

## Step-by-Step: Creating a Custom Handler

Follow these four steps to add support for a new file type. The example below demonstrates adding a **Markdown** handler for `.md` files.

### Step 1: Implement the Handler Class

Create a new file at [`src/utils/files/markdown.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/markdown.ts) that implements the `FileHandler` interface:

```typescript
import fs from "fs/promises";
import { FileHandler, ReadOptions, FileResult, FileInfo } from './base.js';

export class MarkdownFileHandler implements FileHandler {
    private static readonly EXT = ['.md'];
    private static readonly MIME = 'text/markdown';

    canHandle(path: string): boolean {
        const lower = path.toLowerCase();
        return MarkdownFileHandler.EXT.some(ext => lower.endsWith(ext));
    }

    async read(path: string, options?: ReadOptions): Promise<FileResult> {
        const content = await fs.readFile(path, { encoding: 'utf8', signal: options?.signal });
        return { content, mimeType: MarkdownFileHandler.MIME, metadata: {} };
    }

    async write(path: string, content: string, mode: 'rewrite' | 'append' = 'rewrite'): Promise<void> {
        if (mode === 'append') await fs.appendFile(path, content);
        else await fs.writeFile(path, content);
    }

    async getInfo(path: string): Promise<FileInfo> {
        const stats = await fs.stat(path);
        return {
            size: stats.size,
            created: stats.birthtime,
            modified: stats.mtime,
            accessed: stats.atime,
            isDirectory: stats.isDirectory(),
            isFile: stats.isFile(),
            permissions: stats.mode.toString(8).slice(-3),
            fileType: 'text',
            metadata: {}
        };
    }
}

```

### Step 2: Export from the Index

Expose your handler from [`src/utils/files/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/index.ts) so the factory can import it:

```typescript
export { getFileHandler, isExcelFile, isImageFile } from './factory.js';
export { MarkdownFileHandler } from './markdown.js';

```

### Step 3: Register in the Factory

Modify [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts) to add lazy initialization and insert your handler into the priority chain:

```typescript
import { MarkdownFileHandler } from './markdown.js';

let markdownHandler: MarkdownFileHandler | null = null;
function getMarkdownHandler(): MarkdownFileHandler {
    if (!markdownHandler) markdownHandler = new MarkdownFileHandler();
    return markdownHandler;
}

// Inside getFileHandler() - insert before generic text/binary handlers
if (getMarkdownHandler().canHandle(filePath)) {
    return getMarkdownHandler();
}

```

### Step 4: Add UI Preview Support (Optional)

To enable custom preview panels for your file type, register a handler in [`src/ui/file-preview/src/file-type-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/file-type-handlers.ts):

```typescript
markdown: {
    getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
    renderBody: ({ payload }) => {
        return renderHtmlPreview(payload.content, 'full');
    },
},

```

## Complete Example: YAML File Handler

Here is a minimal, production-ready handler for YAML files that follows the same pattern:

**File:** [`src/utils/files/yaml.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/yaml.ts)

```typescript
import fs from "fs/promises";
import { FileHandler, ReadOptions, FileResult, FileInfo } from "./base.js";

export class YamlFileHandler implements FileHandler {
    private static readonly EXT = [".yaml", ".yml"];
    private static readonly MIME = "application/x-yaml";

    canHandle(p: string): boolean {
        const low = p.toLowerCase();
        return YamlFileHandler.EXT.some(e => low.endsWith(e));
    }

    async read(p: string, opts?: ReadOptions): Promise<FileResult> {
        const txt = await fs.readFile(p, { encoding: "utf8", signal: opts?.signal });
        return { content: txt, mimeType: YamlFileHandler.MIME, metadata: {} };
    }

    async write(p: string, cnt: string, mode: "rewrite" | "append" = "rewrite"): Promise<void> {
        if (mode === "append") await fs.appendFile(p, cnt);
        else await fs.writeFile(p, cnt);
    }

    async getInfo(p: string): Promise<FileInfo> {
        const s = await fs.stat(p);
        return {
            size: s.size,
            created: s.birthtime,
            modified: s.mtime,
            accessed: s.atime,
            isDirectory: s.isDirectory(),
            isFile: s.isFile(),
            permissions: s.mode.toString(8).slice(-3),
            fileType: "text",
            metadata: {}
        };
    }
}

```

**Registration in** [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts)**:**

```typescript
import { YamlFileHandler } from "./yaml.js";

let yamlHandler: YamlFileHandler | null = null;
function getYamlHandler(): YamlFileHandler {
    if (!yamlHandler) yamlHandler = new YamlFileHandler();
    return yamlHandler;
}

// Inside getFileHandler()
if (getYamlHandler().canHandle(filePath)) {
    return getYamlHandler();
}

```

**UI Preview in** [`src/ui/file-preview/src/file-type-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/file-type-handlers.ts)**:**

```typescript
yaml: {
    getCapabilities: (p) => buildPreviewCapabilities(p, true),
    renderBody: ({ payload }) => ({
        notice: "",
        html: `<pre class="code-viewer"><code class="hljs language-yaml">${escapeHtml(payload.content)}</code></pre>`
    })
},

```

## Summary

- **Interface-driven design:** All handlers implement `FileHandler` from [`src/utils/files/base.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/base.ts), ensuring type safety and consistent behavior across read, write, and metadata operations.
- **Factory registration:** Add your handler to [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts) using lazy singleton initialization and insert it into the priority chain before generic fallbacks.
- **Zero core changes:** Once registered, existing tools in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts) and [`src/tools/edit.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/edit.ts) automatically work with your new file type through the abstract interface.
- **Optional UI support:** Extend [`src/ui/file-preview/src/file-type-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/file-type-handlers.ts) to provide custom preview renderers for your file type.

## Frequently Asked Questions

### What methods are required when implementing a custom file handler?

You must implement `read()`, `write()`, `getInfo()`, and `canHandle()` from the `FileHandler` interface in [`src/utils/files/base.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/base.ts). The `editRange()` method is optional and only needed if your file type supports partial content editing. All methods must handle their own error conditions and return properly typed promises according to the `FileResult` and `FileInfo` interfaces.

### How does Desktop Commander decide which handler to use for a file?

The `getFileHandler()` function in [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts) checks handlers in a fixed priority order by calling each handler's `canHandle()` method until one returns `true`. You should register specific handlers (like those for `.yaml` or `.md`) before generic handlers (like the text or binary fallbacks) to ensure proper type detection.

### Can I add support for file types that don't use standard extensions?

Yes. While the examples show extension-based detection using `path.toLowerCase().endsWith()`, you can implement any logic inside `canHandle()`. For content-based detection, make `canHandle()` asynchronous and read the file header or perform content sniffing to determine compatibility. The factory supports both synchronous and asynchronous `canHandle()` implementations.

### Do I need to modify the MCP tool definitions to use a new handler?

No. The filesystem tools in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts) and editing tools in [`src/tools/edit.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/edit.ts) interact exclusively with the `FileHandler` abstraction. Once your handler is registered in the factory, these tools automatically route file operations to your implementation without requiring changes to their code or the MCP server configuration.