doc-control
Edit Word documents by address: tracked changes, comments, anchors, with backups and undo.
- 01Stars
- 0
- 02Version
- 0.6.4
- 03License
- Apache-2.0
- 04Review
- Passed
- Language
- Python
- Last commit
- 2h ago
- Forks
- 0
- Open issues
- 24
- Registry update
- Oct 15, 2026
Connect
pypi$claude mcp add doc-control \ -- uvx doc-control==0.6.4
Environment variables
Nothing to configure — it works as is.
Security review
Review passedReviewed 41m ago.
- metadata
- scanned
- packages
- 1 checked
No findings.
Tools
This server runs on your machine, so its tool list is not fetched before install — the README below describes what it offers.
doc-control
Status: file engine working (pre-1.0; the API may still change). The live engine drives LibreOffice Writer and Microsoft Word (Windows). See
status/specification.mdandstatus/plan.md.
A deterministic tool that lets AI agents work on Word documents quickly, precisely and safely, ideally live in the Word window the writer has open. Every content change is a tracked change the writer can accept or reject.
For anyone — writers and editors first — who wants an AI agent's help inside a Word document without a single silent change.
Usage
Install with pip install doc-control (or run it without installing: uvx doc-control).
Python 3.11+, no other dependencies. To drive the Word that is open on Windows, install
doc-control[live] (it adds pywin32); LibreOffice needs no extra package.
Command line, JSON in and JSON out:
doc-control outline --json '{"doc": "Novela.docx"}'
doc-control find --json '{"doc": "Novela.docx", "query": "lluvia"}'
doc-control batch --json '{"doc": "Novela.docx", "ops": [
{"op": "text.replace", "at": "p12#a7b2a7b2:4-10", "text": "nieve"},
{"op": "comment.add", "at": "p40#c0ffee00", "text": "¿Seguro?"}]}'
doc-control help batch # the JSON Schema of any operation
Every write is a tracked change by DOC_CONTROL_AUTHOR (default Claude), made after a
backup and verified, with undo and history.* to go back. Addresses like
p12#a7b2a7b2 come from read results and fail loudly if the paragraph changed.
From Python, a document can be held open for many reads, with whole results:
from doc_control import api
with api.open("Novela.docx") as doc:
for p in doc.paragraphs(normalized=True, pages=True):
print(p["at"], p["style_name"], p["page"], p["text"][:40])
chapters = doc.run("segment", {"unit": "chapters"})["chapters"]
Recipes propose batches of fixes (Spanish typography, quotes, dashes, spacing, empty
paragraphs and more): recipe.list, then recipe.run returns a batch to review and apply.
Notes, headers, footers and tables
Reads cover the body only unless asked; stories opts in to the rest ("notes" for
footnotes and endnotes, "headers" for headers and footers) on paragraphs.list,
find, locate, para.get, outline, stats and revisions.list. Their paragraphs
carry an address with a prefix (fn3/p1#0c9e0c9e, en2/…, hdr1.default/…,
ftr1.first/…) that the edit operations accept, so a tracked edit inside a footnote or a
running header works like one in the body. A body read stays as fast as before.
doc-control paragraphs.list --json '{"doc": "Tesis.docx", "stories": ["body", "notes"]}'
doc-control text.replace --json '{"doc": "Tesis.docx", "at": "fn3/p1#0c9e0c9e:4-10", "text": "2019"}'
doc-control note.add --json '{"doc": "Tesis.docx", "at": "p40#c0ffee00:17", "kind": "footnote", "text": "Ver el cap. 2."}'
doc-control sections.list --json '{"doc": "Tesis.docx"}'
doc-control table.get --json '{"doc": "Tesis.docx", "table": 1}'
doc-control table.row.insert --json '{"doc": "Tesis.docx", "table": 1, "after_row": 2, "cells": ["a", "b"]}'
doc-control table.row.delete --json '{"doc": "Tesis.docx", "table": 1, "row": 4}'
note.add inserts the reference mark and the note as tracked insertions (rejecting them
removes both); the row operations write tracked row insertions and deletions. All of it is
backed up, verified and undoable like any other write. scripts/real_word_stories.py
checks it in real Word (see docs/real-word-check.md).
Page layout: front matter, numbering, headers and fields
layout.get returns the document's page plan: per section where it starts (from), its
break, its page numbering and its header and footer text per kind (default, first,
even; null inherits the previous section's). layout.apply takes a plan in the same
shape and brings the document to it. This one gives a novel an unnumbered title page,
then starts chapter 1 on a right-hand page numbered 1, with the chapter title in the odd
headers and the book title in the even ones:
doc-control layout.get --json '{"doc": "Novela.docx"}'
doc-control layout.apply --json '{"doc": "Novela.docx", "untracked": true, "plan": {
"version": 1, "two_sided": true, "mirror_margins": true,
"sections": [
{"from": "p1#ea7412aa", "break": "nextPage",
"numbering": {"shown": false, "start": 1, "format": "lowerRoman"},
"header": {"default": "", "even": ""}, "footer": {"default": "", "even": ""}},
{"from": "p2#5fac7aad", "break": "oddPage",
"numbering": {"shown": true, "start": 1, "format": "decimal"},
"header": {"default": "{chapter}", "even": "La casa del río"},
"footer": {"default": "{PAGE}", "even": "{PAGE}"}}]}}'
Each from is the address of the paragraph the section starts at (from outline or
paragraphs.list). {PAGE}, {NUMPAGES} and {chapter} (the current Heading 1) become
fields. Keys left out stay as they are. Breaks and numbering go in as tracked changes;
a new header or footer part and the two-sided and mirror settings cannot be tracked in
Word, so the plan needs untracked: true (without it the answer names what needs it).
Run it with "dry_run": true first to see the changes; nothing is written unless the
plan read back from the result equals the request. A new section break adds a paragraph
to carry it, so the addresses after it renumber: read them again with layout.get.
Fields: fields.list shows every field (TOC, PAGE, SEQ, REF, citations) and whether its
result is stale; toc.insert, caption.add, xref.add, index.mark and
index.insert add them as tracked insertions; fields.update refreshes the TOC, page,
sequence and reference results through headless LibreOffice (ENGINE_UNAVAILABLE
without it; set_update_on_open asks Word to refresh them on open instead). An index is
built by Word (F9); citation fields are never changed. scripts/real_word_layout.py checks the
result in real Word (see docs/real-word-check.md).
Live: the document the writer has open
With LibreOffice Writer installed, the same front doors work on the open window: the writer sees each comment and tracked change appear, and saves when they choose.
doc-control live.open --json '{"doc": "Novela.docx"}'
doc-control live.select --json '{"doc": "Novela.docx", "find_phrase": "lluvia fina"}'
doc-control batch --json '{"doc": "Novela.docx", "engine": "live", "ops": [
{"op": "comment.add", "at": "p40#c0ffee00", "text": "¿Seguro?"}]}'
doc-control live.close --json '{"doc": "Novela.docx", "save": true}'
Live batch takes comment.add, text.replace and para.insert; anything else, or a
machine with no app to drive, answers ENGINE_UNAVAILABLE with the reason
(live.status lists the apps found). LibreOffice needs a Python its own bridge was
built for: the system Python with python3-uno for a distribution's package, the
Python version the snap bundles for the snap (live.status names it). Closing never
drops unsaved work: a changed document needs save or discard.
References: operations · errors.
Planned shape
- One core, two engines, one API
- File engine: edits a closed
.docxwith the Python standard library only, rewriting only the parts it touches. Read operations also take a.txt,.mdor.odtfile (hard-wrapped text detected; Markdown#headings and OpenDocument headings become Heading styles); writes refuse it. - Live engine: controls the open document (LibreOffice Writer, and Word on Windows over COM; Mac later).
- File engine: edits a closed
- Two front doors: a CLI with JSON in/out, and an MCP server
(
doc-control mcp, stdio) for Claude Desktop / Cowork and other MCP clients. - Safety first: lock-file and on-disk change detection, backup before every
write, verification, automatic restore, operation log with undo, and
unintended_changes == 0as a gate. - No AI inside. Judgment belongs to the agent; approval belongs to the writer.
It will replace docx-fixer, whose rules become packs built on this tool, and
serve literary-critic as its first downstream consumer.
MCP server
doc-control mcp serves every operation as an MCP tool over stdio, one tool per
operation (para.move becomes para_move), with the same JSON Schemas and the
same response envelope as the CLI. It needs nothing beyond the package itself:
{
"mcpServers": {
"doc-control": { "command": "uvx", "args": ["doc-control", "mcp"] }
}
}
To drive the Word a writer has open on Windows, the server needs the live extra:
"args": ["--from", "doc-control[live]", "doc-control", "mcp"].
Documents
- Operation reference and error reference, generated from the code
- Initial research: tooling and risks
Development
This repo uses claude-arsenal
(spec-driven workflow, gated tasks in arsenal/tasks/). See CLAUDE.md.
Related projects
Same author:
- claude-arsenal — a Claude Code skill and plugin marketplace: specs before code, failing tests first, gates with numbers.
- literary-critic — lets an LLM index, analyse and advise on a full novel manuscript the way a professional editor would; doc-control's first planned consumer.
- drawspec — declarative diagram spec → clean, themeable SVG; JSON with no coordinates, so an LLM can write it.
- integral-job-search — a candidate-centred job search run as a conversation in Claude Code.
License
From the server's repository — third-party content, read it before running anything it suggests.
More in Productivity
superPDF AI
io.github.arjunprabhulal
PDF tools on your computer: read, convert, edit, merge, fill and compare PDFs. Nothing is uploaded.
xlsx-for-ai
io.github.senoff
50 spreadsheet tools for .xlsx: recalc formulas, repair broken refs, read/write, diff, audit.
token-optimizer-mcp
io.github.ooples
Context-window optimization: caching, compression & smart file tools that cut tokens 60-90%.
kordoc
io.github.chrisryugj
Parse Korean documents (HWP, HWPX, PDF, XLSX, DOCX, PPTX) to Markdown; fill forms, diff, render.
RealRun
com.baseledgerip.realrun
Independent test scores for AI models on everyday tasks: inbox, scheduling, receipts.
Agent Harbour
io.github.bianhai-111
Find tasks for people and AI; publish, claim, discuss and deliver work with verified-email access.
Listing from the official MCP Registry · repository facts from GitHub, 44m ago.