How UI Tool Metadata Enables Rich File Previews in Claude Desktop

Claude Desktop renders rich file previews by coupling UI tool metadata with type-specific payload data, allowing the interface to dynamically switch between image, PDF, and text renderers based on file content.

The DesktopCommanderMCP server powers this experience through a two-layer metadata system: tool-level metadata tells Claude Desktop which UI widget to load, while file-type metadata embedded in each response determines how that widget renders the content. This architecture enables context-aware previews without hardcoding file handlers in the client.

Understanding the Two-Layer Metadata Architecture

Layer 1: Tool Metadata Defines the UI Widget

Tool metadata lives in src/ui/contracts.ts and provides the bridge between MCP tools and their corresponding UI components.

The FILE_PREVIEW_RESOURCE_URI constant identifies the file-preview widget:

// src/ui/contracts.ts
export const FILE_PREVIEW_RESOURCE_URI = "file-preview";

The UiToolMeta type and buildUiToolMeta helper construct the metadata object:

// src/ui/contracts.ts
export type UiToolMeta = Record<string, unknown>;

export function buildUiToolMeta(
  resourceUri: string,
  showMcpUiPreviews: boolean,
  shouldShowUI: boolean
): UiToolMeta {
  return {
    _meta: {
      resourceUri,
      showMcpUiPreviews,
      shouldShowUI,
    },
  };
}

When registering the read_file tool in src/server.ts, the server attaches this metadata at lines 418, 486, 607, and 867:

// src/server.ts (excerpt)
{
  name: 'read_file',
  description: 'Read a file from the filesystem',
  // ... other tool configuration
  _meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews),
}

This _meta field signals to Claude Desktop: "Load the file-preview resource when this tool returns content."

Layer 2: File-Type Metadata Drives Rendering Decisions

While tool metadata selects the widget, file-type metadata determines what that widget displays. File handlers in src/utils/files/*.ts extract this metadata during processing.

PDF handling (src/utils/files/pdf.ts, lines 56-71):

const pdfResult = await parsePdfToMarkdown(path, { offset: 0, length: 0 });

return {
  content: '',
  mimeType: 'application/pdf',
  metadata: {
    author: pdfResult.metadata.author,
    title: pdfResult.metadata.title,
    totalPages: pdfResult.metadata.totalPages,
    isPdf: true,
    pages: pdfResult.pages,  // rendered markdown per page
  },
};

Image handling adds isImage: true. Plain text handlers may include lineCount. Each handler contributes domain-specific fields that the UI consumes.

How the File Preview UI Consumes Metadata

The file-preview widget (src/ui/file-preview/src/app.ts) receives a RenderPayload containing both content and metadata. It delegates rendering decisions to renderPayloadBody in src/ui/file-preview/src/file-type-handlers.ts (lines 1-30):

// src/ui/file-preview/src/file-type-handlers.ts
export function renderPayloadBody(payload: RenderPayload) {
  const { metadata } = payload;

  if (metadata?.isImage) {
    return `<img src="${payload.content}" alt="Image preview"/>`;
  }

  if (metadata?.isPdf) {
    return renderPdfViewer(metadata);
  }

  return renderMarkdown(payload.content);
}

The PDF renderer uses the structured metadata to build a complete viewer:

function renderPdfViewer(metadata: PdfMetadata) {
  return `
    <div class="pdf-header">
      <h2>${metadata.title || 'Untitled'}</h2>
      <span>By ${metadata.author || 'Unknown'} • ${metadata.totalPages} pages</span>
    </div>
    <div class="pdf-pages">
      ${metadata.pages.map((page, i) => `
        <div class="page" data-page="${i + 1}">${page}</div>
      `).join('')}
    </div>
  `;
}

Complete Implementation Flow

  1. Tool registration — Server attaches buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews) to read_file

  2. File readread_file executes and routes to type-specific handler in src/utils/files/

  3. Metadata extraction — Handler returns { content, mimeType, metadata } with type flags (isPdf, isImage, etc.)

  4. UI routing — Claude Desktop sees _meta.resourceUri: "file-preview" and loads that widget

  5. Conditional renderingfile-type-handlers.ts inspects metadata flags and renders appropriate component

This separation of concerns allows new file types to add support without modifying the UI codebase—only the server-side handler and its returned metadata shape need changes.

Summary

  • UI tool metadata in src/ui/contracts.ts and src/server.ts binds MCP tools to specific UI widgets via _meta fields
  • buildUiToolMeta constructs metadata objects that specify which resource URI to load and whether to show the UI
  • File-type handlers in src/utils/files/*.ts return domain-specific metadata (isPdf, isImage, author, totalPages) alongside content
  • file-type-handlers.ts routes to appropriate renderers by inspecting metadata flags, enabling extensible preview types without UI changes
  • Claude Desktop combines these layers to show rich, context-aware file previews without embedded file logic

Frequently Asked Questions

What is UI tool metadata in DesktopCommanderMCP?

UI tool metadata is a structured object attached to MCP tool definitions that tells Claude Desktop which UI component to render when that tool produces output. According to the DesktopCommanderMCP source code, this metadata is created by buildUiToolMeta in src/ui/contracts.ts and attached via the _meta field during tool registration in src/server.ts.

How does the file preview UI know which renderer to use?

The file preview UI examines the metadata field of the payload returned by the server. In src/ui/file-preview/src/file-type-handlers.ts, the renderPayloadBody function checks for boolean flags like isImage or isPdf, then branches to the appropriate renderer. This design keeps rendering logic decoupled from file detection logic.

Can new file types be supported without modifying the UI code?

Yes. Adding support for a new file type requires only: (1) creating a handler in src/utils/files/ that returns appropriate metadata flags and content, and (2) adding a corresponding branch in src/ui/file-preview/src/file-type-handlers.ts. The UI widget itself remains generic and data-driven.

Where is the PDF metadata like author and title extracted?

PDF metadata extraction occurs in src/utils/files/pdf.ts (lines 56-71). The handler parses the PDF, extracts document properties including author, title, and totalPages, and returns these in the metadata object alongside rendered page content. The UI then consumes these fields to populate the PDF viewer header.

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 →