Skip to main content
edit_document applies a list of edits in one call. By default, each edit is recorded as a Word tracked change so a human can accept or reject it.
Suggestion mode lets the user review edits before they change the document.

Parameters

array
required
The edits to apply. Maximum 20 per call.
Pass the document session in _meta under com.vespper/session-id. The optional com.vespper/author field controls the name Word shows on tracked changes, and com.vespper/track-changes controls whether new edits are tracked. Vespper SDK auto-patching supplies these values for you. See Document sessions.

Control tracked changes behavior

By default, edit_document records new edits as Word tracked changes. To apply new edits directly, configure auto-patching or set the value manually:
This setting controls only the new edits made by that call. Existing tracked changes remain unchanged unless an edit explicitly accepts, rejects, or modifies them. When tracking is enabled, inserted text appears underlined and deleted text appears struck through until accepted. That visible text does not mean the edit failed; do not repeat a successful edit.

Returns

boolean
true whenever the document changed - including a batch where only some edits landed.
integer
How many edits actually applied.
string
The latest cumulative session document, base64-encoded. Decode it to save the .docx or publish it to a live editor. Subsequent tool calls automatically use this updated session document.
With auto-patching, retrieve these bytes with getSessionDocument(sessionId) or get_session_document(session_id). With manual wiring, retain and decode the newest base64 value yourself.
integer
Size of the patched document.
integer
Revision of the cumulative session document returned by this call.
array
Present only on a partial batch. Each entry is { index, reason }, where index is 1-based over the edits you sent.
string
Agent-facing summary of what landed and what didn’t.
number
Metered cost of this call.

Edits succeed or fail individually

A batch can apply in part. The edits counted in count are already saved in the session 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.
When nothing applies you get an error instead and the session document is untouched; then fix the listed edits and resend the whole batch.

Example

An unknown, closed, or user-mismatched session returns document session not found without changing a document.

Suggestion mode

When the Vespper SDK patches the MCP client with suggest: true, edit_document doesn’t change the document. It locates each edit in the session document and returns it as a suggestion for the user to review. See Suggestions to enable it and apply the accepted suggestions. When the agent proposes changing “grew steadily” to “grew 12%” in sample.docx, edit_document returns:
boolean
true when at least one edit was proposed.
string
Always "proposed".
integer
How many edits were proposed.
array
Edits that could not be proposed. Each entry is { index, code, reason }, where index is 1-based over the edits you sent.
string
Agent-facing summary. It tells the model the document has not changed, so the model does not re-read the document or resend the edits.
array
The proposed edits.
string
Stylesheet for rendering the suggestions’ HTML: the document’s styles and the utility classes the suggestions use. It is unscoped, so scope it to the element that shows your suggestions.
Keep suggestions and css out of the model’s context. Mastra and Vercel AI SDK patching hides them from the model for you. In a native loop, return only message as the tool output, as the Quickstart does.

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

What you cannot anchor on

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.

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. 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. The tool handles any new revision markup when tracking is enabled. Rejecting Jane’s insertion:
Accepting Jane’s insertion, where Sam’s was not mentioned and so stays exactly as it was:
Modifying Jane’s insertion to drop “brown”. Her text is edited in place. When tracking is enabled, the deletion is recorded inside her insertion: