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

# Search document

> Regex-match blocks in a document and get back pasteable edit anchors

`search_document` runs a regular expression over the document and returns the matching blocks,
ready to edit — without pulling the whole document into the conversation.

Use it to jump straight to the passages you care about, or to locate text in a document too
large to read whole. The document is converted to the same HTML
[`read_document`](/primitives/read-document) returns, so an anchor from either tool is equally
valid.

## Parameters

<ParamField path="pattern" type="string" required>
  A Python regular expression. `.` matches newlines, so a pattern can span lines.
</ParamField>

<ParamField path="start_at" type="integer" default="1">
  1-based index of the first match to return. Use it to page through a pattern with more
  matches than one call can carry.
</ParamField>

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

### Pattern examples

| Pattern                    | Matches            |
| -------------------------- | ------------------ |
| `Effective Date`           | Literal text       |
| `termination\|terminate`   | Either word        |
| `(?i)confidential`         | Case-insensitively |
| `\$[0-9,]+(?:\.[0-9]{2})?` | Dollar amounts     |

The pattern is matched against both the HTML and the text a reader sees in it. A phrase Word
split across `<span>` runs, or spelled with a non-breaking space, still matches when typed
plainly — and a pattern naming markup (a class, an `<ins>`, a tag) still works. Only the
document's content is searched, never the stylesheet in its `<head>`, so a pattern naming a CSS
class finds the blocks carrying it rather than the rule that defines it.

## Returns

<ResponseField name="matches" type="array">
  The matching blocks.

  <Expandable title="match">
    <ResponseField name="match_text" type="string">
      Verbatim HTML for the complete block the match sits in — the whole `<p>`, `<li>`, `<td>`,
      or heading, tags and all, with nothing cut off. Copy this straight into `edit_document` as
      `old`; it needs no trimming.
    </ResponseField>

    <ResponseField name="matched" type="string">
      What your pattern actually matched inside the block, so you can see why it hit.
    </ResponseField>

    <ResponseField name="page" type="integer">
      1-based page the block falls on, in the same numbering `read_document` uses.
    </ResponseField>

    <ResponseField name="unique" type="boolean">
      `false` when that block's text appears elsewhere in the document too, so pasting it as
      `old` would be ambiguous.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total_matches" type="integer">
  How many spots match in the whole document.
</ResponseField>

<ResponseField name="returned_matches" type="integer">
  How many came back in this call. One call carries a bounded budget of blocks.
</ResponseField>

<ResponseField name="start_at" type="integer">
  Index this page of matches started at.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  `true` when matches remain. Fetch them with `start_at` — for example `start_at=9` after eight
  came back.
</ResponseField>

<ResponseField name="note" type="string">
  One line stating the paging situation, and whether a relaxation was applied. If the pattern
  only matched by ignoring case or collapsing a repeated space, `note` says so.
</ResponseField>

<ResponseField name="cost_usd" type="number">
  Metered cost of this call. Search is billed as a read.
</ResponseField>

Nothing is silently dropped, so you never have to narrow a pattern just to see the rest of its
matches.

## Example

<CodeGroup>
  ```python Python theme={null}
  result = await session.call_tool(
      "search_document",
      {"pattern": "(?i)effective date"},
      meta={"com.vespper/document": docx_b64},
  )
  for match in result.structuredContent["matches"]:
      print(match["page"], match["unique"], match["match_text"])
  ```

  ```typescript TypeScript theme={null}
  const result = await mcp.callTool({
    name: "search_document",
    arguments: { pattern: "(?i)effective date" },
    _meta: { "com.vespper/document": docx_b64 },
  });
  ```
</CodeGroup>

## Duplicate blocks

Two spots that read identically — a running footer, a repeated placeholder — come back as two
matches with the same `match_text`, both marked `unique: false`. That is not a mistake and not a
reason to search again; they differ by `page`, which is how you pick the one you want.

To edit a non-unique block, extend `old` to include a neighbouring block that *is* unique. See
[Making an anchor unique](/primitives/edit-document#make-old-unique).

## Errors

An empty or whitespace-only `pattern` returns `{ error: "invalid_pattern", message }` without
touching the document.
