# How the File Preview UI Renders Markdown and Images in Claude Desktop with Desktop Commander MCP

> Discover how Claude Desktop's File Preview UI renders Markdown and images using Desktop Commander MCP. Learn about its custom pipeline and secure image handling.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-07-28

---

**The File Preview UI in Claude Desktop uses Desktop Commander MCP's modular framework to dispatch file types to specialized handlers, render Markdown through a custom markdown-it pipeline with syntax highlighting, and display local images securely via sandboxed blob URLs.**

Desktop Commander MCP powers the integrated file preview experience inside Claude Desktop, enabling users to view Markdown documents and embedded images without leaving the chat window. This article examines the exact rendering pipeline, from the initial file payload received in [`main.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/main.ts) to the final HTML injection and secure image resolution.

## File Preview Architecture and Dispatch Flow

When a user opens a file in Claude Desktop, the File Preview UI follows a strict dispatch pattern to determine how to handle the content.

### Entry Point and Payload Handling

The process begins in [`src/ui/file-preview/src/main.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/main.ts), which receives a payload containing the file path and raw content from the Claude Desktop host. This entry point initializes the preview environment and passes control to the file-type dispatcher.

### File Type Detection and Routing

The `getFileHandler` function 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) selects the appropriate handler based on file extension:

```typescript
// src/ui/file-preview/src/file-type-handlers.ts
export function getFileHandler(ext: string) {
  if (['.md', '.markdown'].includes(ext)) return markdownHandler;
  if (['.png', '.jpg', '.jpeg', '.gif', '.svg'].includes(ext)) return imageHandler;
  // …other handlers (PDF, plain text, etc.)
}

```

For Markdown files, the dispatcher returns `markdownHandler`, which constructs the **Markdown Controller**. For images, it returns `imageHandler`, which prepares the blob resolution pipeline.

## Markdown Rendering Pipeline

Once a Markdown file is detected, the system initializes a multi-stage rendering pipeline that converts raw text into safe, interactive HTML.

### The Markdown Controller and Editor

The **Markdown Controller** ([`src/ui/file-preview/src/markdown/controller.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/controller.ts)) orchestrates the UI components, creating a **Markdown Editor** instance ([`src/ui/file-preview/src/markdown/editor.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/editor.ts)) built on *tiptap* with the `tiptap-markdown` extension. This setup handles round-trip conversion between ProseMirror document nodes and raw Markdown source, while decorating links with `data-markdown-link` attributes for in-app editing.

### HTML Generation with markdown-it

The core rendering logic resides in [`src/ui/file-preview/src/components/markdown-renderer.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/components/markdown-renderer.ts). This module configures **markdown-it** with a custom highlighter and specialized renderers for headings and links:

```typescript
// src/ui/file-preview/src/components/markdown-renderer.ts
import { highlightSource } from './highlighting.js';
import { createMarkdownIt, prepareMarkdownSource, readHeadingProjection, type MarkdownToken } from '../markdown/parser.js';
import { createSlugTracker } from '../markdown/slugify.js';

const markdown = createMarkdownIt({
  // Syntax‑highlight each fenced code block
  highlight(code: string, language: string): string {
    const normalizedLanguage = (language || 'text').toLowerCase();
    const highlighted = highlightSource(code, normalizedLanguage);
    return `<pre class="code-viewer"><code class="hljs language-${normalizedLanguage}">${highlighted}</code></pre>`;
  },
});

export function renderMarkdown(content: string): string {
  // Prepare source → markdown‑it → HTML
  return markdown.render(prepareMarkdownSource(content), { nextSlug: createSlugTracker() });
}

```

The `renderMarkdown` function processes the source through `prepareMarkdownSource`, injects `id` and `data-heading-id` attributes for document outline navigation, and returns sanitized HTML ready for injection into the preview pane.

## Secure Image Loading in the Preview Pane

Handling local images requires special security considerations to prevent exposing the host file system to the webview.

### Blob URL Resolution

When markdown-it parses `![](path/to/img.png)` into an `<img>` tag, the **Image Preview** component ([`src/ui/file-preview/src/image-preview.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/image-preview.ts)) intercepts these elements. It reads the file bytes from the sandboxed filesystem, creates a `Blob`, and sets the `src` attribute to a `blob:` URL:

```typescript
// Assume an <img> element is already in the rendered HTML.
import { resolveImageBlob } from './image-preview.js';

async function fixImgSrc(img: HTMLImageElement, baseDir: string) {
  const filePath = new URL(img.src, `file://${baseDir}/`).pathname;
  const blob = await resolveImageBlob(filePath);
  img.src = URL.createObjectURL(blob);
}

```

This technique allows Claude Desktop to display local images without revealing absolute filesystem paths to the renderer process.

## Copy Rendered Content as Plain Text

The File Preview UI includes a **Copy** button that extracts clean text from the rendered HTML, stripping tags and decoding entities.

The `getRenderedMarkdownCopyText` function in [`src/ui/file-preview/src/markdown/preview.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/preview.ts) handles this transformation:

```typescript
// src/ui/file-preview/src/markdown/preview.ts
import { renderMarkdown } from '../components/markdown-renderer.js';

export function getRenderedMarkdownCopyText(content: string): string {
  const html = renderMarkdown(content);
  const normalizedHtml = html
    .replace(/<\s*br\s*\/?>/gi, '\n')
    .replace(/<\/p>/gi, '\n\n')
    .replace(/<\/h[1-6]>/gi, '\n\n')
    .replace(/<\/li>/gi, '\n')
    .replace(/<li>/gi, '- ')
    .replace(/<[^>]+>/g, '');
  return normalizedHtml
    .replace(/&nbsp;/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&#39;/g, "'")
    .replace(/&quot;/g, '"')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

```

This function converts `<br>` tags to newlines, paragraphs to double newlines, and list items to hyphen-prefixed lines, producing clipboard-friendly plain text that preserves the document structure.

## Practical Implementation Examples

Developers extending Desktop Commander MCP can leverage these core functions directly.

### Rendering a Markdown File Programmatically

To generate HTML from a file for custom display logic:

```typescript
import { renderMarkdown } from './components/markdown-renderer.js';
import { readFile } from '../utils/filesystem.js';   // MCP helper

async function previewMarkdown(filePath: string) {
  const raw = await readFile(filePath, 'utf‑8');
  const html = renderMarkdown(raw);
  document.getElementById('preview')!.innerHTML = html;
}

```

### Implementing the Copy-to-Clipboard Feature

To add copy functionality to a custom toolbar:

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

function copyRendered() {
  const content = editor.getValue();               // raw markdown from tiptap
  const plain = getRenderedMarkdownCopyText(content);
  navigator.clipboard.writeText(plain);
}

```

## Summary

The **File Preview UI** in Claude Desktop relies on Desktop Commander MCP's modular architecture to deliver secure, interactive document previews:

- **File payloads** enter through [`src/ui/file-preview/src/main.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/main.ts) and route via [`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 specialized handlers for Markdown or images.
- **Markdown processing** uses `renderMarkdown` in [`src/ui/file-preview/src/components/markdown-renderer.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/components/markdown-renderer.ts), which runs markdown-it with custom syntax highlighting via `highlightSource` and enriches headings/links with navigation IDs.
- **UI orchestration** is managed by the `MarkdownController` in [`src/ui/file-preview/src/markdown/controller.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/controller.ts), which wires the tiptap-based editor surface, toolbar, and document outline.
- **Image security** is maintained by [`src/ui/file-preview/src/image-preview.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/image-preview.ts), converting filesystem paths to sandboxed `blob:` URLs before display.
- **Text extraction** is handled by `getRenderedMarkdownCopyText` in [`src/ui/file-preview/src/markdown/preview.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/preview.ts), enabling users to copy rendered content as clean plain text.

## Frequently Asked Questions

### How does Claude Desktop handle image security in the File Preview UI?

The UI converts local filesystem paths to `blob:` URLs using [`src/ui/file-preview/src/image-preview.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/image-preview.ts). This approach allows the webview to display image content without exposing the absolute host file system path, maintaining strict sandbox boundaries while preserving visual fidelity.

### What library does Desktop Commander MCP use for Markdown parsing?

It uses **markdown-it**, configured in [`src/ui/file-preview/src/components/markdown-renderer.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/components/markdown-renderer.ts). The implementation extends markdown-it with a custom `highlightSource` function for syntax highlighting and specialized renderers that inject `data-heading-id` and `data-markdown-link` attributes to support navigation and link editing features.

### Can users copy formatted content from the preview as plain text?

Yes. The `getRenderedMarkdownCopyText` function in [`src/ui/file-preview/src/markdown/preview.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/preview.ts) processes the rendered HTML, replacing tags with appropriate whitespace (such as converting `</p>` to double newlines), stripping remaining HTML, and decoding entities like `&amp;` and `&lt;` to produce clipboard-ready plain text.

### How does the UI determine which preview handler to use?

The `getFileHandler` function 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) inspects the file extension. It returns `markdownHandler` for `.md` and `.markdown` files, `imageHandler` for `.png`, `.jpg`, `.jpeg`, `.gif`, and `.svg` files, and appropriate handlers for other formats like PDF or plain text.