PDF Manipulation System Architecture in Desktop Commander MCP: How Chrome Powers Markdown-to-PDF Conversion

Desktop Commander MCP uses a three-layer PDF architecture where Chrome serves as the rendering engine for Markdown-to-PDF conversion, while pure JavaScript libraries handle editing, image extraction, and PDF-to-Markdown extraction.

The Desktop Commander MCP repository treats PDF operations as first-class capabilities, implementing a modular system for editing documents, converting between formats, and extracting visual assets. This article breaks down the architecture, explains how each layer functions, and clarifies Chrome's specific role in the conversion pipeline.

Three-Layer Architecture Overview

The PDF manipulation system in wonderwhy-er/DesktopCommanderMCP separates concerns across three distinct layers:

Layer Responsibility Primary Module
PDF Editing Add, remove, or reorder pages in existing PDFs src/tools/pdf/manipulations.ts
Markdown ↔︎ PDF Conversion Render Markdown as PDF or extract Markdown from PDF src/tools/pdf/markdown.ts
Image Extraction Pull raster images from PDF pages src/tools/pdf/extract-images.ts

Each layer operates independently and can be composed together for complex workflows.

Layer 1: PDF Editing with pdf-lib

The editing layer uses pdf-lib, a pure-JavaScript PDF manipulation library, to modify document structure without external dependencies.

Key capabilities in src/tools/pdf/manipulations.ts:

  • PDFDocument.load() — parses PDF bytes into a mutable document object
  • pdfDoc.removePage() — deletes pages by index (supports negative offsets like -1 for last page)
  • Page insertion — either from another PDF buffer or generated from Markdown

The editPdf() function normalizes page indexes and returns the modified document as a Uint8Array:

import { editPdf } from './tools/pdf/manipulations.js';

const operations = [
  { type: 'delete', pageIndexes: [1, -1] },  // delete page 2 and last page
  {
    type: 'insert',
    pageIndex: 0,  // insert at beginning
    markdown: '# New Intro\nWelcome to the revised document',

  },
];

const editedPdf = await editPdf('original.pdf', operations);

When Markdown is supplied for insertion, editPdf() delegates to parseMarkdownToPdf() — the only layer that requires Chrome.

Layer 2: Markdown ↔︎ PDF Conversion — Where Chrome Fits

This is the only PDF manipulation layer that requires a browser. The src/tools/pdf/markdown.ts module implements bidirectional conversion using two distinct strategies:

Markdown → PDF: Chrome as Rendering Engine

The parseMarkdownToPdf() function transforms Markdown into PDF through HTML rendering:

  1. Resolves a Chrome executable via three-stage lookup
  2. Passes Markdown and Chrome path to md-to-pdf
  3. md-to-pdf launches Puppeteer with the specified executable
  4. Puppeteer renders Markdown as HTML and prints to PDF
import { parseMarkdownToPdf } from './tools/pdf/markdown.js';

const newPdfBuffer = await parseMarkdownToPdf('# Report\n\nExecutive summary here...');

await fs.writeFile('output.pdf', newPdfBuffer);

PDF → Markdown: Pure JavaScript Extraction

The reverse direction requires no browser. parsePdfToMarkdown() uses @opendocsg/pdf2md to parse PDF structure directly:

import { parsePdfToMarkdown } from './tools/pdf/markdown.js';

const { markdown } = await parsePdfToMarkdown('report.pdf', [1, 2]);  // pages 1-2 only

Chrome Discovery and Caching Mechanism

The src/tools/pdf/markdown.ts module implements sophisticated Chrome resolution to ensure headless rendering works across environments:

Three-Stage Lookup Priority

  1. Puppeteer cache — checks puppeteer-cache/chrome for previously downloaded Chrome-for-Testing builds
  2. System Chrome — searches common OS locations:
    • Windows: C:\Program Files\Google\Chrome\Application\chrome.exe
    • macOS: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    • Linux: /usr/bin/google-chrome, /usr/bin/chromium
  3. On-demand install — uses @puppeteer/browsers to download stable Chrome if no binary is found

Performance Optimization

The resolved path is stored in module-level variables (cachedChromePath and chromeCheckPromise) to eliminate repeated filesystem scans. Subsequent PDF conversions reuse the cached path immediately.

Error Handling

If Chrome cannot be located or launched, parseMarkdownToPdf() throws a descriptive error guiding users to install Chrome or Chromium.

Layer 3: Image Extraction with unpdf and sharp

The src/tools/pdf/extract-images.ts module extracts raster images from PDF pages without browser dependencies:

  • getDocumentProxy() from unpdf parses PDF structure
  • extractImages() retrieves raw image buffers per page
  • sharp (optional) compresses or converts images to JPEG/WebP
import { extractImagesFromPdf } from './tools/pdf/extract-images.js';
import { readFile } from 'fs/promises';

const pdfData = await readFile('presentation.pdf');
const images = await extractImagesFromPdf(pdfData, undefined, {
  format: 'webp',
  quality: 80
});

// images[pageNumber] → ImageInfo[] with base64-encoded data

Complete Round-Trip Example

Combine all three layers for complex document workflows:

import { editPdf } from './tools/pdf/manipulations.js';
import { parsePdfToMarkdown, parseMarkdownToPdf } from './tools/pdf/markdown.js';

// 1. Extract content from pages 1-2
const { markdown } = await parsePdfToMarkdown('source.pdf', [1, 2]);

// 2. Modify and enhance the Markdown
const enhanced = markdown.replace('# Old Title', '# Revised Title\n\n*Updated: 2024*');

// 3. Convert back to PDF (uses Chrome)
const newPages = await parseMarkdownToPdf(enhanced);

// 4. Insert into original document
const finalPdf = await editPdf('source.pdf', [
  { type: 'delete', pageIndexes: [1, 2] },  // remove original pages
  { type: 'insert', pageIndex: 1, pdfBytes: newPages }  // insert revised
]);

Summary

  • Three-layer architecture separates editing (pdf-lib), conversion (puppeteer + md-to-pdf / pdf2md), and image extraction (unpdf + sharp)
  • Chrome is required only for Markdown→PDF rendering, acting as the HTML-to-PDF conversion engine
  • Chrome discovery uses cached Puppeteer builds, system installations, or automatic downloads
  • All other operations run in pure JavaScript without browser overhead
  • Composable design allows mixing operations: edit PDF structure, convert content formats, and extract assets in single workflows

Frequently Asked Questions

Why does Desktop Commander MCP need Chrome for PDF conversion?

Chrome (or Chromium) serves as the rendering engine that converts Markdown→HTML→PDF. The md-to-pdf library uses Puppeteer to launch a headless Chrome instance, render the styled Markdown content, and print it to PDF. This ensures CSS support, web fonts, and modern HTML features render correctly. All other PDF operations—editing pages and extracting images—work without Chrome.

What happens if Chrome isn't installed on the system?

According to src/tools/pdf/markdown.ts, the system implements a three-stage fallback: first checking the Puppeteer cache for previous downloads, then scanning common OS paths for system Chrome, and finally auto-downloading Chrome via @puppeteer/browsers if no executable is found. Only if all three stages fail does the user receive an installation error.

Can I use this PDF system without installing Chrome locally?

Yes, partially. PDF→Markdown conversion (parsePdfToMarkdown) and all editing/extraction operations work without Chrome. Only Markdown→PDF conversion requires the browser. If you deploy to an environment without Chrome, you can pre-generate PDFs elsewhere or configure a remote Chrome instance via Puppeteer's browserWSEndpoint option.

How does image extraction preserve PDF quality?

The extractImagesFromPdf() function in src/tools/pdf/extract-images.ts uses unpdf to read embedded raster images without re-rendering, preserving original resolution. Optional sharp processing allows format conversion (JPEG, WebP, PNG) and quality adjustment, but the default behavior returns raw image data as stored in the PDF.

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 →