How DOCX Editing Works with XML Manipulation in Desktop Commander MCP
Desktop Commander MCP enables surgical DOCX editing by treating Word documents as ZIP archives containing XML parts, allowing precise find-and-replace operations on pretty-printed XML fragments while preserving formatting, styles, and embedded media.
Desktop Commander MCP provides a powerful mechanism for programmatic DOCX editing through direct XML manipulation. Unlike traditional document generation libraries that rebuild files from scratch, this approach treats .docx files as ZIP archives containing XML parts, enabling precise surgical modifications. The implementation centers on the DocxFileHandler class in src/utils/files/docx.ts, which exposes three core operations: reading document outlines and raw XML, performing targeted edits on specific XML fragments, and creating new documents from plain text.
Understanding the DOCX File Structure
Every .docx file is essentially a ZIP archive containing XML parts like word/document.xml, headers, footers, and relationship files. The DocxFileHandler extracts these components and provides two reading modes: an outline mode (default) that returns a human-readable summary of paragraphs, tables, and images, and a raw XML mode that exposes the underlying markup for direct manipulation.
The outline extraction logic resides in extractOutline() (lines 25-34), which parses <w:p>, <w:tbl>, and other WordprocessingML elements to generate a structural summary. When you need to inspect the actual markup, prettyPrintXml() (lines 31-52) formats the compact XML into readable, indented lines with line numbers for precise targeting.
Core Operations in DocxFileHandler
Reading DOCX Files (Outline vs Raw XML)
The handler distinguishes between high-level structure inspection and low-level markup access using the offset parameter:
- Outline mode (
offset = 0): Returns a summary showing paragraph count, table structure, and image placeholders - Raw XML mode (
offset ≠ 0): Returns pretty-printed XML starting at the specified line number
This dual-mode approach allows you to first locate content via the outline, then retrieve the specific XML lines containing the text you want to modify.
Editing XML Fragments
The editing engine performs find-and-replace operations on pretty-printed XML rather than the compacted single-line format. The editRange() function (lines 18-27) implements this by:
- Locating the exact
old_stringXML fragment in the document - Searching headers and footers if the string isn't found in
word/document.xml(lines 62-76) - Validating the occurrence count against
expected_replacements(default: 1) - Replacing the fragment with
new_string - Compacting the XML back to a single line (lines 98-112)
- Repacking the ZIP archive
Because edits operate on exact XML fragments, only targeted nodes change while the surrounding document structure—including styles, relationships, and embedded media—remains untouched.
Creating New Documents
The write() method (lines 72-100) generates new DOCX files from plain text input. It converts markdown-style headings (lines starting with #) into Word heading styles (Heading1, Heading2, etc.) and normal lines into paragraphs, then assembles the ZIP via createMinimalDocxZip().
The XML Editing Workflow
A typical editing session follows this three-step process:
- Retrieve the outline to locate the section requiring changes
- Fetch raw XML with specific offset and length to obtain the exact markup fragment
- Execute edit_block with the precise XML strings to replace content
This workflow ensures you work with readable, indented XML during inspection while the handler manages the complexity of compacting and repacking the final document.
Practical Code Examples
Reading Document Outlines
To inspect document structure without viewing raw XML:
await client.read_file({
path: '/home/user/report.docx'
// offset omitted → returns outline mode
});
Output:
DOCX Outline: 87 body children, 45 paragraphs, 7 tables, 3 images
[0] w:p style="Heading1"
Summary of quarterly results
[1] w:p
The company achieved...
[2] w:tbl (3 rows)
row0: [Region | Q1 | Q2 | Q3]
Extracting Raw XML for Editing
Once you identify the target section, retrieve the specific XML:
await client.read_file({
path: '/home/user/report.docx',
offset: 120, // Start at line 120
length: 50 // Return 50 lines
});
Sample output:
[DOCX XML: lines 120-169 of 845 (676 remaining)]
<w:p>
<w:pPr><w:pStyle w:val="Heading2"/></w:pPr>
<w:r><w:t>Executive Summary</w:t></w:r>
</w:p>
<w:p>
<w:r><w:t xml:space="preserve">Our revenue grew …</w:t></w:r>
</w:p>
Performing Surgical XML Edits
Copy the exact XML fragment from the previous step and modify it:
await client.edit_block({
file_path: '/home/user/report.docx',
old_string: '<w:t xml:space="preserve">Our revenue grew …</w:t>',
new_string: '<w:t xml:space="preserve">Our revenue increased by 12% …</w:t>'
});
The operation returns { success: true, editsApplied: 1 } and updates the DOCX while preserving all other formatting.
Creating Documents from Text
Generate new Word documents using markdown-style syntax:
await client.write_file({
path: '/home/user/newdoc.docx',
content: `# Title
This is a paragraph.
## Section
More text.`
});
The handler automatically converts # Title to Heading1 style and ## Section to Heading2.
Summary
- Desktop Commander MCP treats DOCX files as ZIP archives containing XML parts, enabling direct markup manipulation
- The DocxFileHandler in
src/utils/files/docx.tsprovides dual reading modes: human-readable outlines and pretty-printed raw XML - Edits use exact XML fragment matching via
editRange()(lines 18-27), with automatic fallback to headers/footers (lines 62-76) - The workflow preserves document formatting, styles, and embedded media by only modifying targeted XML nodes
- New documents can be generated from plain text using
write()(lines 72-100), which converts markdown headings to Word styles
Frequently Asked Questions
How does Desktop Commander MCP preserve formatting when editing DOCX files?
The system preserves formatting by operating on specific XML fragments rather than regenerating the entire document. When you call edit_block() with an exact XML fragment, the handler replaces only that specific node in the pretty-printed XML, then compacts the result back into the ZIP structure. This surgical approach leaves surrounding styles, relationships, and embedded media untouched, as implemented in the replacement logic at lines 98-112 of src/utils/files/docx.ts.
Why does the editing workflow require reading raw XML first?
Raw XML reading is required because DOCX editing relies on exact string matching of XML fragments. The prettyPrintXml() function (lines 31-52) formats the compact XML into indented, line-numbered output that you can copy precisely. Since the edit operation performs a find-and-replace on the pretty-printed representation, you must provide the exact whitespace and formatting shown in the raw output to ensure accurate targeting.
Can Desktop Commander MCP edit headers and footers in Word documents?
Yes, the editRange() implementation automatically searches headers and footers if the specified XML fragment is not found in the main document body. The logic at lines 62-76 iterates through all document parts in the ZIP archive, including word/header*.xml and word/footer*.xml, ensuring that text replacements can target any location within the document package.
What happens if the old_string pattern appears multiple times in the document?
The handler includes an expected_replacements parameter that defaults to 1. If your old_string appears more frequently than the expected count, the operation will fail with an error indicating the mismatch. This safety mechanism prevents accidental bulk replacements across the document. You can adjust expected_replacements to match the actual occurrence count when intentional multiple replacements are desired.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →