<!-- Generated by crates/swarmops-cli/src/reference/mcp_page.rs from the code: do not edit. Change the code, then run SWARMOPS_REGEN_REFERENCE=1 with the regen_reference test. -->

# read_file

Reads one file at a revision, without a checkout, as UTF-8 text or base64, from offset or the lines a range names; pass max\_bytes of 32768 or less (less for a binary file), since clients limit long tool results and this one holds the content twice, and read on from next\_offset while the result is truncated.

```yaml
title: "read_file"
description: "Read a file."
since: "0.1.0"
mutates: false
identity_required: false
cli_command: "swarmops cat"
envelope_op: "read_file"
request_type: "ReadFileReq"
annotations: {"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true}
max_result_chars: 200000
```

## Input schema

`arguments` is a `ReadFileReq`: `swarmops schema ReadFileReq` prints the type. A field the type does not know is ignored.

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `lines` | object or null | no |  | Read these lines instead, numbered from 1, both ends included: a line ends after its newline, and a last line without one is a line too. The lines of the range that exist come back, none past the file's last. Give this or `offset`, not both (`E_INVALID_ARG`). |
| `max_bytes` | integer | no | `1048576` | The most content bytes to return; more comes back truncated, with the `next_offset` to read on from. |
| `offset` | integer | no | `0` | Where to start reading, in bytes from the file's start: a truncated read's `next_offset` reads on from where it stopped. An offset past the end gives empty content. Give this or `lines`, not both (`E_INVALID_ARG`). |
| `path` | string | yes |  | The file's repository path. |
| `rev` | string | yes |  | The revision to read at. A revision as the caller wrote it, for the store to resolve: a ref name (`main`, `release/1.2`), a full 64-hex changeset id or a unique prefix of at least 12 hex digits, optionally followed by `~N` for the N-th first-parent ancestor. |

### JSON Schema

As `tools/list` gives it: each `$ref` replaced by its definition, so that it stands alone.

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "`read_file` (CLI `cat`): read one file at a revision, without a checkout: from an offset, or\na range of its lines.",
  "properties": {
    "lines": {
      "anyOf": [
        {
          "description": "A range of a file's lines, numbered from 1: `first` to `last`, both included.",
          "properties": {
            "first": {
              "description": "The range's first line: at least 1.",
              "format": "uint64",
              "minimum": 1,
              "type": "integer"
            },
            "last": {
              "description": "The range's last line: at least `first`.",
              "format": "uint64",
              "minimum": 1,
              "type": "integer"
            }
          },
          "required": [
            "first",
            "last"
          ],
          "type": "object"
        },
        {
          "type": "null"
        }
      ],
      "description": "Read these lines instead, numbered from 1, both ends included: a line ends after its\nnewline, and a last line without one is a line too. The lines of the range that exist\ncome back, none past the file's last. Give this or `offset`, not both (`E_INVALID_ARG`)."
    },
    "max_bytes": {
      "default": 1048576,
      "description": "The most content bytes to return; more comes back truncated, with the `next_offset` to\nread on from.",
      "format": "uint64",
      "minimum": 0,
      "type": "integer"
    },
    "offset": {
      "default": 0,
      "description": "Where to start reading, in bytes from the file's start: a truncated read's `next_offset`\nreads on from where it stopped. An offset past the end gives empty content. Give this or\n`lines`, not both (`E_INVALID_ARG`).",
      "format": "uint64",
      "minimum": 0,
      "type": "integer"
    },
    "path": {
      "description": "The file's repository path.",
      "type": "string"
    },
    "rev": {
      "description": "The revision to read at. A revision as the caller wrote it, for the store to resolve: a ref name (`main`,\n`release/1.2`), a full 64-hex changeset id or a unique prefix of at least 12 hex digits,\noptionally followed by `~N` for the N-th first-parent ancestor.",
      "type": "string"
    }
  },
  "required": [
    "rev",
    "path"
  ],
  "title": "ReadFileReq",
  "type": "object"
}
```

## Annotations

It only reads the store.

| Annotation | Value | Meaning |
| --- | --- | --- |
| `destructiveHint` | `false` | True: a tool that changes the store may overwrite or delete what is there, not only add to it. |
| `idempotentHint` | `true` | True: calling it again with the same arguments changes nothing more. |
| `openWorldHint` | `false` | True: it reaches beyond a closed set of things. Every tool here stays inside the store. |
| `readOnlyHint` | `true` | True: the tool does not change its environment, the store. |

Its entry in `tools/list` carries `_meta: {"anthropic/maxResultSizeChars": 200000}`: Claude Code keeps a result of up to 200000 characters in the conversation instead of saving it to a file.

## CLI command

[`swarmops cat`](../commands/cat.md) runs the same operation and prints the same envelope.

## Examples

### MCP

```json
{
  "id": 8,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "path": "README.md",
      "rev": "main"
    },
    "name": "read_file"
  }
}
```

```json
{
  "id": 8,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "text": "{\"ok\":true,\"op\":\"read_file\",\"api\":1,\"result\":{\"blob\":\"[blob]\",\"size\":7,\"kind\":\"file\",\"encoding\":\"utf8\",\"content\":\"# acme\\n\",\"truncated\":false},\"meta\":{\"repo\":\"[tmp]/store\",\"elapsed_ms\":\"[elapsed_ms]\",\"resolved\":{\"main\":\"[c1]\"},\"auth\":\"trust\"}}",
        "type": "text"
      }
    ],
    "isError": false,
    "structuredContent": {
      "api": 1,
      "meta": {
        "auth": "trust",
        "elapsed_ms": "[elapsed_ms]",
        "repo": "[tmp]/store",
        "resolved": {
          "main": "[c1]"
        }
      },
      "ok": true,
      "op": "read_file",
      "result": {
        "blob": "[blob]",
        "content": "# acme\n",
        "encoding": "utf8",
        "kind": "file",
        "size": 7,
        "truncated": false
      }
    }
  }
}
```

The server ran as `swarmops mcp --repo [tmp]/store --as agent/worker-17`, after `initialize`. Recorded by the MCP tests. A name in square brackets, such as `[tmp]` or `[c1]`, stands for what differs from run to run: the test's temporary directory, an id, a time, the version.

## Details

Pass `max_bytes` of 32768 or less (less for a binary file, which comes back as base64): the
operation's own default is 1 MiB, and a result holds the content twice.
While the result is truncated, call again with `offset` set to its `next_offset`.
`lines` reads a range of lines, numbered from 1, both ends included, instead of an `offset`;
giving both is `E_INVALID_ARG`.

`read_file` is the one tool that raises Claude Code's result size limit for itself, through its
`_meta` in `tools/list`: see [Result size limits](../../develop/mcp.md#result-size-limits).

## Return information

A tool result: the envelope, whose `op` is `read_file`, as `structuredContent` and again as the text of its one `content` block. `isError` is true exactly when the envelope's `ok` is false. On success the envelope's `result` is what [`swarmops cat`](../commands/cat.md#return-information) returns.

## Errors

A failed call is a tool result with `isError: true`; the envelope's `error.code` is one of the [error codes](../errors.md).

- [`E_INVALID_ARG`](../errors.md#e_invalid_arg): arguments that do not fit `ReadFileReq`, a field named twice among them included, or that break one of the server's own rules.
- [`E_NOT_A_REPO`](../errors.md#e_not_a_repo): the server's store is not there; the server serves on.
- [`E_INTERNAL`](../errors.md#e_internal): the operation panicked, a bug.

Then the errors of the operation, as [`swarmops cat`](../commands/cat.md#errors) lists them.

## See also

- [`swarmops cat`](../commands/cat.md)
- [MCP tools](README.md)
- [MCP integration](../../develop/mcp.md)
- [Error codes](../errors.md)
