# doc-control (pypi · doc-control)

Edit Word documents by address: tracked changes, comments, anchors, with backups and undo.

- Trust score: 75/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-17

## Components

- pypi · `doc-control`: 75/100 (this document), [markdown](https://verifymcp.io/servers/nuncaeslupus-doc-control/doc-control.md), [page](https://verifymcp.io/servers/nuncaeslupus-doc-control/doc-control)

## Channel facts

- Registry: `pypi`
- Package: `doc-control`
- Version: `0.8.3`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-17.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised build step with no custom scripting around it.
  - No production dependencies, so there is no dependency health to assess.
- **Provenance & Transparency**: 97/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to nuncaeslupus/doc-control).
  - Clear OSI-approved license (Apache-2.0).
  - Actively maintained (last published 0 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 38/100
  - 0% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 18106 tokens (~266/item across 68 items; 67 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 7/100
  - Stability observed for 2 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 4 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 68 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.
  - Supports UI / widget rendering.

## Install

### How do I install the doc-control MCP server?

doc-control runs locally as a PyPI package, launched with uvx doc-control. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add nuncaeslupus-doc-control -- uvx doc-control
```

### Cursor

```json
{
  "mcpServers": {
    "nuncaeslupus-doc-control": {
      "command": "uvx",
      "args": [
        "doc-control"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "nuncaeslupus-doc-control": {
      "command": "uvx",
      "args": [
        "doc-control"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add nuncaeslupus-doc-control -- uvx doc-control
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "nuncaeslupus-doc-control": {
      "type": "local",
      "command": [
        "uvx",
        "doc-control"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add nuncaeslupus-doc-control --command uvx --arg doc-control
```

### Hermes

```yaml
mcp_servers:
  nuncaeslupus-doc-control:
    command: "uvx"
    args: ["doc-control"]
```

### Netclaw

```json
{
  "McpServers": {
    "nuncaeslupus-doc-control": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "doc-control"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add nuncaeslupus-doc-control -t stdio -c uvx -a doc-control
```

### Other

```json
{
  "mcpServers": {
    "nuncaeslupus-doc-control": {
      "command": "uvx",
      "args": [
        "doc-control"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-16 (score 75, +16)

- [security regression] Tool safety: pass → unverified
- [security improvement] Malware scan: unverified → pass
- [security] Stability: Stability not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- [functional regression] Capabilities: pass → unverified
- [functional regression] Schema quality: 0 → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional improvement] Stability: unverified → 0.03
- [functional] Package version: 0.8.1 → 0.8.3

### 2026-10-15 (score 59)

First indexed and scored.

## MCP tools (67)

### `anchor_create` (~239 tokens)

Make an anchor for a passage so it can be found again after edits shift or change addresses: the quoted text, `context` characters before and after (default 32, max 200), and the paragraph's number and fingerprint then. `at` is a range in one paragraph (p142#a7b2a7b2:10-15), a whole paragraph (p142#a7b2a7b2) or a sentence (p142#a7b2a7b2.s3). Use it to bookmark passages you will come back to; find them later with anchor.resolve (text.find searches for any text instead). Read-only; STALE_ADDRESS when `at` is outdated, SCHEMA_INVALID for a range across paragraphs.

Input parameters:

- `at` (string, required): Range inside one paragraph, e.g. p142#a7b2a7b2:10-15, or a whole paragraph or sentence (p142#a7b2a7b2.s3).
- `context` (integer): Characters of prefix and of suffix to keep.
- `doc` (string, required): Path to the .docx file.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `anchor_resolve` (~405 tokens)

Find an anchor's passage in the document as it is now: its current range, a 0-1 score and the match kind (exact, normalized or fuzzy). Pass the `anchor` object anchor.create returned, or just {exact, prefix, suffix}. mode best (default) falls back to a fuzzy match scoring at least 0.80; mode exact accepts only verbatim, normalised or folded (match_level, default `default`: case, accents, punctuation) matches. Fails with ANCHOR_NOT_FOUND, or ANCHOR_AMBIGUOUS listing up to 5 tied candidates. Use it to re-address a passage after edits; doc.diff maps addresses between two files instead. Read-only.

Input parameters:

- `anchor` (object, required): An anchor from anchor.create, or just {exact, prefix, suffix}.
- `doc` (string, required): Path to the .docx file.
- `match_level`: How far the quote is folded when neither it nor its normalised text occurs. exact: today's rule (verbatim, then normalised, then fuzzy in mode best). default: then ignoring case, accents and punctuat…
- `mode`: best: verbatim matches first, then fuzzy ones (match says which). exact: only places holding the quote verbatim or, when none does, in the normalised text (soft hyphens and zero-width characters igno…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `caption_add` (~298 tokens)

Add a numbered caption next to a body paragraph (a table, figure or equation), as tracked insertions: label, a SEQ field with the number, and `text` (e.g. label Table, text "Results" gives "Table 3 Results"). position defaults to above for Table, below for Figure and Equation. A hidden bookmark wraps label and number so xref.add can point at it; field.update renumbers after later inserts. Backed up and undoable (history.undo); dry_run previews; expect.sha256 refuses a changed file. Returns the caption's address; UNSUPPORTED_CONTENT for a paragraph in a text box.

Input parameters:

- `at` (string, required): Address of the body paragraph the caption goes next to.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `label` (required): The caption label.
- `position`: Default: above for a Table, below for a Figure or Equation.
- `tag` (string): Appended to the author.
- `text` (string, required): Caption text after the number; may be empty.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `comment_add` (~348 tokens)

Add one comment on a range: `at` is a paragraph (p3#a1b2c3d4), part of one (p3#a1b2c3d4:10-15) or a span across paragraphs (p3#a1b2c3d4:10..p5#e5f6a7b8:2); a newline in `text` starts a new comment paragraph. Author defaults to DOC_CONTROL_AUTHOR. File engine: backed up first, dry_run previews, expect.sha256 refuses a file changed since, logged for history.undo. engine live adds it in the open Word/LibreOffice window (after live.open). Use doc.batch to add several, or with comment.reply/comment.resolve, all or nothing. Returns the new comment id.

Input parameters:

- `at` (string, required): Range to comment on: p3#fp, p3#fp:10-15 or across.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `engine`: `live` edits the document open in Word or LibreOffice (see live.open).
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `tag` (string): Appended to the author.
- `text` (string, required): Comment text; a newline starts a new paragraph.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `comment_list` (~222 tokens)

List the comments in thread order (each root comment, then its replies) with id, author, date, text (clipped at 1,000 characters), resolved state and the address where each starts, including comments in notes and headers. Filter with `resolved` (true or false) and `author` (exact). Use the ids to reply or resolve with doc.batch (comment.reply, comment.resolve); use revision.list for tracked changes instead. Read-only; paged: a reply is capped at about 8,000 bytes (limit up to 1,000), continue with `cursor` set to the reply's `next`.

Input parameters:

- `author` (string): Only this author.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `resolved` (boolean): Only resolved (true) or open (false).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_batch` (~833 tokens)

The main editing tool: apply several edits in one verified write, as tracked changes, all or nothing. One family per batch: text.replace, para.insert, para.delete, para.move, style.apply, format.apply, comment.add/reply/resolve; or table.row.insert/delete; or style.create/update/rename/merge; or revision.accept/reject. Example ops: [{"op": "text.replace", "at": "p3#a1b2c3d4:10-15", "text": "new"}]. Every address is as read; overlapping edits are BATCH_CONFLICT, outdated ones STALE_ADDRESS. File engine: backed up first, dry_run previews, expect.sha256 refuses a changed file, history.undo takes the batch back. tracked: false writes approved text plainly, on either engine. engine live edits the open app (comment.add, text.replace, para.insert only; no dry_run or rollback). server.help <kind> gives each kind's fields.

Input parameters:

- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `engine`: `live` edits the document open in Word or LibreOffice (see live.open).
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `ops` (array, required): Operations, applied together. Every address refers to the document as read; an operation placed relative to a paragraph another operation deletes or moves is a BATCH_CONFLICT. Kinds: text.replace ['a…
- `tag` (string): Appended to the author.
- `tracked` (boolean): true (default): edits are tracked changes. false: a plain write, for a change the writer already approved: built as the tracked edit, then this call's own revisions are accepted in the same verified…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_compare` (~232 tokens)

Write a redline: a new .docx at `output` (which must not exist) holding the `new` version with every difference from `old` as a tracked change (author default Claude, `date` ISO 8601 with time zone, default now), so a writer reviews and accepts or rejects them in Word. The input files are never modified; the redline is verified (accepting every change gives the new text) or removed (VERIFY_FAILED_RESTORED). Use doc.diff for the alignment as data without writing a file. Returns the output path and hash, per-kind paragraph counts and the revision count.

Input parameters:

- `author` (string): Revision author (default Claude).
- `date` (string): Revision date, ISO 8601 with time zone.
- `new` (string, required): Path of the later version (.docx).
- `old` (string, required): Path of the earlier version (.docx).
- `output` (string, required): Path of the redline to create (must not exist).
- `tag` (string): Appended to the author as `author (tag)`.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_diff` (~233 tokens)

Align the paragraphs of two versions (`old`, `new`; .docx, or .txt/.md/.odt): which are unchanged, modified, moved, split, merged, inserted or deleted, with a map from old addresses to new ones (null for deleted). Use it to carry addresses read from an older version over to the current one, or to see what changed; doc.compare writes the same differences as a redline .docx instead. collapse_runs folds runs of shifted unchanged paragraphs into one record. Read-only; paged at about 8,000 bytes with limit and `cursor` (the `next` of the previous reply).

Input parameters:

- `collapse_runs` (boolean): List runs of shifted unchanged paragraphs as one record each.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `new` (string, required): Path of the later version (.docx).
- `old` (string, required): Path of the earlier version (.docx).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_info` (~135 tokens)

Cheap first look at a file: size, sha256 (pass it as expect.sha256 to a later write so it is refused if the file changed meanwhile), core properties (title, author, dates), whether Track Changes is on, whether Word holds it open (a ~$ lock file) and which parts exist (comments, notes, headers). The envelope's `doc` gives the paragraph count. Use doc.stats for word, heading and revision counts, doc.outline for the headings. Read-only; DOC_NOT_FOUND or NOT_A_DOCX for a bad path.

Input parameters:

- `doc` (string, required): Path to the .docx.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_open` (~132 tokens)

Start a session on a document: back it up into its store (.doc-control/<file name>/ next to it; the newest 20 backups or 30 days are kept) and note it in the undo log if it changed since the last logged write. Call it once before editing; the document itself is not changed. If the document has no history yet, `adoptable` lists orphaned stores (it was moved or renamed) that history.adopt can re-attach. Returns the backup path, the log entry and the store folder.

Input parameters:

- `doc` (string, required): Path to the .docx.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_outline` (~270 tokens)

List the headings (levels 1-9, by style or outline level) in document order, each with its address, level, style and text: the table-of-contents view. max_level 2 keeps levels 1-2; stories ["body", "notes"] adds headings in notes. Use doc.segment for chapters that are not styled as headings, para.list for every paragraph, para.get to read one heading in full. Read-only; paged: about 8,000 bytes per reply (limit up to 1,000), continue with `cursor` set to the reply's `next`.

Input parameters:

- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `max_level` (integer): Only headings up to this level.
- `stories` (array): Stories to read (default ["body"]): "notes" adds footnotes and endnotes, each after the paragraph holding its mark; "headers" adds headers and footers, after the body. Their addresses carry a prefix…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_segment` (~359 tokens)

Split a manuscript into units with stable ids: unit chapters (default), parts or scenes (detected from headings, numerals, centring, scene markers; signals say which), or sentences or dialogue lines within `range` (an address, p24#a1b2c3d4..p60#e5f6a7b8, or a unit id such as ch-d5a3930d). `chapter` limits scenes to one chapter; language (default es) sets sentence rules; profile and overrides tune detection. Use it to work chapter by chapter or to get sentence addresses (p142#a7b2a7b2.s3); doc.outline lists only styled headings. Read-only; paged at about 8,000 bytes with `cursor`.

Input parameters:

- `chapter` (string): scenes only: the chapter id whose scenes to list.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `language` (string): Sentence rules (default es).
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `overrides` (array): Force or deny a chapter or scene at paragraphs by fingerprint.
- `profile` (object): Detection settings for this document.
- `range` (string): sentences and dialogue only: what to cover — a paragraph address or range, a run of whole paragraphs (p24#fp..p60#fp), or a chapter, scene or part id (ch-…, sc-…, pt-…; same profile and overrides).
- `unit` (string): What to list (default chapters).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `doc_stats` (~191 tokens)

Count what the body holds: paragraphs (and empty ones), words, characters, headings, tables, comments, footnotes, tracked changes by type, styles in use and the 20 most used fonts. stories ["notes", "headers"] adds separate counts for notes and for headers/footers (the body counts stay the body's). Use it to size a document or check for leftover revisions; doc.info gives file facts and the hash, revision.list the changes themselves. Read-only; one reply, not paged.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `stories` (array): Stories to read (default ["body"]): "notes" adds footnotes and endnotes, each after the paragraph holding its mark; "headers" adds headers and footers, after the body. Their addresses carry a prefix…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `field_list` (~189 tokens)

List every field (PAGE, TOC, REF, SEQ, XE, citations such as ADDIN ZOTERO_ITEM...) with its address, story, code, type, shown result and whether it is stale (needs updating). stories defaults to all: body, notes, headers and comments; `type` keeps one type ("TOC"). Covers nested, split and simple fields and fields in text boxes. Use it before field.update to see what is stale, or to find citations; it never changes anything. Read-only; one reply, not paged.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `stories` (array): Stories to read (default all): "body", "notes", "headers" and "comments".
- `type` (string): Only fields of this type, e.g. "TOC" or "ADDIN ZOTERO_ITEM".

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `field_update` (~269 tokens)

Recompute the body's TOC, PAGE, NUMPAGES, SEQ and REF/PAGEREF results with headless LibreOffice and write only those result runs, as tracked changes (old result struck, new inserted). Needs LibreOffice (soffice): without it, ENGINE_UNAVAILABLE names the stale fields; set_update_on_open instead sets w:updateFields so Word updates them on opening. Citations are never changed; INDEX, SECTIONPAGES, SECTION, NOTEREF and unresolvable REFs are left and listed under `skipped`. Run after toc.insert, caption.add or xref.add. Backed up and undoable; dry_run previews; slow (seconds).

Input parameters:

- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `set_update_on_open` (boolean): Only set w:updateFields in settings.xml (no LibreOffice).
- `tag` (string): Appended to the author.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_adopt` (~213 tokens)

Re-attach a moved or renamed document to its old undo history, so history.undo and history.restore work again. It finds the orphaned store whose last recorded state is this file's sha256 (doc.open lists them as `adoptable`, history.orphans lists all) and moves it, backups and log, to where this document's store lives now. Pass `store` when several match (the reply lists `candidates`); `force` adopts a file edited since; a document that already has history is never overwritten (BATCH_CONFLICT). dry_run moves nothing.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Say what would move; move nothing.
- `force` (boolean): Adopt although the document's sha256 is not the store's last.
- `store` (string): The orphan store to adopt (see doc.open's `adoptable` or history.orphans); needed when several match or the old folder is far away.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_list` (~211 tokens)

List the document's undo log, newest first: each write's op_id, kind, time, author, the operations it ran, whether it was undone, and the deleted paragraphs history.restore can bring back (change numbers). Filter with `kind` (batch, restore, undo, open, purge, adopt); `store` reads an orphaned store instead. Use it to find the op_id for history.undo or history.restore; history.search finds a quote in the log. Read-only; paged with limit (default 20) and `cursor` (the reply's `next`).

Input parameters:

- `cursor` (string): The `next` of the previous response.
- `doc` (string): Path to the .docx.
- `kind`: Only entries of this kind (batch: every logged edit).
- `limit` (integer): Most entries per page (default 20).
- `store` (string): A store folder (see history.orphans) to read instead of the document's own.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_orphans` (~128 tokens)

List undo stores whose document is gone (moved, renamed or deleted): the app-data stores, plus `dir`/.doc-control when `dir` is given, each with its folder, last document path, entry count and last time; `unknown` holds stores that name no document. Their history is never deleted automatically. Use history.adopt to re-attach one to a moved document, history.list or history.restore with `store` to read from it, history.purge with `store` to delete it. Read-only.

Input parameters:

- `dir` (string): Also scan this folder.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_purge` (~211 tokens)

Delete undo history for privacy: the whole log (`all`) or entries before a date (`before`, ISO 8601, UTC), of the document or of an orphaned `store`. Two steps: run with dry_run to see what would go and get a `confirm` token, then repeat with `confirm`; a log that changed in between is refused. Leaves a tombstone entry; backups are not removed by it. Destructive: purged entries can no longer be undone or restored. Use history.list first to check what you remove.

Input parameters:

- `all` (boolean): Remove the whole log.
- `before` (string): Remove entries older than this ISO date or time (UTC).
- `confirm` (string): From the dry run.
- `doc` (string): Path to the .docx.
- `dry_run` (boolean): List what would go; returns confirm.
- `store` (string): A store folder (see history.orphans) to read instead of the document's own.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_restore` (~365 tokens)

Bring back paragraphs a logged write deleted or changed, as a tracked insertion (tracked: false: plainly, for a change the writer approved). Give the `op_id` (from history.list or history.search); without `change`, every paragraph that op deleted comes back after the paragraph now holding its old place; `change` picks one (also an edited one) and `after` (p12#a1b2c3d4) places it when that place was edited (BATCH_CONFLICT otherwise). Use history.undo to revert a whole write instead. Backed up, logged, undoable; dry_run previews.

Input parameters:

- `after` (string): Insert after this paragraph address instead of the old place (needed when that place was edited, or the history comes from `store`).
- `author` (string): Revision author.
- `change` (integer): Index of one change of the op (history.list shows them).
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `op_id` (string, required): The logged op the paragraph was changed by.
- `store` (string): A store folder (see history.orphans) to read instead of the document's own.
- `tag` (string): Appended to the author.
- `tracked` (boolean): false: a plain write for a change the writer already approved: the paragraph comes back without revision marks (this call's own insertion accepted in the same verified write).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_search` (~430 tokens)

Search the undo log for a quote: in each logged request and in the text of every paragraph before and after each write, newest first. Use it to find when a passage was changed or deleted, and the op_id for history.undo or history.restore; text.find searches the current document instead. Exact by default (ignore_case, ignore_accents loosen it); match_level default or loose folds case, accents and punctuation and returns each hit's offsets and level. Works on an orphaned `store` too. Read-only; limit (default 20) caps hits, `total` counts all.

Input parameters:

- `doc` (string): Path to the .docx.
- `ignore_accents` (boolean): Ignore accents: `maria` finds `María`.
- `ignore_case` (boolean): Ignore case.
- `limit` (integer): Most hits to return (default 20); `total` counts them all.
- `match_level` (string): Fold the search as `text.find` does: `default` ignores case, accents and punctuation (symbols such as `$` still count), `loose` drops symbols too; `exact` keeps the flags. When passed, Word's Find co…
- `query` (string, required): Text to find.
- `store` (string): A store folder (see history.orphans) to read instead of the document's own.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `history_undo` (~243 tokens)

Take back one logged write (doc.batch, text.replace and the other direct writes, comment and style writes, styleset.install, term.rename, field and note inserts, layout.apply, history.restore), returning its paragraphs and parts to their state before it: by default the latest one still in effect, or the `op_id` from history.list. Refused with BATCH_CONFLICT if a paragraph or part it changed was edited since (undo the later write first). Use history.restore to bring back single deleted paragraphs, revision.reject to drop tracked changes by author or id. The undo is itself logged; dry_run checks without writing; expect.sha256 refuses a changed file.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Check and preview; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `op_id` (string): The op to undo (default: the latest in effect).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `index_insert` (~257 tokens)

Insert an index (an INDEX field) after a body paragraph `after` (p40#a1b2c3d4), as tracked insertions: a title (default "Index", "" for none), the field with a placeholder result, and a closing paragraph. Mark entries first with index.mark; Word builds the entries and page numbers when the field is updated there (F9), since field.update leaves indexes alone. Backed up and undoable (history.undo); dry_run previews; expect.sha256 refuses a changed file. Returns the field paragraph's address.

Input parameters:

- `after` (string, required): Address of the body paragraph it goes after.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `tag` (string): Appended to the author.
- `title` (string): Title paragraph above it (default "Index"); "" for none.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `index_mark` (~266 tokens)

Mark an index entry: insert an XE field, as a tracked insertion, right after a range (p8#a1b2c3d4:10-15) so that word or phrase is indexed on its page. `entry` is "Term", or "Term:Sub" / "Term:Sub:Detail" for sub-entries. Mark every occurrence you want listed, then insert the index once with index.insert. Backed up and undoable (history.undo); dry_run previews; expect.sha256 refuses a changed file. Returns the paragraph address and field code.

Input parameters:

- `at` (string, required): A range inside one paragraph; the mark goes after it.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `entry` (string, required): The index entry, e.g. "Term" or "Term:Sub".
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `tag` (string): Appended to the author.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `layout_apply` (~404 tokens)

Bring the document to a page plan, the shape layout.get returns: per section its start (`from` address), break, page-number start and format, first-page difference, and header/footer text per kind ({PAGE}, {NUMPAGES}, {chapter} become fields; null inherits). Keys left out stay as they are. Edit the layout.get result and send it back. Breaks, numbering and header text are tracked changes; a new header part, a dropped reference, two_sided or mirror_margins need untracked: true. All or nothing: UNSUPPORTED_CONTENT if the result would differ from the plan or a section has a pending setup change. dry_run lists the changes.

Input parameters:

- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `plan` (object, required): The page plan, as layout.get returns it: version 1, two_sided, mirror_margins and sections, each with a `from` address; keys left out stay as they are, and numbering.shown is checked, not set. Every…
- `tag` (string): Appended to the author.
- `untracked` (boolean): Allow a change Word cannot track (LAYOUT D-L2).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `layout_get` (~119 tokens)

Read the page plan: per section where it starts (`from` address), its break, page numbering (shown, start, format), first-page difference, and header/footer text per kind (default, first, even) with fields as {PAGE}, {NUMPAGES} and {chapter}; null means inherited from the previous section. Edit it and pass it to layout.apply to change the layout; section.list gives the same setup with header/footer part names instead of text. Read-only.

Input parameters:

- `doc` (string, required): Path to the .docx.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_close` (~143 tokens)

Close the document in its Word or LibreOffice window (attached with live.open). With unsaved changes it is refused (UNSAVED_CHANGES) unless `save` (save, then close) or `discard` (drop them) is given; nothing unsaved is ever lost silently. Use live.save to keep the window open. Returns whether it saved.

Input parameters:

- `discard` (boolean): Close and drop the unsaved changes.
- `doc` (string, required): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.
- `save` (boolean): Save, then close.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_get_selection` (~130 tokens)

Read what the user has selected in Word or LibreOffice right now, and the paragraph the selection starts in with its address (p12#a1b2c3d4), to edit with doc.batch or text.replace. Use it when the user points at text ("this paragraph", "here"); live.select moves the selection instead. The document must be attached with live.open (ENGINE_UNAVAILABLE otherwise). Read-only.

Input parameters:

- `doc` (string, required): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_open` (~200 tokens)

Attach to Word or LibreOffice for live editing: find the window already holding the document, or open it in `app` (word or libreoffice; default the one reachable, see live.status) and bring it to the front (front: false keeps it behind). Call it before live.select, live.get_selection, live.save, live.close and doc.batch with engine live. Opens or focuses a window; never changes the document. ENGINE_UNAVAILABLE when no app can be driven, DOC_AMBIGUOUS when a file name matches several open documents.

Input parameters:

- `app` (string): `word` or `libreoffice`.
- `doc` (string, required): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.
- `front` (boolean): Bring the app's window to the front (default true); the reply's `front` says whether it came.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_places` (~223 tokens)

Show the user a list of places as clickable buttons: in a client that renders MCP Apps, a click moves Word's or LibreOffice's cursor there (mode cursor) or selects the paragraph (mode select) by calling live.select, with no assistant turn. Each place has a `label` and an `at` address and/or a `find_phrase` from para.locate. Use it to walk a writer through findings; live.select jumps to one place directly. Other clients just get the list back. Read-only.

Input parameters:

- `doc` (string, required): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.
- `mode`: What a click does: `cursor` (default) puts the cursor at the place's start; `select` selects its paragraph.
- `places` (array, required): The buttons, in order: a `label` and `at` or `find_phrase` each (with both, `find_phrase` is tried when `at` fails).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_save` (~115 tokens)

Save the document open in Word or LibreOffice (attached with live.open) to its file, from inside the app. Call it after live edits (doc.batch with engine live) to keep them; live.close with save: true saves and closes in one call. Overwrites the file on disk with the app's version; no backup is taken.

Input parameters:

- `doc` (string, required): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_select` (~478 tokens)

Show the user a place in Word or LibreOffice: select a paragraph, or with mode cursor only put the cursor at its start, by address (`at`, p142#a7b2a7b2) or by a `find_phrase` from para.locate (matched loosely per match_level). paragraphs_after moves on that many paragraphs. Use it to point at a passage you are discussing; live.places offers several as buttons. Changes only the selection; needs live.open. STALE_ADDRESS when `at` no longer matches, ANCHOR_NOT_FOUND or ANCHOR_AMBIGUOUS for a phrase.

Input parameters:

- `at` (string): Paragraph address, e.g. `p142#a7b2a7b2`.
- `doc` (string, required): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.
- `find_phrase` (string): A phrase that occurs once in the document (`\r` for a paragraph mark), as `para.locate` gives it.
- `find_phrase_across`: `para.locate`'s `find_phrase_across`, for a phrase spanning a mark.
- `front` (boolean): Bring the app's window to the front (default true); the reply's `front` says whether it came.
- `match_level`: How loosely `find_phrase` matches. `default` (the default) ignores case, accents and punctuation; `loose` symbols too. Either tries the levels from verbatim on and takes the first that matches, so on…
- `mode`: `select` (default) selects the paragraph; `cursor` only puts the cursor at its start, so typing or Delete cannot replace it.
- `paragraphs_after` (integer): Select the paragraph this many after the one named.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `live_status` (~144 tokens)

Check the live apps: which of Word and LibreOffice are installed and reachable, and which documents they have open; with `doc`, which app holds it, whether that app is driven by this server or only a lock file says so, its full path and whether it has unsaved changes. Call it before live.open, or to decide between live editing and the file engine (a file open in Word should be edited live or closed first). Read-only; never fails for a missing app.

Input parameters:

- `doc` (string): Path to the document, or the file name of one open in a live app.
- `engine`: Always the live engine; may be left out.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `note_add` (~316 tokens)

Add a footnote or endnote: the reference mark goes at a body point (p3#a1b2c3d4:10-10, after offset 10), after a range, or at the end of a paragraph (p3#a1b2c3d4); `text` is the note's one line. Mark and note are tracked insertions, so rejecting them removes the note; the notes part is created when missing. Backed up and undoable (history.undo); dry_run previews; expect.sha256 refuses a changed file. Returns the note's id, displayed number and address (fn5/p1#…).

Input parameters:

- `at` (string, required): Body point p3#fp:10-10 (the mark goes after the offset), a range inside one paragraph (after it) or a paragraph (at its end).
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `kind` (required): footnote (bottom of the page) or endnote (end of the document).
- `tag` (string): Appended to the author.
- `text` (string, required): The note text, one line.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `page_estimate` (~224 tokens)

Estimate the page each paragraph starts on (and ends on when it spans pages): from Word's rendered page marks and explicit breaks where present, else a words-per-page model; always flagged `estimated`. `at` (p142#a7b2a7b2) gives one paragraph; by "page" lists pages with their first and last paragraph instead. Use it to tell a user roughly where text is; para.locate adds chapter, scene and a find phrase. Read-only; paged at about 8,000 bytes with `cursor`.

Input parameters:

- `at` (string): Only this paragraph, by address, e.g. p142#a7b2a7b2.
- `by` (string): List one row per paragraph (default) or per page.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `para_delete` (~467 tokens)

Delete a paragraph (p3#a1b2c3d4) or a run of paragraphs (p3#a1b2c3d4..p5#e5f6a7b8), as a tracked deletion the writer can reject. File engine: backed up first, logged for history.undo, dry_run previews, expect.sha256 refuses a file changed since; tracked: false deletes plainly (a change the writer approved). STALE_ADDRESS if the paragraph changed since it was read. Use text.replace to delete part of a paragraph, doc.batch for several edits all or nothing. Returns the address_map of shifted paragraphs.

Input parameters:

- `at` (string, required): Paragraph p3#fp or run p3#fp..p5#fp.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `tag` (string): Appended to the author.
- `tracked` (boolean): true (default): edits are tracked changes. false: a plain write, for a change the writer already approved: built as the tracked edit, then this call's own revisions are accepted in the same verified…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `para_get` (~208 tokens)

Read one paragraph in full by address (p142#a7b2a7b2, or fn3/p1#… in a note): text, style, direct paragraph formatting, character-formatted spans (bold, italic, font...) and layout facts (heading_level is null for a body paragraph, 1-9 for a heading). Use it before editing a paragraph or to check its formatting; para.list reads many paragraphs, style.get a style. Read-only; a very long text is paged (limit characters, `cursor` from the reply's `next`). STALE_ADDRESS if it changed since read.

Input parameters:

- `at` (string, required): Paragraph address from a read result, e.g. p142#a7b2a7b2.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most characters of text to return.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `para_insert` (~495 tokens)

Insert one new paragraph with `text` after or before an existing one (give `after` or `before`, e.g. p3#a1b2c3d4), as a tracked insertion (style it with style.apply in doc.batch). File engine: backed up first, logged for history.undo, dry_run previews, expect.sha256 refuses a changed file. tracked: false inserts plainly, on either engine. engine live inserts in the open Word/LibreOffice window (after live.open). Use doc.batch for several edits all or nothing. Returns the address_map of shifted paragraphs.

Input parameters:

- `after` (string): Insert after this paragraph (or before).
- `author` (string): Revision author.
- `before` (string): Insert before this paragraph (or after).
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `engine`: `live` edits the document open in Word or LibreOffice (see live.open).
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `tag` (string): Appended to the author.
- `text` (string, required): Text of the new paragraph.
- `tracked` (boolean): true (default): edits are tracked changes. false: a plain write, for a change the writer already approved: built as the tracked edit, then this call's own revisions are accepted in the same verified…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `para_list` (~318 tokens)

List every paragraph in document order with its address, text, style, heading level and layout facts (centred, page break before, blank paragraphs before, empty, table cell); heading_level is null for a body paragraph. normalized adds normalised text, pages the estimated page, stories notes or headers. Use it to index or scan a whole document; para.get reads one paragraph with formatting, doc.outline only headings, text.find searches. Read-only; paged: about 8,000 bytes per reply (limit up to 1,000), continue with `cursor` set to the reply's `next`.

Input parameters:

- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `normalized` (boolean): Add `normalized`, the text as `text.normalize` gives it.
- `pages` (boolean): Add the estimated `page` and `page_label` per paragraph, and `page_basis`, `rendered_marks` and `page_count` (see page.estimate).
- `stories` (array): Stories to read (default ["body"]): "notes" adds footnotes and endnotes, each after the paragraph holding its mark; "headers" adds headers and footers, after the body. Their addresses carry a prefix…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `para_locate` (~321 tokens)

Say where paragraphs are, in terms a person can follow: for one paragraph, a range (p40#a1b2c3d4:0..p45#e5f6a7b8:0, offsets ignored) or the whole document, each one's chapter, scene, estimated page, shortest unique first … last words phrase, and a phrase Word's Find meets exactly once (for Ctrl+F or live.select). A phrase can be null with a `reason` (empty, duplicate). structure: false skips chapter detection for speed. Use it to tell a writer where a passage is; page.estimate gives only pages. Read-only; paged at about 8,000 bytes with `cursor`.

Input parameters:

- `at` (string): Paragraph address or range (offsets ignored), e.g. p142#a7b2a7b2; default the whole document (empty paragraphs left out).
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `overrides` (array): Force or deny a chapter or scene at paragraphs by fingerprint.
- `profile` (object): Detection settings for this document.
- `structure` (boolean): false: skip chapter, part and scene detection (they come back null), for callers that keep their own structure; faster on a single paragraph. Default true.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `recipe_list` (~62 tokens)

List the cleanup recipes recipe.run can apply: the bundled ones and the user's own, each with name, version, description and source, plus any recipe files that failed validation and why. Call it to pick a recipe before recipe.run. Read-only; needs no document.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `recipe_run` (~175 tokens)

Run a cleanup recipe (a name from recipe.list, or an inline `definition`) over the document and return the edits it proposes as a ready doc.batch request (`batch`, guarded by expect.sha256), repeating until the text is stable. Writes nothing itself: review the proposal, then apply it with doc.batch (dry_run there to preview). RECIPE_INVALID for an unknown or invalid recipe; edits that would touch unsupported content are listed under skipped. Refused for .txt/.md/.odt sources, which cannot be written.

Input parameters:

- `definition` (object): An inline declarative recipe (same schema as a stored one).
- `doc` (string, required): Path to the .docx file.
- `dry_run` (boolean): Accepted for symmetry; recipe.run never writes.
- `recipe` (string): Name from recipe.list.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `revision_accept` (~286 tokens)

Accept tracked changes, making them plain text: by `ids` (from revision.list), by revision `author`, in a paragraph or run (`at`, p3#a1b2c3d4..p9#e5f6a7b8), or `all`. Not itself a tracked change: on the file engine the file is backed up first and the write is logged for history.undo; dry_run previews counts; expect.sha256 refuses a changed file. Use revision.reject to drop changes instead, doc.batch to accept and reject in one write. Returns counts and the revisions resolved.

Input parameters:

- `all` (boolean): true: every revision in the document.
- `at` (string): Paragraph p3#fp or run p3#fp..p9#fp.
- `author` (string): Exact revision author.
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `ids` (array): Accept the revisions with these ids (from revision.list).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `revision_list` (~271 tokens)

List the tracked changes (insertions, deletions, moves, formatting, table rows and cells) in document order, each with id, type, author, date, paragraph address and text (clipped at 200 characters). Filter by `author` or `type`; stories notes or headers adds their changes. Use the ids with revision.accept or revision.reject; doc.stats counts changes by type. Read-only; paged: about 8,000 bytes per reply (limit up to 1,000), continue with `cursor` set to the reply's `next`.

Input parameters:

- `author` (string): Only this author.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `stories` (array): Stories to list (default ["body"]): "notes" adds footnotes and endnotes, "headers" headers and footers, after the body's revisions; their addresses carry a prefix (fn3/p1#…, hdr1.default/p1#…). Row a…
- `type` (string): Only this type.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `revision_reject` (~297 tokens)

Reject tracked changes, undoing them in the text (insertions removed, deletions restored): by `ids` (from revision.list), by revision `author`, in a paragraph or run (`at`, p3#a1b2c3d4..p9#e5f6a7b8), or `all`. Not itself a tracked change: on the file engine the file is backed up first and the write is logged for history.undo; dry_run previews counts; expect.sha256 refuses a changed file. Use revision.accept to keep changes instead, doc.batch to accept and reject in one write. Returns counts and the revisions resolved.

Input parameters:

- `all` (boolean): true: every revision in the document.
- `at` (string): Paragraph p3#fp or run p3#fp..p9#fp.
- `author` (string): Exact revision author.
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `ids` (array): Reject the revisions with these ids (from revision.list).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `section_list` (~112 tokens)

List each section's page setup: break type, title page, even/odd headers, page-number start and format, and which header and footer story (hdr1.default, ftr2.first...) it uses per kind; null means inherited. Use the story keys to read header text with para.list (stories headers) or para.get; layout.get gives the same setup with the header and footer text, in the form layout.apply takes. Read-only.

Input parameters:

- `doc` (string, required): Path to the .docx.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `server_capabilities` (~86 tokens)

Describe this doc-control server: API and package version, engines (file, live), which live apps are reachable, feature support per engine, every operation with its kind and summary, every error code, and whether it is a frozen build. Call it once at the start of a session; server.help gives one operation's full request and result schemas. Read-only; needs no document.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `server_help` (~133 tokens)

Get the JSON Schemas of one operation (`op`, e.g. "para.get": its request schema and its result's output_schema), or of every operation when `op` is left out. A doc.batch kind ("comment.reply", "para.move"...) gives the fields it takes inside `ops`, under batch_operations; a name that is both gets both. Old names (before 0.7.0) give the renamed operation. Call it before using an operation whose fields you are unsure of; server.capabilities lists the operations. Read-only.

Input parameters:

- `op` (string): Operation or batch operation kind.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `style_create` (~419 tokens)

Add a new paragraph, character, table or numbering style with its parent (based_on), next and linked styles and run and paragraph formatting (e.g. run {"bold": true, "size_pt": 14}; paragraph in twips). `id` defaults from `name`. Refused (STYLE_CONFLICT) when the id or name exists: use style.update to change a style, style.rename to rename one, styleset.install to add many. Not a tracked change, but history.undo takes it back: the file is backed up first (`backup`); dry_run previews.

Input parameters:

- `based_on` (string): Parent style id or name; '' for none.
- `custom` (boolean): A user-defined (not built-in) style.
- `default` (boolean): The default style of its type.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `id` (string): Style id (default: from the name).
- `link` (string): Linked paragraph/character style; '' for none.
- `name` (string, required): Display name.
- `next` (string): Style of the next paragraph; '' for none.
- `paragraph` (object): Paragraph formatting (twips): alignment, indent{left,right,first_line,hanging}, spacing{before,after,line,line_rule}, keep_next, keep_lines, page_break_before, outline_level; null removes a key.
- `quick` (boolean): Show in the quick-style gallery.
- `run` (object): Character formatting: bold, italic, small_caps, strike, underline, font, size_pt, color, highlight, vertical_align; null removes a key.
- `type`: Kind of style (default paragraph).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `style_get` (~122 tokens)

Read one style by id or name ("Heading1" or "heading 1"): its type, parent chain, next and linked styles, how often it is used, heading level, the formatting it sets itself and its effective formatting after inheritance. Use it before style.update, or to see why text looks as it does; style.list lists all styles, para.get a paragraph's direct formatting. Read-only; STYLE_NOT_FOUND for an unknown style.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `style` (string, required): Style id or name.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `style_list` (~197 tokens)

List the styles defined in the document with id, name, type, parent and how many times each is used in the body. `type` keeps one kind (paragraph, character, table, numbering); used_only drops unused ones. Use it to find styles to clean up or reuse; style.get gives one style's full and inherited formatting. Read-only; paged: about 8,000 bytes per reply (limit up to 1,000), continue with `cursor` set to the reply's `next`.

Input parameters:

- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string, required): Path to the .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `type` (string): Only this kind of style.
- `used_only` (boolean): Only styles used at least once.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `style_merge` (~221 tokens)

Merge styles: re-point every use of the `from` style or styles (ids or names) to `into` in the body, notes, headers and other styles, then delete the `from` styles. Use it to clean up duplicate styles; style.rename only renames, style.update changes formatting. Not a tracked change, but history.undo takes it back; it removes style definitions, so the file is backed up first (`backup`); check with dry_run first. STYLE_NOT_FOUND for an unknown style.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `from` (required): Style id or name, or a list of them (they are removed).
- `into` (string, required): Style id or name.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `style_rename` (~217 tokens)

Rename a style's display `name` and/or `id` (id defaults from the name); every reference follows: paragraphs, runs, headers, notes, numbering, and based_on, next and link in other styles. STYLE_CONFLICT if the new name or id is taken. Use style.merge to fold one style into another instead. Not a tracked change, but history.undo takes it back: the file is backed up first (`backup`); dry_run previews.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `id` (string): Style id (default: from the name).
- `name` (string): Display name.
- `style` (string, required): Style id or name.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `style_update` (~355 tokens)

Change an existing style: its parent (based_on), next or linked style, quick-gallery flag, or run and paragraph formatting (e.g. run {"italic": true, "font": "Garamond"}; null removes a key; other keys stay). Every paragraph using the style changes with it; to format one paragraph use format.apply in doc.batch. Not a tracked change, but history.undo takes it back: the file is backed up first (`backup`); dry_run previews. STYLE_NOT_FOUND for an unknown style.

Input parameters:

- `based_on` (string): Parent style id or name; '' for none.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `link` (string): Linked paragraph/character style; '' for none.
- `next` (string): Style of the next paragraph; '' for none.
- `paragraph` (object): Paragraph formatting (twips): alignment, indent{left,right,first_line,hanging}, spacing{before,after,line,line_rule}, keep_next, keep_lines, page_break_before, outline_level; null removes a key.
- `quick` (boolean): Show in the quick-style gallery.
- `run` (object): Character formatting: bold, italic, small_caps, strike, underline, font, size_pt, color, highlight, vertical_align; null removes a key.
- `style` (string, required): Style id or name.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `styleset_export` (~143 tokens)

Export the document's style definitions (all, the `styles` named, or custom_only) as a JSON style set, returned and also written to `out` when given (never overwrites an existing file). Feed it to styleset.install on another document to copy the look; styleset.export_dotx writes a Word template instead. The document is not changed.

Input parameters:

- `custom_only` (boolean): Only user-defined styles.
- `doc` (string, required): Path to the .docx.
- `name` (string): Set name.
- `out` (string): Also write the JSON here.
- `styles` (array): Only these styles (ids or names).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `styleset_export_dotx` (~158 tokens)

Write the document's style definitions (all, the `styles` named, or custom_only) to a new .dotx Word template at `out` (must not exist), for Word users to attach or base documents on. The document itself is never changed. styleset.import_dotx brings a template's styles into a document; styleset.export gives a JSON style set instead. Returns the path, style count and size.

Input parameters:

- `custom_only` (boolean): Only user-defined styles.
- `doc` (string, required): Path to the .docx.
- `name` (string): Set name.
- `out` (string, required): New .dotx to create.
- `styles` (array): Only these styles (ids or names).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `styleset_import_dotx` (~215 tokens)

Install the styles of a .dotx Word template (`path`) into the document: new styles are added, existing ids replaced (on_conflict replace, default), kept (skip) or refused (error: STYLE_CONFLICT). Use it to apply a house template; styleset.install takes a JSON style set, style.create adds one style. Not a tracked change, but history.undo takes it back: the file is backed up first (`backup`); dry_run previews.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `on_conflict`: A style id already in the document (default replace).
- `path` (string, required): The .dotx to read.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `styleset_install` (~238 tokens)

Install a JSON style set (inline `styleset`, or a file at `path`, as styleset.export writes it) into the document: new styles are added, existing ids replaced (on_conflict replace, default), kept (skip) or refused (error: STYLE_CONFLICT). Use it to copy styles between documents; styleset.import_dotx reads a Word template, style.create adds one style. Not a tracked change, but history.undo takes it back: the file is backed up first (`backup`); dry_run previews.

Input parameters:

- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `on_conflict`: A style id already in the document (default replace).
- `path` (string): Or a style set file.
- `styleset` (object): The style set (inline).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `table_get` (~239 tokens)

Read one table's grid: rows of cells, each with its first grid column, span, vertical merge (null, restart, continue) and the addresses of its paragraphs (read their text with para.get or para.list). Name it by `table` (1-based, body only, the `cell.table` para.list shows) or by `at`, any paragraph address in it, also in notes and headers. Use it before table.row.insert/delete or to edit cells with text.replace. Read-only; ANCHOR_NOT_FOUND for no such table.

Input parameters:

- `at` (string): Instead of `table`: any paragraph address in the table, in any story (`fn3/p1#…`, `hdr1.default/p1#…`); the reply's `table` is then numbered within that story.
- `doc` (string, required): Path to the .docx.
- `table` (integer): Table number in the main story, 1-based in document order (the `cell.table` of a paragraph row); text-box and DrawingML tables are not counted. Tables of notes, headers and footers are reached with `…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `table_row_delete` (~250 tokens)

Delete one row of a body table (`table` as table.get numbers it, `row` 1-based) as a tracked change the writer can reject. Refused (UNSUPPORTED_CONTENT) for a row with a vertically merged cell. Backed up and logged for history.undo; dry_run previews; expect.sha256 refuses a changed file. Use doc.batch to change several rows at once, para.delete for paragraphs outside tables.

Input parameters:

- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `row` (integer, required): The row, 1-based.
- `table` (integer, required): Table number as `table.get` numbers it: 1-based, in document order, main story only (a table in a note, header or footer cannot be named here).
- `tag` (string): Appended to the author.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `table_row_insert` (~310 tokens)

Insert a row into a body table as a tracked change: after row `after_row` (0: before the first), with one text per cell in `cells` (e.g. ["2024", "12.5"]), matching the row it follows (SCHEMA_INVALID otherwise). Refused (UNSUPPORTED_CONTENT) between two rows a vertical merge spans. Backed up and logged for history.undo; dry_run previews; expect.sha256 refuses a changed file. Use doc.batch for several rows at once.

Input parameters:

- `after_row` (integer, required): Insert after this row (1-based); 0 inserts before the first row.
- `author` (string): Revision author.
- `cells` (array, required): One text per cell of the row it follows (the first row for after_row 0); a horizontally merged cell is one cell.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `table` (integer, required): Table number as `table.get` numbers it: 1-based, in document order, main story only (a table in a note, header or footer cannot be named here).
- `tag` (string): Appended to the author.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `term_rename` (~636 tokens)

Rename a term or a character's name everywhere (Lucía to Elena) as one tracked batch: whole words by default, case-insensitive with case preserved (LUCÍA becomes ELENA), with optional inflections (["s"] also renames Lucías) and ignore_accents. match_level default/loose folds case, accents and punctuation. Matches inside fields or links are left and counted. Run dry_run first to check counts and the preview, then the same request without it. Use it instead of many text.replace edits. Backed up, undoable; tracked: false writes plainly.

Input parameters:

- `author` (string): Revision author.
- `case_sensitive` (boolean): Exact spelling only; `to` is used as written.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Plan and preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `from` (string, required): Term to rename.
- `ignore_accents` (boolean): `Lucia` also matches `Lucía`.
- `inflections` (array): Suffixes that follow the term: "s" renames Lucías to Elenas; {"from": "o", "to": "a"} renames Lucíao to Elenaa.
- `match_level` (string): Fold the search as `text.find` does: `default` ignores case, accents and punctuation (symbols such as `$` still count), `loose` drops symbols too; `exact` is the rename as without it. When passed, Wo…
- `preserve_case` (boolean): LUCÍA becomes ELENA, lucía becomes elena, Lucía becomes Elena.
- `preview_limit` (integer): Most changes echoed back in `preview`.
- `tag` (string): Appended to the author.
- `to` (string, required): New term.
- `tracked` (boolean): false: a plain write for a change the writer already approved: the same edits, with this call's own revisions accepted in the same verified write (no revision marks; undo works). Refused (UNSUPPORTED…
- `whole_word` (boolean): Whole words only.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `text_find` (~599 tokens)

Find text or a Python regex (`regex`) in the document; each match comes back as a range (p8#b72009e4:0-8) with context, ready for text.replace or anchor.create. ignore_case, ignore_accents (maria finds María), whole_word and match_level (default: also ignores punctuation) loosen it; stories adds notes or headers. The first page gives `total`, and query_word, the query as typed in Word's Find box. Use para.locate to describe where a paragraph is, history.search for removed text. Read-only; limit (default 20) per page, `cursor` continues; stops after 10 s with `more`.

Input parameters:

- `context` (integer): Characters of surrounding text on each side of a match.
- `cursor` (string): The `next` value of the previous page.
- `doc` (string, required): Path to the .docx file.
- `ignore_accents` (boolean): Ignore accents: `maria` finds `María`. A regex must be written without accents then.
- `ignore_case` (boolean): Ignore case.
- `limit` (integer): Most matches to return; more are reached with `cursor`.
- `match_level` (string): Fold a literal query: `default` ignores case, accents and punctuation (symbols such as `$` still count), `loose` drops symbols too; `exact` is the search as without it. When passed, Word's Find codes…
- `query` (string, required): Text to find (at most 1000 characters), or a Python regex if `regex`.
- `regex` (boolean): Treat `query` as a regex.
- `stories` (array): Stories to read (default ["body"]): "notes" adds footnotes and endnotes, each after the paragraph holding its mark; "headers" adds headers and footers, after the body. Their addresses carry a prefix…
- `whole_word` (boolean): Match only whole words (no letter, digit or mark on either side).

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `text_normalize` (~238 tokens)

Normalise text (NFC, soft hyphens and zero-width characters removed, whitespace collapsed) and return a two-way offset map to the original code points, for the whole document, one paragraph or range (`at`, p142#a7b2a7b2:10-15), or a `text` string (give `doc` or `text`). Use it to compare or search text robustly and map found offsets back to addresses for text.replace; para.list normalized adds just the text. Read-only; paged at about 8,000 bytes with `cursor`; a very long paragraph comes in parts.

Input parameters:

- `at` (string): Paragraph address or range, e.g. p142#a7b2a7b2:10-15.
- `cursor` (string): The `next` value of the previous response, to continue the list.
- `doc` (string): Path to a .docx.
- `limit` (integer): Most items to return; a response is also capped at about 8,000 bytes.
- `text` (string): Text to normalise instead of `doc`.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `text_replace` (~550 tokens)

Replace one range with new text, as a tracked change: part of a paragraph (p3#a1b2c3d4:10-15, code-point offsets), a whole paragraph (p3#a1b2c3d4) or a span across paragraphs (p3#a1b2c3d4:10..p5#e5f6a7b8:2); empty `text` deletes it. Get ranges from text.find or para.get. File engine: backed up first, logged for history.undo, dry_run previews, expect.sha256 refuses a changed file. tracked: false writes plainly, on either engine. engine live edits the open app. STALE_ADDRESS if the paragraph changed since read. Use term.rename for a name everywhere, doc.batch for several edits.

Input parameters:

- `at` (string, required): Range p3#fp:10-15, p3#fp or p3#fp:10..p5#fp:2.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx (live: or the file name of one open in a live app).
- `dry_run` (boolean): Preview only; write nothing.
- `engine`: `live` edits the document open in Word or LibreOffice (see live.open).
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `tag` (string): Appended to the author.
- `text` (string, required): New text; empty deletes the range.
- `tracked` (boolean): true (default): edits are tracked changes. false: a plain write, for a change the writer already approved: built as the tracked edit, then this call's own revisions are accepted in the same verified…

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `toc_insert` (~281 tokens)

Insert a table of contents (a TOC field) after a body paragraph `after` (p1#a1b2c3d4), as tracked insertions: a title (default "Contents", "" for none), the field listing heading levels `levels` (default "1-3") with a placeholder result, and a closing paragraph. Run field.update next to fill in the entries and page numbers (until then field.list shows it stale). Backed up and undoable (history.undo); dry_run previews. Returns the field paragraph's address.

Input parameters:

- `after` (string, required): Address of the body paragraph it goes after.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `levels` (string): Heading levels it lists, e.g. "1-3" (default) or "2".
- `tag` (string): Appended to the author.
- `title` (string): Title paragraph above it (default "Contents"); "" for none.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

### `xref_add` (~308 tokens)

Insert a cross-reference field at an insertion point (`at`: p14#a1b2c3d4:20-20, or a paragraph address for its end), pointing `to` a caption or another body paragraph. show number (default: "Table 3" for a caption), page (a PAGEREF) or text. The target gets a hidden bookmark when it has none; run field.update afterwards to fill in the shown value. A tracked insertion; backed up and undoable (history.undo); dry_run previews. Add captions with caption.add first.

Input parameters:

- `at` (string, required): Insertion point: p<n>#<fp>:<k>-<k>, or a paragraph address for its end.
- `author` (string): Revision author.
- `doc` (string, required): Path to the .docx.
- `dry_run` (boolean): Preview only; write nothing.
- `expect` (object): Write guard: the sha256 read earlier (doc.info, or a reply's doc.sha256), e.g. {"sha256": "9f86d081…"}; a file changed since is DOC_CHANGED_ON_DISK.
- `show`: number (default): a caption's label and number, or a paragraph's number; page: the page it is on; text: the caption or paragraph text.
- `tag` (string): Appended to the author.
- `to` (string, required): Address of the caption or heading it points at.

Output parameters:

- `api` (string): API version (1.0).
- `doc` (object): The document read or written: path, sha256 and paragraph count.
- `next` (string|null): Cursor for the next page (pass as cursor), or null.
- `ok` (boolean): Always true on success.
- `op` (string): The operation that ran.
- `result` (object): What the operation returns.
- `warnings` (array): Things to know about the result.

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/nuncaeslupus-doc-control/doc-control#diagnostics

## Score history

- 2026-10-17: 75
- 2026-10-16: 75
- 2026-10-15: 59

## Common questions

### What is the doc-control MCP server?

doc-control is an MCP server listed in the public MCP registry as io.github.nuncaeslupus/doc-control. Edit Word documents by address: tracked changes, comments, anchors, with backups and undo. This page covers its PyPI package (doc-control).

### Is the doc-control MCP server safe to use?

doc-control scores 75 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 17 October 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the doc-control MCP server expose?

doc-control exposes 67 tools: anchor_create, anchor_resolve, caption_add, comment_add, comment_list, and 62 more. Their descriptions and schemas cost roughly 18,102 tokens of context every time the server is loaded.

### Is the doc-control MCP server still maintained?

doc-control is still listed as active in the MCP registry. We last reached this channel on 17 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the doc-control MCP server under?

doc-control declares the Apache-2.0 licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- PyPI project: https://pypi.org/project/doc-control/
- Socket report: https://socket.dev/pypi/package/doc-control
- Repository: https://github.com/nuncaeslupus/doc-control
- Changelog RSS feed: https://verifymcp.io/servers/nuncaeslupus-doc-control/doc-control.xml
- Changelog JSON feed: https://verifymcp.io/servers/nuncaeslupus-doc-control/doc-control.json
- HTML version of this page: https://verifymcp.io/servers/nuncaeslupus-doc-control/doc-control
