> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vespper.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Edit document

> Apply a batch of HTML edits as Word tracked changes

`edit_document` applies a list of edits in one call, each recorded as a Word tracked change.
Nothing is overwritten silently: every applied edit stays visible as a redline until a human
accepts or rejects it.

## Parameters

<ParamField path="edits" type="array" required>
  The edits to apply. Maximum 20 per call.

  <Expandable title="edit">
    <ResponseField name="old" type="string" required>
      HTML copied verbatim from the document's current HTML — from
      [`read_document`](/primitives/read-document) or
      [`search_document`](/primitives/search-document) — matching exactly one spot. Prefer a
      whole block: a `<p>`, `<li>`, `<tr>`, or heading.
    </ResponseField>

    <ResponseField name="new" type="string" default="">
      The full replacement HTML for that block, as its final text. Write no `<ins>` or `<del>`
      of your own; the tracked change is derived from `old` → `new`. Use `""` to delete the
      block.
    </ResponseField>
  </Expandable>
</ParamField>

The document and the tracked-change author travel in `_meta`, under `com.vespper/document` and
`com.vespper/author`. See
[Attaching the document](/primitives/overview#attaching-the-document).

## Returns

<ResponseField name="ok" type="boolean">
  `true` whenever the document changed — including a batch where only some edits landed.
</ResponseField>

<ResponseField name="count" type="integer">
  How many edits actually applied.
</ResponseField>

<ResponseField name="base64" type="string">
  The patched `.docx`, base64-encoded. Thread it forward as the next call's document, or decode
  it to save the file.
</ResponseField>

<ResponseField name="bytes" type="integer">
  Size of the patched document.
</ResponseField>

<ResponseField name="failed_edits" type="array">
  Present only on a partial batch. Each entry is `{ index, reason }`, where `index` is 1-based
  over the `edits` you sent.
</ResponseField>

<ResponseField name="message" type="string">
  Agent-facing summary of what landed and what didn't.
</ResponseField>

<ResponseField name="cost_usd" type="number">
  Metered cost of this call.
</ResponseField>

## Edits succeed or fail individually

<Warning>
  A batch can apply in part. The edits counted in `count` are **already saved** in the returned
  document, so never resend the whole batch after a partial result — that re-applies them and
  duplicates the text. Resend only the indexes named in `failed_edits`, fixed, or tell the user
  what could not be changed.
</Warning>

When *nothing* applies you get an error instead and the document is untouched; then fix the
listed edits and resend the whole batch.

## Example

<CodeGroup>
  ```python Python theme={null}
  out = (
      await session.call_tool(
          "edit_document",
          {"edits": [{"old": last_block, "new": last_block + "<p>Hello.</p>"}]},
          meta={
              "com.vespper/document": docx_b64,
              "com.vespper/author": "Vespper Agent",
          },
      )
  ).structuredContent

  docx_b64 = out["base64"]          # thread the patched document forward
  for failure in out.get("failed_edits", []):
      print(failure["index"], failure["reason"])
  ```

  ```typescript TypeScript theme={null}
  const result = await mcp.callTool({
    name: "edit_document",
    arguments: { edits: [{ old: lastBlock, new: `${lastBlock}<p>Hello.</p>` }] },
    _meta: {
      "com.vespper/document": docx_b64,
      "com.vespper/author": "Vespper Agent",
    },
  });

  const out = result.structuredContent as { base64: string };
  docx_b64 = out.base64;
  ```
</CodeGroup>

## Anchoring rules

### Make `old` unique

If a block is identical to another in the document — two blank `Print Name ____` lines, repeated
checkbox rows, a lone `[MAILING ADDRESS]` — it matches several spots and the edit fails with
`multiple matches`. Disambiguate by extending `old` to include a neighbouring block that *is*
unique: a run of consecutive blocks copied verbatim in order, with every change in `new`.

Non-adjacent blocks cannot share one `old`; give them separate edits.

### Lazy classes

Write `class="..."` — literally three dots — for every class attribute in both `old` and `new`.
It means "keep this class", and saves you pasting real class lists.

Spell a class out only when changing the class *is* the edit, to break a `multiple matches` tie,
or on a brand-new inserted block: it has no counterpart in `old` to inherit from, so it must
spell its class out.

<Warning>
  `class="..."` is all-or-nothing. It stands for the entire class value, so it must be the whole
  attribute with nothing else inside or after the quotes. `class="..." bg-[#F6F4EF]"` is invalid
  and fails to locate — to keep a distinguishing class, spell the whole class out for that
  element instead.
</Warning>

### What you cannot anchor on

| Not editable                               | Why                                                                                   |
| ------------------------------------------ | ------------------------------------------------------------------------------------- |
| Headers and footers                        | They live outside the editable body                                                   |
| The `<style>` block at the top of the HTML | Global styles are rejected; reuse an existing class or apply a local one on the block |
| Word-generated list numbers and bullets    | Anchor on the item's content instead; numbering re-flows automatically                |

## Batching

Send all of a request's edits in one call when it makes sense — they reconcile in parallel, so a
10-edit call takes about as long as one edit, while 10 separate calls are roughly 10× slower.

Every edit must target a different block. Two edits touching the same block, or the same
consecutive span, overlap and **both** are dropped — combine those changes into a single
`old` / `new` pair.

Keep each edit small. One edit carrying a huge `new` — beyond roughly 10,000 characters — risks
truncation or a timeout and fails the whole edit. When writing a lot of new content, break it
into several edits of a few blocks each, anchored on different neighbouring blocks.

## Tracked changes stay visible

Each applied edit is a redline that stays until it is accepted: inserted text shows underlined,
deleted text shows struck through rather than removed. Re-reading the document after a
successful edit still shows both — that is correct, not a failure. Don't repeat the edit, and
don't re-read just to verify it.

## Resolving an existing tracked change

`read_document` renders pending changes as `<ins>` and `<del>` elements carrying a
`data-author`. To act on one, copy its whole block verbatim into `old`, keeping the wrappers and
their `data-author`, then write `new` as the block's final state.

| Action              | Result in `new`                                                         |
| ------------------- | ----------------------------------------------------------------------- |
| Accept an insertion | Keep its text, drop the `<ins>` wrapper                                 |
| Accept a deletion   | Drop the `<del>` and its text                                           |
| Reject an insertion | Drop the `<ins>` and its text                                           |
| Reject a deletion   | Keep its text, drop the `<del>` wrapper                                 |
| Modify a change     | Keep the wrapper *and* its `data-author`, and edit only the text inside |

Two rules constrain this. First, resolve only the change you were asked about: a block often
carries several pending changes, and every one you were not asked about must appear in `new`
exactly as it stands in `old`. Pending is the default state, so clearing a change nobody
mentioned is a wrong edit even when the resulting words look right. Second, the only `<ins>` and
`<del>` allowed in `new` are the ones already in `old` — never write one of your own, since your
redline is derived and stamped with the author from `_meta`.

Rejecting Jane's insertion:

```html theme={null}
old: <p>The <ins class="..." data-author="Jane">quick brown </ins>fox</p>
new: <p>The fox</p>
```

Accepting Jane's insertion, where Sam's was not mentioned and so stays exactly as it was:

```html theme={null}
old: <p>A <ins class="..." data-author="Jane">big </ins>red <ins class="..." data-author="Sam">fast </ins>car</p>
new: <p>A big red <ins class="..." data-author="Sam">fast </ins>car</p>
```

Modifying Jane's insertion to drop "brown". Her text is edited in place, and the deletion is
recorded inside her insertion automatically:

```html theme={null}
old: <p>The <ins class="..." data-author="Jane">quick brown </ins>fox</p>
new: <p>The <ins class="..." data-author="Jane">quick </ins>fox</p>
```
