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

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 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, 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 selects the appropriate handler based on file extension:

// 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) orchestrates the UI components, creating a Markdown Editor instance (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. This module configures markdown-it with a custom highlighter and specialized renderers for headings and links:

// 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) intercepts these elements. It reads the file bytes from the sandboxed filesystem, creates a Blob, and sets the src attribute to a blob: URL:

// 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 handles this transformation:

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

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:

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:

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. 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. 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 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 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.

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 →