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

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 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 makes an immediate dispatch decision:

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
// 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. 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)
// 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:

  • 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
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):

  • 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
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):


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.

Constraints:

  • Text files only — throws on images
  • No status line — pure file content
  • Preserves exact line endings via splitLinesPreservingEndings()
// 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →