Surgically edit existing (brownfield) .docx files with formatting preservation and tracked changes via the Safe-DOCX MCP server. Use when reading, searching, editing, commenting on, or comparing Word documents — not for from-scratch generation.
Scanned 6/7/2026
Install to Claude Code
npx -y skills add modbender/skill-library-mcp --skill docx-editing --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Docx Editing?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/modbender-docx-editing)More formats (shields.io, HTML) on the badges page.
---
name: docx-editing
description: >-
Surgically edit existing (brownfield) .docx files with formatting preservation
and tracked changes via the Safe-DOCX MCP server. Use when reading, searching,
editing, commenting on, or comparing Word documents — not for from-scratch generation.
metadata:
author: usejunior
version: "0.1.1"
openclaw:
homepage: https://github.com/UseJunior/safe-docx
requires:
bins:
- node
- safe-docx
install:
- kind: node
package: "@usejunior/safe-docx@0.1.2"
bins:
- safe-docx
- safedocx
---
# Editing .docx Files with Safe-DOCX
Safe-DOCX is a local MCP server for surgically editing existing `.docx` files. It preserves formatting, generates tracked-changes redlines, and runs entirely on the local filesystem — no hosted endpoint, no data leaves the machine.
## Safety Model
- **Local-only stdio runtime** — the MCP server runs as a child process, never binds a port.
- **Path policy** — only files under `~/` (home directory) and system temp directories are accessible. Symlinks must resolve to allowed roots.
- **Archive guardrails** — zip bomb detection and hostile payload rejection protect against malformed `.docx` inputs.
## When to Use This Skill
Use Safe-DOCX when you need to:
- Change clauses or paragraphs in an existing `.docx`
- Insert or delete content with formatting preservation
- Add comments or footnotes for reviewers
- Produce a tracked-changes redline from edits
- Compare two `.docx` files into a redline
- Extract revisions to structured JSON
- Apply layout formatting (spacing, row heights, cell padding)
## Not for From-Scratch Generation
Safe-DOCX edits already-existing `.docx` files — it does not create documents from blank. For new document generation, use a template-filling workflow (e.g. OpenAgreements). Safe-DOCX can refine generated docs downstream.
## Quick Start
```
1. read_file(file_path="~/doc.docx") → see paragraphs + _bk_* IDs
2. grep(file_path="~/doc.docx", patterns=["target phrase"]) → find paragraph IDs
3. replace_text(session_id, target_paragraph_id, old_string, new_string, instruction)
4. save(session_id, save_to_local_path="~/doc-edited.docx")
```
## Core Workflow: Read, Locate, Edit, Save
**Step 1 — Read.** Call `read_file` with `format: "toon"` (token-efficient table) to see paragraphs and their stable `_bk_*` IDs.
**Step 2 — Locate.** Use `grep` with regex patterns to find target paragraphs. It returns paragraph IDs with surrounding context.
**Step 3 — Edit.** Use `replace_text` to swap text within a paragraph, or `insert_paragraph` to add new paragraphs before/after an anchor.
**Step 4 — Save.** Call `save` to write output. Default is `save_format: "both"` which produces a clean copy and a tracked-changes redline.
## Gotchas That Will Bite You
### Unique match required
`replace_text` needs `old_string` to match **exactly one** location in the target paragraph. If the text appears multiple times, you get `MULTIPLE_MATCHES`. Fix: include more surrounding context in `old_string`.
```
BAD: old_string: "the Company" → 5 matches, fails
GOOD: old_string: "the Company shall indemnify" → 1 match, succeeds
```
### Footnote markers are display-only
`read_file` shows footnotes as `[^1]`, `[^2]`, etc., but these markers are **not part of the editable text**. You cannot search for or replace `[^1]` via `replace_text`. To modify footnotes, use the dedicated `add_footnote`, `update_footnote`, and `delete_footnote` tools.
### Hyperlinks are read-only
`read_file` shows links as `<a href="...">text</a>`, but you **cannot create new hyperlinks** via `replace_text` or `insert_paragraph`. The `<a>` tag is stripped from new text. Existing hyperlinks are preserved when surrounding text is edited.
### Paragraph IDs are session-scoped
The `_bk_*` bookmark IDs are generated when a document is opened and are **tied to that session**. Do not store or reuse IDs across sessions. Always re-read the document to get fresh IDs.
### Smart text matching
`replace_text` is tolerant of:
- Quote variants: straight `"`, curly `\u201c\u201d`, angle `\u00ab\u00bb` all match each other
- Whitespace differences: multiple spaces, tabs, and line breaks are normalized
This means you can copy text from `read_file` output and use it in `old_string` even if the underlying XML uses different quote characters.
## Formatting Tags
When writing `new_string` in `replace_text` or `insert_paragraph`, use inline tags to apply formatting:
| Tag | Effect |
|-----|--------|
| `<b>text</b>` | Bold |
| `<i>text</i>` | Italic |
| `<u>text</u>` | Underline |
| `<highlighting>text</highlighting>` | Yellow highlight |
Tags can be nested: `<b><i>bold italic</i></b>`. Formatting from the original matched text is preserved for untagged replacement text.
## Batch Edits with apply_plan
For 3+ edits on one document, prefer `apply_plan` over sequential `replace_text` calls. It validates all steps before applying any, so you get all-or-nothing transactional semantics.
```
1. read_file / grep → gather paragraph IDs and text
2. apply_plan(file_path, steps=[
{ step_id: "1", operation: "replace_text", target_paragraph_id, old_string, new_string, instruction },
{ step_id: "2", operation: "insert_paragraph", positional_anchor_node_id, new_string, instruction },
...
])
3. save(session_id, save_to_local_path)
```
## Insert Paragraphs
`insert_paragraph` adds new content before or after an anchor paragraph.
- `position`: `"BEFORE"` or `"AFTER"` (default `"AFTER"`)
- `style_source_id`: optional `_bk_*` ID of a paragraph whose formatting you want to clone
- Multi-paragraph: separate with `\n\n` in `new_string` (each becomes its own paragraph)
## Comments and Footnotes
**Comments:** `add_comment` anchors to a paragraph (optionally to a text span via `anchor_text`). Use `get_comments` to list, `delete_comment` to remove. Supports threaded replies via `parent_comment_id`.
**Footnotes:** `add_footnote` inserts a footnote marker in a paragraph (optionally after specific text via `after_text`). Use `get_footnotes`, `update_footnote`, `delete_footnote` to manage.
## Comparing Documents
Two modes:
- **Two files:** `compare_documents(original_file_path, revised_file_path, save_to_local_path)` — produces a redline
- **Session edits:** `compare_documents(session_id)` — compares current session state against the original
Use `extract_revisions` on any document with tracked changes to get structured JSON diffs.
## Accepting Tracked Changes
Call `accept_changes(session_id)` to flatten all tracked changes into a clean document. This removes all revision markup.
## Session Behavior
- Sessions auto-create when you first use `file_path` with any tool
- Sessions expire after **1 hour** of inactivity (each tool call resets the timer)
- Call `clear_session` to clean up when done
- Documents are **normalized on open**: format-identical runs are merged and proof-error markers removed, which improves text matching reliability
## Layout Formatting
`format_layout` applies paragraph spacing, table row height, and cell padding without touching text content. Units are in twips (1/20 of a point) or DXA (1/635 of an inch).
## Path Restrictions
By default, only files under `~/` (home directory) and system temp directories are accessible. Symlinks must resolve to allowed roots.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!