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

# Read document

> Return a contiguous range of the document's pages as HTML

`read_document` converts the attached `.docx` and returns a contiguous range of its logical
pages as HTML. Every edit anchor must be markup copied verbatim from this tool or from
[`search_document`](/primitives/search-document), so read a spot before you edit it.

Reach for this when you need the *shape* of a document — what sections exist, how a table is
laid out, where a new paragraph belongs. If you already know the text you want to change,
[`search_document`](/primitives/search-document) is faster and cheaper.

## Parameters

<ParamField path="start_page" type="integer" default="1">
  1-based first page to return.
</ParamField>

<ParamField path="end_page" type="integer">
  1-based last page to return. Omit for a default span starting at `start_page`.
</ParamField>

The document itself travels in `_meta` under `com.vespper/document`. See
[Attaching the document](/primitives/overview#attaching-the-document).

## Returns

<ResponseField name="html" type="string">
  The HTML projection of the requested page range. Its last line always states the range in
  words, for example `Pages 3-7 of 42 returned; 35 pages remain.`
</ResponseField>

<ResponseField name="start_page" type="integer">
  First page actually returned.
</ResponseField>

<ResponseField name="end_page" type="integer">
  Last page actually returned. May be lower than the `end_page` you asked for, because a call
  is also capped by a token budget.
</ResponseField>

<ResponseField name="total_pages" type="integer">
  Pages in the whole document.
</ResponseField>

<ResponseField name="approx_tokens" type="integer">
  Approximate token count of the returned `html`.
</ResponseField>

<ResponseField name="total_tokens" type="integer">
  Approximate token count of the whole document.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  `true` when pages remain past `end_page`. Fetch them with `start_page = end_page + 1`.
</ResponseField>

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

<ResponseField name="cost_usd" type="number">
  Metered cost of this call. Reads are billed as conversions, with no LLM involved.
</ResponseField>

## Example

<CodeGroup>
  ```python Python theme={null}
  result = await session.call_tool(
      "read_document",
      {"start_page": 1, "end_page": 3},
      meta={"com.vespper/document": docx_b64},
  )
  html = result.structuredContent["html"]
  ```

  ```typescript TypeScript theme={null}
  const result = await mcp.callTool({
    name: "read_document",
    arguments: { start_page: 1, end_page: 3 },
    _meta: { "com.vespper/document": docx_b64 },
  });
  const { html } = result.structuredContent as { html: string };
  ```
</CodeGroup>

## Paging through a long document

A wide `end_page` is still bounded by the per-call token budget, so on a large document
`has_more` stays `true`. Keep calling with `start_page = end_page + 1` until it is `false`.

Anchors copied from any page match the whole document, so you can edit straight from a page
without re-reading it. Don't page through a document hunting for a string — search for it.

## Existing tracked changes

Changes already in the document render as `<ins>` and `<del>` elements, each carrying a
`data-author`. Use them to see which changes are pending and who made them before you accept,
reject, or modify one — see
[Resolving a tracked change](/primitives/edit-document#resolving-an-existing-tracked-change).

## Errors

Failures return `{ error, message }` rather than raising: `message` is agent-facing copy that
says what to do next. Out of credits returns HTTP 402 upstream and a message that says so; a
missing `_meta` document returns `No document attached.`
