Skip to content

Releases

v0.7.4 — Word for Mac hard-open failure causes documented in SKILL.md

Choose a tag to compare

@h4x0r h4x0r released this 28 May 23:14

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 latest

Full 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

Choose a tag to compare

@h4x0r h4x0r released this 25 May 22:20

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 to en_core_web_lg / ~560MB
  • README.md: new PII Scrubbing (Experimental) section with a prominent warning callout, reliable vs. unreliable detection split, all four known gaps, and a dry_run=True-first recommended workflow
  • SKILL.md: PII Scrubbing table added to Quick Reference with ⚠️ Experimental heading 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 latest

Full Changelog: v0.7.2...v0.7.3

v0.7.2 — Drop PyTorch, faster PII with better recall

Choose a tag to compare

@h4x0r h4x0r released this 25 May 22:02

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 latest

Full Changelog: v0.7.1...v0.7.2

v0.7.1 — CI fixes and improved change-tracking docs

Choose a tag to compare

@h4x0r h4x0r released this 25 May 08:52

What's new

Bug fixes

  • Portable test fixture paths — replaced hardcoded /Users/4n6h4x0r/... absolute paths in test files with Path(__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; CI ruff check and ruff format --check both 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_text promoted 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 latest

Full Changelog: v0.7.0...v0.7.1

v0.7.0 — Untracked Edits, Change Summary & Diff-to-Text

Choose a tag to compare

@h4x0r h4x0r released this 23 May 12:25

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_footnote gains a url= parameter that wraps the footnote text in a hyperlink
  • add_footnote_ref for 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 _sys moved to module top; quoted type annotations unquoted (UP037)

Full Changelog

v0.6.1...v0.7.0

Full Changelog: v0.6.1...v0.7.0

v0.6.1

Choose a tag to compare

@h4x0r h4x0r released this 21 May 01:03

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

open_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

Choose a tag to compare

@h4x0r h4x0r released this 15 May 06:20

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

  • @h4x0r made their first contribution in #3

Full Changelog: v0.5.0...v0.6.0

v0.4.0

Choose a tag to compare

@h4x0r h4x0r released this 06 May 15:16

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_case parameter on insert_text, delete_text, replace_text
  • Multi-run spanning: replace_text and delete_text now 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:revision in core.xml; clear Company in app.xml; remove attachedTemplate / rsids from settings.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=True mode returns entity list without modifying the document
  • entities=[...] 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_lg

Full Changelog: v0.3.1...v0.4.0

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 09 Apr 15:11

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_text with a position substring 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_markdown auto-saves to disk immediately (no separate save_document call needed)
  • 355 tests, 100% coverage

Full Changelog: v0.3.0...v0.3.1

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 24 Mar 02:04

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 .dotx template; 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