Repository navigation
Releases
v0.7.4 — Word for Mac hard-open failure causes documented in SKILL.md
What's new
SKILL.md: Word for Mac hard-open failure causes (5 patterns)
This release documents five distinct causes of "Word experienced an error trying to open the file" on Word for Mac, discovered through systematic binary-search debugging of a production report generator. Windows Word is more forgiving about all five.
1. w:fldChar / w:instrText packed into one <w:r>
Every field code element must be in its own separate <w:r>. Packing begin/instrText/end into one run is schema-invalid and triggers a hard open failure on Mac.
2. w:titlePg without a matching first-page header/footer
Declaring <w:titlePg/> in sectPr without also providing a w:headerReference type="first" causes Mac Word to refuse the file.
3. PNG relationship before header/footer relationships in document.xml.rels
An undocumented Mac Word constraint: any PNG image relationship that appears before header/footer relationships in word/_rels/document.xml.rels causes a hard open failure. JPEG is fine. Fix: access section.header/section.footer before any add_picture() call.
4. w:abstractNum after w:num in numbering.xml
CT_Numbering is a strict OOXML sequence — all w:abstractNum elements must precede all w:num elements. Merging custom numbering into a template via naive .append() violates this.
5. wp:extent cx/cy values in the billions (NEW)
Word for Mac refuses files where any <wp:extent> attribute is in the billions or trillions of EMUs (normal range: 1–10 million). Two distinct root causes:
Cause A — PIL BytesIO without DPI metadata:
# WRONG — no DPI → python-docx gets DPI≈0 → cx/cy in trillions
img.save(buf, format='JPEG', quality=92)
# CORRECT
img.save(buf, format='JPEG', quality=92, dpi=(96, 96))Cause B — double Inches() conversion:
A helper that calls Inches(width) internally receives a pre-converted EMU value. Tell-tale fingerprint: 4,849,538,688,000 = 5,303,520 × 914,400. Fix: pass float inches to such helpers, never a pre-converted EMU constant.
Detection script (added to SKILL.md):
import zipfile; from lxml import etree
with zipfile.ZipFile("file.docx") as z:
tree = etree.fromstring(z.read("word/document.xml"))
WPD = 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
for i, ext in enumerate(tree.iter(f'{{{WPD}}}extent')):
if int(ext.get('cx', 0)) > 100_000_000:
print(f"Drawing {i}: cx={int(ext.get('cx')):,} *** OVERSIZED")No server code changes
This is a documentation-only patch. No API or behaviour changes.
uvx docx-mcp-server # always pulls latestFull Changelog: v0.7.3...v0.7.4
Full Changelog: v0.7.3...v0.7.4
v0.7.3 — PII scrubbing marked experimental, NER limitations documented
What's new
Documentation & safety
scrub_pii is now clearly marked experimental — not for production use across all surfaces:
server.py: docstring opens with[EXPERIMENTAL]warning, lists all four known NER gap categories, corrects model name toen_core_web_lg / ~560MBREADME.md: newPII Scrubbing (Experimental)section with a prominent warning callout, reliable vs. unreliable detection split, all four known gaps, and adry_run=True-first recommended workflowSKILL.md: PII Scrubbing table added to Quick Reference with⚠️ Experimentalheading and WARNING block — agents reading the skill at runtime will see the caveat before calling the tool
Known NER gaps documented in all three locations:
- Names in ALL-CAPS (ledger headers, table cells) — frequently missed
- Single-token names with no surrounding context — unreliable
- Non-English names (Arabic, CJK, African) — low recall on the English model
- Names in legal boilerplate (
Lender: Jane Doe,Authorized Signatory: John Smith) — often skipped
Pattern-based detectors (email, phone, SSN, credit card, IBAN, IP address) remain reliable and are unaffected.
No code changes
This is a documentation-only patch on top of v0.7.2. No API or behaviour changes.
uvx docx-mcp-server # always pulls latestFull Changelog: v0.7.2...v0.7.3
v0.7.2 — Drop PyTorch, faster PII with better recall
What's new
Performance & install size
Swapped the PII NER model from en_core_web_trf to en_core_web_lg, removing the entire PyTorch stack (torch, transformers, tokenizers, triton, sympy) from the dependency tree.
Benchmarked on 6 real financial/legal PDFs (1.6M chars):
| Model | Load | Inference | Total | PyTorch? |
|---|---|---|---|---|
en_core_web_trf (old) |
1.5s | 23.4s | 24.9s | yes (~1 GB) |
en_core_web_lg (new) |
1.7s | 8.8s | 10.5s | no |
en_core_web_lg is 2.4× faster and has higher PERSON recall — the transformer model missed all 18 person names in a general ledger document that the large CNN model caught.
Install size:
- Before: ~1.5 GB (dominated by PyTorch wheel)
- After: ~100 MB
uvx docx-mcp-server is now a fast, normal-sized install. The en_core_web_lg model (~560 MB) downloads on first scrub_pii call, deferred and one-time.
Standalone release binaries also now fit within GitHub's 2 GiB asset limit.
Upgrade from v0.7.1
No API changes. If you have en_core_web_trf cached, it will no longer be used. The new model downloads automatically on first PII call.
uvx docx-mcp-server # always pulls latestFull Changelog: v0.7.1...v0.7.2
v0.7.1 — CI fixes and improved change-tracking docs
What's new
Bug fixes
- Portable test fixture paths — replaced hardcoded
/Users/4n6h4x0r/...absolute paths in test files withPath(__file__).parent / "fixtures", fixing CI failures on Glama and any non-local environment - Ruff lint and format — resolved all outstanding I001 (import sort), SIM115 (bare
open()), E501 (long lines), F841 (unused variable) violations across the entire project; CIruff checkandruff format --checkboth pass clean
Documentation
- README: updated tagline, capabilities table (new "Change log" row), two new examples (Change Log After a Revision Session, Compare Two Separate Document Versions), Track Changes tool section expanded with
replace_text/modify_cell/edit_header_footer/set_track_changes/get_tracked_changes; new Change Log tool section (generate_change_summary,compare_documents,diff_to_text); tool count corrected from "45" to "200+" - SKILL.md (agent skill): tracked=False opt-out documented for all five editing tools;
replace_textpromoted to preferred one-step replacement; three new workflow sections (Generate Change Log, Compare Two Files, Direct Edits); Change Log tool table added; Audit Checklist updated
Upgrade from v0.7.0
No API or behaviour changes — this is a CI and documentation patch.
uvx docx-mcp-server # always pulls latestFull Changelog: v0.7.0...v0.7.1
v0.7.0 — Untracked Edits, Change Summary & Diff-to-Text
What's New
Direct Edits Without Tracked-Change Markup (tracked=False)
All five editing tools now accept tracked: bool = True. Pass tracked=False to apply changes directly to the XML without emitting w:ins/w:del markup — useful for programmatic clean-up workflows where revision history is unwanted.
Affected tools: insert_text, delete_text, replace_text, modify_cell, edit_header_footer.
generate_change_summary — Email-Ready Change Log
New MCP tool that scans the open document for w:ins/w:del elements and writes a numbered .txt file with author, date, and text per change. Adjacent del+ins sibling pairs are grouped as REPLACEMENT entries. Suitable for pasting directly into an email or pull-request description.
diff_to_text — DOCX Diff With Plain-Text Output
New MCP tool that combines compare_documents (tracked-change DOCX) with a companion plain-text summary. Returns {docx_path, text_path, change_count}.
Multilevel List Tab-Alignment Fix
Explicit w:tabs/w:tab[@w:val="num"] tab stops are now written into every w:lvl in both create_multilevel_list() and the static _NUMBERING_XML template. Without these stops Word uses default tab intervals, causing section-heading text to drift rightward as numbers grow longer (e.g. 24.1 vs 24.1.1).
Footnote Improvements
add_footnotegains aurl=parameter that wraps the footnote text in a hyperlinkadd_footnote_reffor internal bookmark cross-references- Consecutive superscript refs are comma-delimited (
¹,²,³) - Internal hyperlinks and bookmark cross-references in footnotes
Real-World DOCX Test Corpus (44 fixtures)
44 .docx files sourced from Apache POI, Pandoc, Mammoth.js, python-docx, and LibreOffice test suites. All 44 pass DocxDocument.open(). These serve as the doer-checker gate — synthetic fixtures alone cannot catch real-world OOXML quirks.
Bug Fixes
- Fix CI:
import sys as _sysmoved to module top; quoted type annotations unquoted (UP037)
Full Changelog
Full Changelog: v0.6.1...v0.7.0
v0.6.1
What's new
Concurrent session support (contributed by @FeritMelih)
All 45+ tools now accept an optional document_handle string parameter. Multiple clients (e.g. parallel agents in a single Claude Code session) can edit different documents through one server process without clobbering each other's state.
Usage:
- Single-client callers: omit the handle — identical behavior to before
- Parallel-aware callers: pass a unique handle (e.g. a UUID) per session
# Parallel agents — each gets its own isolated slot
h1 = json.loads(open_document("/path/to/a.docx", document_handle="session-1"))["handle"]
h2 = json.loads(open_document("/path/to/b.docx", document_handle="session-2"))["handle"]
search_text("clause", document_handle=h1) # sees only a.docx
search_text("clause", document_handle=h2) # sees only b.docxopen_document, create_document, and create_from_markdown now return the handle in their response so callers can capture and thread it through subsequent calls.
Backward compatible: empty handle resolves to __default__ slot — no existing code needs to change.
Full Changelog: v0.6.0...v0.6.1
v0.6.0
What's new
New tool: get_body_text
Returns the accepted-view body text of the document (hyperlink text included, deleted text excluded) plus footnote text. Used by downstream consumers to extract document content for AI context windows without manually traversing OOXML.
replace_text now returns del_id and ins_id separately
Previously replace_text returned a single change_id. It now returns both del_id (the deletion half) and ins_id (the insertion half) of a tracked replacement, enabling callers to accept or reject each half independently via accept_change / reject_change.
accept_change / reject_change persist fix
Both methods now call _mark("word/document.xml") after modifying the DOM. Previously, accepted/rejected changes were applied in-memory but save_document silently wrote the original XML, discarding the resolution.
_flatten_para traverses w:hyperlink
Hyperlink run text is now included when flattening paragraph text for search. Previously, text inside <w:hyperlink> elements was silently omitted.
Comprehensive integration test suite
19 new tests (tests/test_mike_integration.py) covering: hyperlink text in search, accepted-view body, footnotes, NBSP/smart-quote normalization, context disambiguation, accept/reject roundtrip, batch edits, and real-fixture smoke tests.
P8/P9 feature expansion
New tools: field ops, header/footer layout, paragraph pagination, document operations (copy_document, flatten_document, get_reading_time), extended style tools, table query, accessibility (set_alt_text, get_alt_text, check_accessibility), block elements, text boxes, image borders, advanced field codes, and PDF export.
What's Changed
- feat: get_body_text tool, replace_text del_id/ins_id, accept/reject persist fix (v0.5.0) by @h4x0r in #3
New Contributors
Full Changelog: v0.5.0...v0.6.0
v0.4.0
What's New
Fuzzy text matching (OOXML artifact normalization)
- Invisible character removal: soft hyphen (U+00AD), zero-width space (U+200B), word-joiner (U+2060) are stripped from both query and document text before matching — these commonly appear in copy-pasted legal text and were silently breaking find/replace
- Non-standard space normalization: thin space, figure space, hair space, narrow no-break space → regular space
ignore_caseparameter oninsert_text,delete_text,replace_text- Multi-run spanning:
replace_textanddelete_textnow handle text that spans multiple<w:r>runs
get_tracked_changes — structured revision extraction
Returns all pending w:ins / w:del tracked changes as structured JSON with type, change_id, author, date, para_id, and text fields.
compare_documents — DOCX diff to tracked changes
Diffs two DOCX files at paragraph level (LCS via difflib) and produces a third document where changes appear as Word tracked revisions (w:ins / w:del), ready for review in Word or LibreOffice.
sanitize_metadata — three-level metadata stripping
- Level 1: remove rsid session-fingerprint attributes
- Level 2: anonymize tracked-change authors (
w:author) - Level 3: clear
dc:creator,cp:lastModifiedBy,cp:revisionincore.xml; clearCompanyinapp.xml; removeattachedTemplate/rsidsfromsettings.xml
scrub_pii — Presidio-powered PII redaction
- Detects PERSON, EMAIL_ADDRESS, PHONE_NUMBER, CREDIT_CARD, SSN, IP_ADDRESS, IBAN_CODE, and all other Presidio entity types via spaCy NER (
en_core_web_lg) - Deduplication pass: every occurrence of a detected entity string is redacted, including instances the NER model missed
- Redacted runs rendered as black bars (
w:highlight val="black") — standard legal redaction appearance in Word dry_run=Truemode returns entity list without modifying the documententities=[...]filter for targeted redaction (e.g. email-only)- Requires:
pip install "docx-mcp-server[pii]"+python -m spacy download en_core_web_lg
Install / Upgrade
pip install --upgrade docx-mcp-server
# For PII scrubbing:
pip install "docx-mcp-server[pii]"
python -m spacy download en_core_web_lgFull Changelog: v0.3.1...v0.4.0
v0.3.1
Bulletproof Documents — Auto-Repair, Backups, and Silent-Failure Fixes
v0.3.1 makes every document that passes through docx-mcp structurally valid on save — no more Word recovery dialogs, no more silently empty conversions.
Save Safety Net
Every save_document() call now runs three layers of protection automatically:
- Backup-on-overwrite — creates
.bak(or.bak2,.bak3...) before overwriting any existing file, so you never lose the original - Auto-repair — silently fixes orphaned footnotes/endnotes, duplicate paraIds, and broken internal relationships before writing
- Warn-on-save — surfaces heading level skips, unpaired bookmarks, inconsistent tables, and residual DRAFT/TODO markers in the save response
Markdown Conversion Fixes
Three bugs that caused create_from_markdown to silently produce near-empty documents:
- BOM handling — files starting with a byte-order mark no longer lose the first heading
- HTML headings —
<h1>–<h6>tags (common in AI-generated markdown) now render as proper Word headings instead of being silently dropped - Unknown content preserved — unrecognized markdown blocks render as plain paragraphs instead of disappearing
Track Changes Fix
- Mid-run insertion placement —
insert_textwith apositionsubstring that falls mid-run now correctly splits the run and inserts at the right point (previously placed at end of run) - Delete-then-insert — inserting after previously deleted text now works correctly
Other
- README rewritten for end-user audience (legal, security, consulting professionals)
create_from_markdownauto-saves to disk immediately (no separatesave_documentcall needed)- 355 tests, 100% coverage
Full Changelog: v0.3.0...v0.3.1
v0.3.0
Create Documents from Scratch — or from Markdown
docx-mcp v0.3.0 adds the two most-requested capabilities: create blank Word documents and convert GitHub-Flavored Markdown straight to .docx — all without leaving your AI coding environment.
New Tools
| Tool | What it does |
|---|---|
create_document |
Create a blank .docx (or from a .dotx template) — ready to edit immediately |
create_from_markdown |
Convert GFM markdown to a fully formatted .docx with headings, tables, lists, images, footnotes, code blocks, and smart typography |
Highlights
- Full GFM support — headings, bold/italic/strikethrough, links, images, bullet & numbered & nested lists, code blocks (with monospace + shaded styling), blockquotes, tables, footnotes, task lists
- Smart typography — straight quotes → curly quotes,
--→ en dash,---→ em dash,...→ ellipsis - Template mode — start from any
.dotxtemplate; custom styles and numbering are bootstrapped automatically - Valid OOXML out of the box — every generated document passes
audit_document()with unique paraIds, proper footnote cross-refs, and correct content types - 45 tools total — up from 43 in v0.2.x
Quick Start
claude mcp add docx-mcp -- uvx docx-mcp-server
Then ask Claude to create a document:
"Convert this markdown to a Word doc: # Hello World\n\nThis is bold and italic."
Stats
- 321 tests, 100% coverage
- Zero new dependencies beyond
mistune(already a lightweight, pure-Python markdown parser)
Full Changelog: v0.2.3...v0.3.0