# How Desktop Commander MCP's readFile Modes Handle Different File Types

> Discover how Desktop Commander MCP's readFile modes automatically select URL or disk mode. Learn how it handles images, PDFs, Excel, and plain text for efficient data retrieval.

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

---

**`readFile()` automatically selects URL mode for remote resources or disk mode for local files, then delegates to type-specific handlers that return base64-encoded images, parsed PDFs, Excel sheet data, or plain text with optional line offsets.**

The **Desktop Commander MCP** server provides a unified `readFile()` API that abstracts away the complexity of reading diverse file formats. According to the [wonderwhy-er/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP) source code, the function branches into distinct **read file modes** based on the `isUrl` option and the target file's extension or MIME type. This article examines each mode's behavior with practical examples from the codebase.

---

## Primary Modes: URL vs. Disk

The entry point in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts) makes an immediate dispatch decision:

```ts
export async function readFile(
    filePath: string,
    options?: ReadOptions
): Promise<FileResult> {
    const { isUrl, offset, length, sheet, range } = options ?? {};
    return isUrl
        ? readFileFromUrl(filePath)                     // 🌐 URL mode
        : readFileFromDisk(filePath, { offset, length, sheet, range }); // 📂 Disk mode
}

```

Both modes return a standardized `FileResult` containing `content` (string or Buffer), `mimeType`, and optional `metadata` with type-specific flags.

---

## URL Mode: `readFileFromUrl()`

URL mode fetches remote content with a **30-second timeout** (`FILE_OPERATION_TIMEOUTS.URL_FETCH`) and classifies responses by MIME type.

### File Type Handling in URL Mode

| File Type | Detection | Output Format |
|-----------|-----------|---------------|
| **PDF** | `Content-Type: application/pdf` or `.pdf` extension | Parsed to markdown, text stored in `metadata.pages` |
| **Image** | `isImageFile(contentType)` returns true | Base64-encoded string with `metadata.isImage: true` |
| **Other** | Fallback to `text/plain` | Raw UTF-8 text with `metadata.isImage: false` |

```ts
// From src/tools/filesystem.ts
const isImage = isImageFile(contentType);
const isPdf   = isPdfFile(contentType) || url.toLowerCase().endsWith('.pdf');

if (isPdf) {
    const pdfResult = await parsePdfToMarkdown(url);
    return { content: '', mimeType: 'text/plain', metadata: { …pdfResult.metadata, isPdf: true } };
} else if (isImage) {
    const buffer = await response.arrayBuffer();
    const content = Buffer.from(buffer).toString('base64');
    return { content, mimeType: contentType, metadata: { isImage: true } };
} else {
    const content = await response.text();
    return { content, mimeType: contentType };
}

```

**Images** are fully loaded into memory and base64-encoded. **PDFs** trigger server-side parsing that extracts text per page. **All other content** streams as text.

---

## Disk Mode: `readFileFromDisk()` and Handler Architecture

Disk mode validates the path, then routes to a specialized handler via the factory in [`src/utils/files/factory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/factory.ts). Handlers implement the `FileHandler` interface and respect only the options relevant to their format.

### Handler Priority Order

The factory selects handlers in this precedence: **DOCX → PDF → Excel → Image → Binary → Text**.

---

### Text Files: `TextFileHandler`

**Extensions:** `.txt`, `.js`, `.json`, `.md`, and any non-binary, non-image file.

**Respected options:**
- **`offset`** — Starting line number (0-based)
- **`length`** — Number of lines to return (defaults to `fileReadLineLimit`, typically 1000)

```ts
// Example: Read lines 100-119 of a log file
const result = await readFile('server.log', { offset: 100, length: 20 });

```

Implementation in [`src/utils/files/text.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/text.ts):
- Reads file once under a **3-minute cancellable timeout**
- Uses `splitLinesPreservingEndings()` to maintain original line endings when slicing
- Returns `metadata.lineCount` and `metadata.isImage: false`

---

### Image Files: `ImageFileHandler`

**Extensions:** `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, etc.

**Behavior:**
- Reads complete binary into a Buffer
- Returns **base64-encoded string** (`content: string`)
- Sets `metadata.isImage: true`
- **Ignores** `offset`, `length`, `sheet`, `range` — images cannot be partially read as text

```ts
const img = await readFile('screenshot.png');
// img.content === 'iVBORw0KGgoAAAANSUhEUgAA...'
// img.metadata.isImage === true

```

---

### Binary Files: `BinaryFileHandler`

**Used for:** Compiled binaries, archives, or any unrecognized format.

**Behavior:**
- Returns raw `Buffer` in `content` (not a string)
- Sets `metadata.isBinary: true`
- No partial read support — entire file delivered

---

### Excel Files: `ExcelFileHandler`

**Formats:** `.xlsx`, `.xls`, `.xlsm`

**Respected options:**

| Option | Purpose |
|--------|---------|
| `sheet` | Worksheet name (string) or index (number) |
| `range` | Cell range like `"A1:C10"` or `"Sheet1!A1:C10"` |
| `offset`/`length` | Row-wise slicing applied after sheet/range selection |

**Implementation** ([`src/utils/files/excel.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/files/excel.ts)):
- Opens workbook with `xlsx` library
- Extracts specified sheet and range
- Returns **JSON-encoded 2D array** in `content`
- Provides `metadata.sheets` (available sheet names) and `metadata.isExcelFile`

```ts
const result = await readFile('sales.xlsx', {
    sheet: 'Q4',
    range: 'B5:D50',
    offset: 0,
    length: 10  // First 10 rows of B5:D50
});
const rows = JSON.parse(result.content);  // [['Jan', 1000, 1200], ...]

```

---

### PDF Files: `PdfFileHandler`

**Extension:** `.pdf`

**Behavior:**
- Delegates to `parsePdfToMarkdown()` (shared with URL mode)
- Returns empty `content` string
- Text stored in `metadata.pages` array
- Includes `metadata.isPdf`, `author`, `title`, `totalPages`

---

### DOCX Files: `DocxFileHandler`

**Extension:** `.docx`

**Behavior:**
- Extracts raw text using `docx` parsing utilities
- Returns plain text in `content`
- Sets `metadata.isDocx: true`

---

## Directory Handling: Graceful Fallback

If `readFileFromDisk()` receives a directory path, it does **not** throw `EISDIR`. Instead, it returns a synthetic text listing (lines 555-576 in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts)):

```

This is a directory, not a file. Use the list_directory tool instead of read_file for directories.

[DIR] subfolder
[FILE] file.ts
[FILE] package.json

```

This allows tools to gracefully handle mistaken directory paths.

---

## Internal Mode: `readFileInternal()`

Used by the **editor** for precise content preservation. Located at lines 893-936 in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts).

**Constraints:**
- **Text files only** — throws on images
- **No status line** — pure file content
- **Preserves exact line endings** via `splitLinesPreservingEndings()`

```ts
// For edit operations requiring byte-accurate reads
import { readFileInternal } from './tools/filesystem.js';

const exactContent = await readFileInternal('src/config.ts', 0, 500);

```

No Excel, image, or binary support — those require dedicated handlers.

---

## Summary

- **URL mode** fetches remote resources, auto-detects PDFs/images by MIME type, and returns base64 images or parsed PDF metadata
- **Disk mode** routes through a handler factory with priority: DOCX → PDF → Excel → Image → Binary → Text
- **Text and Excel handlers** respect `offset`/`length`/`sheet`/`range` for partial reads; other handlers ignore unsupported options
- **Images** always return base64 strings; **binary files** return raw Buffers; **PDFs** return metadata with page-wise text
- **Directories** produce synthetic listings instead of errors
- **`readFileInternal()`** provides editor-grade text reads with exact line-ending preservation

---

## Frequently Asked Questions

### Which `readFile` mode should I use for remote PDFs?

Use **URL mode** by setting `isUrl: true`. The function detects PDFs by `Content-Type` header or `.pdf` extension, then parses them server-side via `parsePdfToMarkdown()`. The textual content populates `metadata.pages` rather than `content`.

### Why does my image return a string instead of binary data?

Both URL and disk modes **base64-encode images** into strings. This ensures JSON-serializable responses across the MCP protocol. Check `metadata.isImage === true` to confirm, then decode the string with `Buffer.from(content, 'base64')` if you need raw bytes.

### Can I read a specific cell range from an Excel file on the web?

No — **range selection requires disk mode**. URL mode treats Excel files as raw text or binary (depending on server response). Download the file first, then use `readFile()` with `sheet` and `range` options for cell-level extraction.

### What happens if I pass line offsets to an image file?

**The options are silently ignored.** The `ImageFileHandler` does not implement partial reads — it returns the complete base64-encoded image regardless of `offset` or `length` parameters. Only `TextFileHandler` and `ExcelFileHandler` respect these slicing options.