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

# API reference: Excel

> The twelve read-only tools in the Excel connector: finding workbooks in OneDrive and SharePoint, sheets, used and addressed cell ranges, tables as keyed rows, named items and chart images. Covers workbook addressing, paging, size limits and the error contract.

All Excel tools are read-only and are called via `tools/call` over
`POST /excel/mcp`. The connector reads Excel workbooks stored in OneDrive for work
or school and in SharePoint document libraries through Microsoft Graph, and is
connected through OAuth with a Microsoft work or school account (see
[Connect Excel](https://docs.consile.ai/en/connect/connect-excel.md)). Your organization needs access to Excel
(a subscription, or a plan that covers it, see
[What decides access](https://docs.consile.ai/developers/authentication.md#what-decides-access)), and your
user must hold a seat or plan licence (assigned under **Team** in the portal, see
[Team](https://docs.consile.ai/en/portal/team.md)).

> [!NOTE]
>
> Endpoint: `https://mcp.consile.ai/excel/mcp`. Excel is its own named connector
> with only its own tools. Tool names are namespaced `excel__<tool>` (e.g.
> `excel__find_workbooks`); the bare names are used below for readability. A bare
> name in a request returns `Unknown tool.`
>
> The connector is multi-instance: one instance reads through one connected
> Microsoft user. An administrator adds further connections (agreements,
> **aftaler**, in the portal), each served at its own endpoint, e.g.
> `https://mcp.consile.ai/excel-2/mcp`, with tools named `excel-2__<tool>`. Each
> agreement must be connected with a different Microsoft user.

For an account with access to Excel, `tools/list` returns the twelve `excel__*`
tools plus the unprefixed system tool [`connect_account`](#connect_account). Without
access only `connect_account` is listed, and the `initialize` result includes
`instructions` explaining how to get access. A user without a seat still sees the
tools, but every call is refused. Every tool has a `title` and the annotations
`readOnlyHint: true`, `destructiveHint: false` and `openWorldHint: false`. A
successful result contains the payload twice: as JSON text in `content` and as the
same object in `structuredContent`. The examples below show that payload.

## Authorization and read-only enforcement

The account is connected through Microsoft's OAuth sign-in. Only work or school
accounts can connect; Microsoft refuses a personal Microsoft account. The requested
delegated permissions are:

* `https://graph.microsoft.com/Files.Read.All` (read the files the user can open)
* `https://graph.microsoft.com/User.Read` (the signed-in user's own profile)
* `offline_access` (keeps the connection alive)

Both Graph permissions are read-only, so a token issued on this consent cannot
change a cell or a file, not even outside Consile. In addition:

* The connector has no write tools.
* Consile sends only read requests to Microsoft Graph.
* The connect is rejected if Microsoft's grant does not contain the requested
  permissions, or contains permissions for a resource other than Microsoft Graph.

`Files.Read.All` often requires an administrator's consent in the customer's
Microsoft organization. See
[If Microsoft asks for an administrator](https://docs.consile.ai/en/connect/connect-excel.md#if-microsoft-asks-for-an-administrator).

Access is renewed automatically. Consile never hands the token to the MCP client,
and the MCP client's own token is never forwarded to Microsoft. Workbook contents
are fetched live for each call and are not stored by Consile.

## Shared conventions

### Addressing a workbook

Every tool except `find_workbooks` reads one workbook, named by these arguments:

**driveId** `string`

The drive (OneDrive or SharePoint document library) the workbook lives in. Take it
from a `find_workbooks` result (`driveId`) or from the SharePoint connector's
`list_site_drives`. Omit it for the connected user's own OneDrive.

**itemId** `string`

The workbook file's item id, from `find_workbooks` (`id`) or the SharePoint
connector.

**path** `string`

The workbook's path under the drive root, e.g. `Finance/Budget 2026.xlsx`. Use it
instead of `itemId` when you know the path. A `..` segment is refused.

One of `itemId` or `path` is required; with neither, the call returns
`The workbook must be named by itemId or path`. When both are given, `itemId` wins.
The workbook must be an .xlsx or .xlsm file in OneDrive or SharePoint.

**worksheet** `string`

Worksheet name as shown on the sheet tab, or the worksheet id from
`list_worksheets`. Required on `get_used_range`, `get_range` and `list_charts`,
optional on `list_tables`.

**table** `string`

Table name or table id from `list_tables` or `get_workbook_overview`. Required on
`get_table_columns` and `get_table_rows`.

### Result context

Every result of a workbook-scoped tool echoes `driveId` (`"me"` when you left it
out) and `workbook` (the `itemId` or `path` you passed), and where it applies the
`worksheet`, `table` or `name` that was read. Cell grids (`values`, `text`,
`formulas`) are arrays of rows, each row an array of cells. `@odata.*` annotation
keys are removed from every result.

### Typical call order

1. `find_workbooks`, to get the `id` and `driveId` of the file.
2. `get_workbook_overview`, to see the sheets, tables and named items.
3. `get_table_rows` for data formatted as an Excel table, or `get_used_range` and
   then `get_range` for plain cells.

***

## find\_workbooks

Finds Excel workbooks (.xlsx, .xlsm, .xlsb) the connected user can reach. With
`query` it searches one drive by file name and content: the user's OneDrive when
`driveId` is omitted, or the document library named by `driveId`. Without `query` it
lists the workbooks among the user's recently used files across OneDrive and
SharePoint. Other file types are filtered out of each page.

**query** `string`

Search text: a file name or words in the workbook. Omit it for recent files.

**driveId** `string`

The drive to search. Only used with `query`.

**pageSize** `integer` (default: 50)

Rows per page, `1` to `500`.

**cursor** `string`

`nextCursor` from a previous page of this tool. When set, the other options are
ignored. Never construct or edit one.

**driveId** `string`

The searched drive (`"me"` for the user's OneDrive), or `"recent"` for the recent
files listing.

**items** `array`

Each item has `id` (the `itemId` for the other tools), `name`, `driveId`,
`parentPath` (the folder under the drive root, or `null` when Microsoft does not
report it), `webUrl`,
`lastModifiedDateTime` and `size` (bytes).

**nextCursor** `string | null`

Token for the next page, or `null` when the listing is exhausted. Because
non-workbook files are filtered out after Microsoft returns a page, `count` can be
lower than `pageSize`, even `0`, while `nextCursor` is still set.

```json Request theme={null}
{
  "name": "excel__find_workbooks",
  "arguments": { "query": "budget 2026" }
}
```

```json Result (payload) theme={null}
{
  "driveId": "me",
  "query": "budget 2026",
  "count": 1,
  "items": [
    {
      "id": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
      "name": "Budget 2026.xlsx",
      "driveId": "b!x1Y2z3A4b5C6d7E8f9G0h1I2j3K4l5M6n7O8p9Q0r1S2t3U4",
      "parentPath": null,
      "webUrl": "https://example-my.sharepoint.com/personal/<name>/Documents/Finance/Budget%202026.xlsx",
      "lastModifiedDateTime": "2026-10-28T09:14:02Z",
      "size": 48213
    }
  ],
  "nextCursor": null
}
```

***

## get\_workbook\_overview

Orients in a workbook in one call: its worksheets, its tables and its named items.
Call it first after `find_workbooks`.

**Arguments:** `driveId`, `itemId`, `path` (see [Addressing a workbook](#addressing-a-workbook)).

**worksheets** `object`

`count` and `items`, each with `id`, `name`, `position` and `visibility`
(`Visible`, `Hidden` or `VeryHidden`, as Excel reports it).

**tables** `object`

`count` and `items`, each with `id`, `name`, `showHeaders`, `showTotals`, `style`
and `worksheet` (the sheet's name, or `null` when it could not be resolved).

**namedItems** `object`

`count` and `items`, each with `name`, `type`, `value` (for a range: the address it
points at), `visible` and `comment`.

```json Request theme={null}
{
  "name": "excel__get_workbook_overview",
  "arguments": { "itemId": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567" }
}
```

```json Result (payload) theme={null}
{
  "driveId": "me",
  "workbook": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
  "worksheets": {
    "count": 2,
    "items": [
      { "id": "{00000000-0001-0000-0000-000000000000}", "name": "Budget", "position": 0, "visibility": "Visible" },
      { "id": "{00000000-0001-0000-0100-000000000000}", "name": "Assumptions", "position": 1, "visibility": "Visible" }
    ]
  },
  "tables": {
    "count": 1,
    "items": [
      { "id": "1", "name": "Costs", "showHeaders": true, "showTotals": false, "style": "TableStyleMedium2", "worksheet": "Budget" }
    ]
  },
  "namedItems": {
    "count": 1,
    "items": [
      { "name": "VatRate", "type": "Range", "value": "Assumptions!$B$2", "visible": true, "comment": null }
    ]
  }
}
```

***

## list\_worksheets

Lists the worksheets of a workbook: `id`, `name` (the tab label), `position` and
`visibility`. `get_workbook_overview` returns the same plus tables and names.

**Arguments:** `driveId`, `itemId`, `path`.

**Result:** `driveId`, `workbook`, `count` and `items`.

***

## get\_used\_range

Reads the block of cells a worksheet actually uses, as rows of values. Formulas come
back as their computed values. An empty sheet answers with the `404` error.

**worksheet** `string` *required*

**maxRows** `integer` (default: 200)

Rows to return from the top of the used range, `1` to `1000`.

**address** `string`

The used range, e.g. `Budget!A1:F120`.

**rowCount** `integer`

The full number of rows in the used range, and `columnCount` the number of columns.

**returnedRows** `integer`

Rows actually returned in `values`.

**truncated** `boolean`

`true` when the used range has more rows than `maxRows`. Read the rest with
[`get_range`](#get_range).

**values** `array`

The cell values, row by row.

```json Request theme={null}
{
  "name": "excel__get_used_range",
  "arguments": {
    "itemId": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
    "worksheet": "Assumptions"
  }
}
```

```json Result (payload) theme={null}
{
  "driveId": "me",
  "workbook": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
  "worksheet": "Assumptions",
  "address": "Assumptions!A1:B3",
  "rowCount": 3,
  "columnCount": 2,
  "returnedRows": 3,
  "truncated": false,
  "values": [
    ["Assumption", "Value"],
    ["VAT rate", 0.25],
    ["Growth", 0.04]
  ]
}
```

***

## get\_range

Reads a window of cells by A1 address: the values, the formatted text, and with
`includeFormulas` the formulas.

**worksheet** `string` *required*

**address** `string` *required*

A1-style address of the window, e.g. `A1:D50`, or a single cell such as `B2`. A
window is limited to 1,000 rows by 100 columns per call.

**includeFormulas** `boolean` (default: false)

Also return the formulas of the cells.

**values** `array`

The cell values, row by row. `text` holds the same cells as Excel displays them,
and `formulas` (only with `includeFormulas`) their formulas.

A window larger than the limit is refused with `The range <address> is <rows> rows x <columns> columns; get_range reads at most 1000 rows x 100 columns per call. Read it
in windows.`

```json Request theme={null}
{
  "name": "excel__get_range",
  "arguments": {
    "itemId": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
    "worksheet": "Budget",
    "address": "A1:C3",
    "includeFormulas": true
  }
}
```

```json Result (payload) theme={null}
{
  "driveId": "me",
  "workbook": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
  "worksheet": "Budget",
  "address": "Budget!A1:C3",
  "rowCount": 3,
  "columnCount": 3,
  "values": [["Department", "Q1", "Q2"], ["Sales", 120000, 135000], ["Total", 120000, 135000]],
  "text": [["Department", "Q1", "Q2"], ["Sales", "120.000", "135.000"], ["Total", "120.000", "135.000"]],
  "formulas": [["Department", "Q1", "Q2"], ["Sales", 120000, 135000], ["Total", "=SUM(B2:B2)", "=SUM(C2:C2)"]]
}
```

***

## list\_tables

Lists the Excel tables of a workbook, or of one worksheet when `worksheet` is given.

**worksheet** `string`

Limits the listing to one sheet.

**items** `array`

Each item has `id`, `name`, `showHeaders`, `showTotals`, `style` and `worksheet`.
Here `worksheet` is `null`; use `get_workbook_overview` to see which sheet each
table is on.

***

## get\_table\_columns

Lists the columns of a table in order.

**table** `string` *required*

**items** `array`

Each item has `id`, `name` (the header) and `index`.

***

## get\_table\_rows

Reads the data rows of a table as records keyed by the table's header row. Formulas
come back as their computed values.

**table** `string` *required*

**pageSize** `integer` (default: 100)

Rows per page, `1` to `500`.

**skip** `integer` (default: 0)

Rows to skip. Pass the previous result's `nextSkip`.

**headers** `string[]`

The keys used in each row. An empty header becomes `column_<n>` (its 1-based
position), and a repeated header gets a suffix (`Amount`, `Amount_2`).

**rows** `array`

Each row has `index` (the row's position in the table) and one key per header.
`rowCount` is the number of rows in this page.

**nextSkip** `integer | null`

The `skip` for the next page, or `null` when the page came back shorter than
`pageSize`. When the table ends exactly on a page boundary, the next call returns
no rows and `nextSkip: null`.

```json Request theme={null}
{
  "name": "excel__get_table_rows",
  "arguments": {
    "itemId": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
    "table": "Costs",
    "pageSize": 2
  }
}
```

```json Result (payload) theme={null}
{
  "driveId": "me",
  "workbook": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
  "table": "Costs",
  "headers": ["Department", "Q1", "Q2"],
  "rowCount": 2,
  "rows": [
    { "index": 0, "Department": "Sales", "Q1": 120000, "Q2": 135000 },
    { "index": 1, "Department": "Marketing", "Q1": 64000, "Q2": 71500 }
  ],
  "nextSkip": 2
}
```

***

## list\_named\_items

Lists the workbook's named items: named ranges (`type: "Range"`, `value` is the
address they point at) and named constants.

**Arguments:** `driveId`, `itemId`, `path`.

**Result:** `driveId`, `workbook`, `count` and `items`, each with `name`, `type`,
`value`, `visible` and `comment`.

***

## get\_named\_item\_range

Reads the cells a named range points at. A named constant has its value in
`list_named_items` already and answers an error here.

**name** `string` *required*

The named item's name from `list_named_items`.

**Result:** `driveId`, `workbook`, `name`, `address`, `rowCount`, `columnCount` and
`values`.

***

## list\_charts

Lists the charts on a worksheet.

**worksheet** `string` *required*

**items** `array`

Each item has `id`, `name`, `height`, `width`, `top` and `left`, as Excel reports
them.

***

## get\_chart\_image

Renders one chart as a PNG image, fitted to the requested size. The image is
returned base64-encoded inside the JSON payload. For the numbers behind a chart,
read the sheet's cells instead.

**worksheet** `string` *required*

**chart** `string` *required*

Chart name or chart id from `list_charts`.

**width** `integer` (default: 800)

Image width in pixels, `1` to `2000`.

**height** `integer` (default: 450)

Image height in pixels, `1` to `2000`.

```json Request theme={null}
{
  "name": "excel__get_chart_image",
  "arguments": {
    "itemId": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
    "worksheet": "Budget",
    "chart": "Chart 1"
  }
}
```

```json Result (payload, shortened) theme={null}
{
  "driveId": "me",
  "workbook": "01ABCDEF2GHIJKLMNOPQRSTUVWXYZ234567",
  "worksheet": "Budget",
  "chartId": "Chart 1",
  "width": 800,
  "height": 450,
  "encoding": "base64",
  "mimeType": "image/png",
  "content": "iVBORw0KGgoAAAANSUhEUgAA…"
}
```

***

## connect\_account

`connect_account` is a system tool that the Excel endpoint lists next to the twelve
tools. It takes no arguments and its name has no `excel__` prefix. It returns
`{ "connect_url": "…", "instructions": "…" }`. The user opens `connect_url` in a
browser and is sent to Microsoft to sign in and approve access. The link expires
after 10 minutes. It only works for accounts that subscribe to Excel and for users
who have accepted Consile's terms in the portal. Use it to reconnect after the
access expired or was removed: completing the link connects the account again with
the new Microsoft sign-in.

```json Request theme={null}
{ "name": "connect_account", "arguments": {} }
```

Replacing an existing connection requires a Consile administrator; for other users
the link is refused.

***

## Errors

Tool failures come back as a normal `tools/call` result with `isError: true` and one
text block, not as a JSON-RPC error. The connector returns explanatory messages for
the common failures:

| Situation | Text in the result |
| - | - |
| Bare tool name (e.g. `find_workbooks`) or another connector's tool | `Unknown tool.` |
| No access to Excel (no subscription, and no plan that covers it) | `You are not subscribed to excel. Purchase it here first: https://app.consile.ai/connectors/excel …` |
| Access exists, but your user has no seat | `Your company subscribes to excel through Consile, but your user does not hold a seat on it yet. …` (an administrator assigns seats under **Team**) |
| Microsoft account not connected yet | `excel is not connected yet. Connect it here: <link>` (link valid 10 minutes; an additional agreement names itself, e.g. `excel-2`) |
| The stored access has expired and could not be renewed | `The Excel access token has expired. The connection must be re-established from the Consile portal.` |
| Microsoft rejected the token (`401`) | `Excel rejected the access token (401). The connection must be re-established from the Consile portal.` |
| Microsoft answered `403` | `Excel returned 403. The connected Microsoft user lacks access to this item, or the organisation's administrator has not consented to the permission this tool needs. Reconnecting will not fix it; ask a Microsoft 365 administrator.` |
| The workbook, sheet, table, named item or chart does not exist or is not visible to the user, or the sheet is empty (`404`) | `Excel returned 404: the requested item does not exist or is not visible to the connected user. Resolve ids with the listing or search tools first.` |
| Microsoft rejected the request (`400`), e.g. an address it cannot parse | `Excel rejected the request (400 <code>): <message>` (at most 500 characters) |
| Microsoft's rate limit is still reached (`429`) | `Excel is throttling requests (Microsoft Graph 429); retried once and still limited. Wait a moment and try again, and ask for fewer rows.` |
| Neither `itemId` nor `path` given | `The workbook must be named by itemId or path` |
| `get_range` window above 1,000 rows or 100 columns | `The range <address> is <rows> rows x <columns> columns; get_range reads at most 1000 rows x 100 columns per call. Read it in windows.` |
| `cursor` is not a continuation token from this tool | `Invalid cursor for Excel: pass the nextCursor from a previous page of the same tool` |
| Arguments outside the input schema (e.g. `pageSize` above `500`, `maxRows` above `1000`, `width` above `2000`), a timeout, a `5xx` or `408` from Microsoft, a chart that returns no image, or a non-JSON response | `Tool invocation failed.` |

## Limits

* `find_workbooks`: page size default 50, maximum 500.
* `get_used_range`: default 200 rows, maximum 1,000 rows per call.
* `get_range`: at most 1,000 rows by 100 columns per call.
* `get_table_rows`: page size default 100, maximum 500.
* `get_chart_image`: default 800 × 450 pixels, maximum 2,000 × 2,000.
* Microsoft Graph throttles requests. When a request is throttled, the tool returns
  the `429` error above.

Requests to the endpoint are also rate-limited per account and connector. When the
limit is exceeded you get HTTP `429` (JSON-RPC error `-32029`, `Retry-After: 1`)
instead of a tool result. See [Errors & limits](https://docs.consile.ai/developers/errors-and-limits.md) for
the current limits and the protocol-level errors shared by all connectors.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.