> ## 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: Outlook

> The ten read-only tools in the Outlook connector: mail folders, message listing and full-text search, one message or a whole conversation as plain text, attachment content, calendars and calendar views. Covers ids, OData filters, KQL search, cursor paging, body and attachment caps, time zones and the error contract.

All Outlook tools are read-only and are called via `tools/call` over
`POST /outlook/mcp`. The connector reads the connected user's own Exchange Online
mailbox and calendars through Microsoft Graph, and is connected through OAuth with a
Microsoft work or school account (see
[Connect Outlook](https://docs.consile.ai/en/connect/connect-outlook.md)). Your organization needs access to
Outlook (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/outlook/mcp`. Outlook is its own named connector
> with only its own tools. Tool names are namespaced `outlook__<tool>` (e.g.
> `outlook__search_messages`); the bare names are used below for readability. A
> bare name in a request returns `Unknown tool.`
>
> The connector is multi-instance: one instance is one connected mailbox. An
> administrator adds further connections (agreements, **aftaler**, in the portal),
> each served at its own endpoint, e.g. `https://mcp.consile.ai/outlook-2/mcp`, with
> tools named `outlook-2__<tool>`. A user with access to Outlook can call every
> instance of the organization, so every member with Outlook access can read every
> connected mailbox.

For an account with access to Outlook, `tools/list` returns the ten `outlook__*`
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 Microsoft Graph permissions are:

* `Mail.Read` (the signed-in user's own mail)
* `Calendars.Read` (the signed-in user's own calendars)
* `User.Read` (the signed-in user's profile, used to name the connection)
* `offline_access` (keeps the connection alive)

All three Graph permissions are read-only, so a token issued on this consent cannot
send, move or delete mail or change a calendar. On top of that, the connector has no
write tools and Consile sends only read requests to Microsoft Graph. The connect is
rejected if Microsoft's grant does not include the three Graph permissions. Many
organizations require an administrator's consent for `Mail.Read` and
`Calendars.Read`; see
[Connect Outlook](https://docs.consile.ai/en/connect/connect-outlook.md#if-microsoft-asks-for-administrator-approval).

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.

### Data handling

Every tool is a live pass-through: the answer is read from Microsoft Graph while the
call runs and returned to the MCP client. Consile stores no copy of a message, an
attachment or an event, and keeps no index or cache of mailbox content. Arguments and
results are not logged; only technical metadata about the call is (tool, time,
duration, outcome). See [Security & data](https://docs.consile.ai/en/essentials/security-and-data.md).

## Shared conventions

### Ids

Graph addresses a mailbox with opaque ids: a mail folder id, a message id, an
attachment id inside a message, a conversation id, a calendar id and an event id.
Take them from a previous result and pass them back unchanged. They cannot be derived
from names. Instead of a folder id, the well-known names `inbox`, `sentitems`,
`drafts`, `deleteditems`, `archive` and `junkemail` are accepted.

### Paging

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

Rows per page, `1` to `500`. On the listing tools that page.

**cursor** `string`

Opaque continuation token from a previous result's `nextCursor`. Omit it for the
first page and never construct or edit one. Pass it back to the same tool. When
`cursor` is set, every other argument except `pageSize` is ignored, including
`filter`, `folderId` and `timeZone`, and the result has only `count`, `items` and
`nextCursor`.

**nextCursor** `string | null`

Token for the next page, or `null` when the collection is exhausted. There is no
total count.

A cursor that is not a continuation token from a previous call is refused with
`Invalid cursor for Outlook: pass the nextCursor from a previous page of the same tool`.

### Message shape

`list_messages`, `search_messages` and `get_conversation` return messages with these
fields. `get_message` returns the same fields plus more.

**id** `string`

Message id.

**subject** `string | null`

**from** `object | null`

`{ "name", "address" }`.

**to** `array`

Recipients, each `{ "name", "address" }`.

**cc** `array`

Cc recipients, same shape.

**receivedDateTime** `string | null`

ISO 8601, UTC.

**sentDateTime** `string | null`

ISO 8601, UTC.

**isRead** `boolean | null`

**hasAttachments** `boolean | null`

**importance** `string | null`

`low`, `normal` or `high`.

**bodyPreview** `string | null`

A short text preview as Outlook stores it.

**conversationId** `string | null`

The thread id `get_conversation` takes.

**webLink** `string | null`

Link that opens the message in Outlook on the web.

**internetMessageId** `string | null`

The message's `Message-ID` header.

### Bodies

Message and event bodies are requested from Microsoft as plain text, not HTML, and
capped before they are returned: 20,000 characters on `get_message` and
`get_event`, 5,000 characters per message on `get_conversation`. `bodyTruncated` is
`true` when the text was cut. Reading a message does not mark it as read.

### Time zones

**timeZone** `string` (default: Europe/Copenhagen)

On the calendar tools that return events. The time zone event `start` and `end`
are rendered in, e.g. `Europe/Copenhagen` or `UTC`. 1 to 100 characters: letters,
digits, `/`, `_`, `+`, `-`, `.` and spaces.

Message timestamps (`receivedDateTime`, `sentDateTime`) are always UTC.

***

## list\_mail\_folders

Lists the top-level folders of the mailbox (Inbox, Sent Items, Archive, the user's
own folders) with the folder id `list_messages` and `search_messages` take.
Subfolders are not listed; `childFolderCount` shows that a folder has some.

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

**cursor** `string`

**items** `array`

Each item has `id`, `displayName`, `parentFolderId`, `childFolderCount`,
`unreadItemCount` and `totalItemCount`.

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

```json Result (payload, shortened) theme={null}
{
  "count": 2,
  "items": [
    { "id": "<folder id>", "displayName": "Inbox", "parentFolderId": "<folder id>", "childFolderCount": 2, "unreadItemCount": 14, "totalItemCount": 3120 },
    { "id": "<folder id>", "displayName": "Sent Items", "parentFolderId": "<folder id>", "childFolderCount": 0, "unreadItemCount": 0, "totalItemCount": 1874 }
  ],
  "nextCursor": null
}
```

***

## list\_messages

Lists the messages of one folder, newest first (by `receivedDateTime`), in the
[message shape](#message-shape). For free-text search use
[`search_messages`](#search_messages); for the full body use
[`get_message`](#get_message).

**folderId** `string` (default: inbox)

Folder id from `list_mail_folders`, or a well-known name (`inbox`, `sentitems`,
`drafts`, `deleteditems`, `archive`, `junkemail`).

**filter** `string`

OData `$filter` over the message properties, e.g. `isRead eq false`,
`receivedDateTime ge 2026-10-01T00:00:00Z` or
`from/emailAddress/address eq 'name@example.com'`. String literals are
single-quoted. Microsoft can refuse a filter it finds too complex; the `400` text
is returned (see [Errors](#errors)).

**unreadOnly** `boolean`

Only unread messages. Adds `isRead eq false` to any `filter`.

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

**cursor** `string`

The result echoes `folderId` on the first page.

```json Request theme={null}
{
  "name": "outlook__list_messages",
  "arguments": { "unreadOnly": true, "filter": "receivedDateTime ge 2026-11-01T00:00:00Z", "pageSize": 20 }
}
```

```json Result (payload, shortened) theme={null}
{
  "folderId": "inbox",
  "count": 1,
  "items": [
    {
      "id": "<message id>",
      "subject": "Delivery next week",
      "from": { "name": "<name>", "address": "name@example.com" },
      "to": [{ "name": "<name>", "address": "you@example.com" }],
      "cc": [],
      "receivedDateTime": "2026-11-02T08:14:03Z",
      "sentDateTime": "2026-11-02T08:13:58Z",
      "isRead": false,
      "hasAttachments": true,
      "importance": "normal",
      "bodyPreview": "Hi, can you confirm the delivery date for…",
      "conversationId": "<conversation id>",
      "webLink": "https://outlook.office365.com/owa/?ItemID=…",
      "internetMessageId": "<message-id@example.com>"
    }
  ],
  "nextCursor": null
}
```

***

## search\_messages

Full-text search across the whole mailbox, including subfolders, or within one
folder. Returns the [message shape](#message-shape) in relevance order. The results
cannot be sorted or filtered further; use `list_messages` with a `filter` for that.

**query** `string` *required*

Search text. Plain words match subject and body. KQL-style properties narrow the
search, e.g. `from:name`, `subject:invoice`, `hasAttachments:true`,
`received:2026-10-01`. Double quotes are removed from the text. A query that is
empty after that is refused.

**folderId** `string`

Folder id or well-known name to search within. Omit for the whole mailbox.

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

**cursor** `string`

The result echoes `query` (and `folderId`, when given) on the first page.

```json Request theme={null}
{
  "name": "outlook__search_messages",
  "arguments": { "query": "from:name@example.com subject:quote" }
}
```

***

## get\_message

Reads one message in full: the [message shape](#message-shape) plus the body, the
reply-to and bcc recipients and the list of attachments.

**messageId** `string` *required*

Message id from `list_messages`, `search_messages` or `get_conversation`.

**replyTo** `array`

Each `{ "name", "address" }`.

**bcc** `array`

Each `{ "name", "address" }`. Usually only set on messages the user sent.

**body** `string | null`

The body as plain text, at most 20,000 characters.

**bodyTruncated** `boolean`

`true` when the body was cut.

**attachments** `array`

Each item has `id` (the `attachmentId` for
[`download_attachment`](#download_attachment)), `name`, `contentType`, `size` (bytes)
and `isInline`.

```json Request theme={null}
{ "name": "outlook__get_message", "arguments": { "messageId": "<message id>" } }
```

```json Result (payload, shortened) theme={null}
{
  "id": "<message id>",
  "subject": "Delivery next week",
  "from": { "name": "<name>", "address": "name@example.com" },
  "receivedDateTime": "2026-11-02T08:14:03Z",
  "conversationId": "<conversation id>",
  "replyTo": [],
  "bcc": [],
  "body": "Hi,\r\n\r\nCan you confirm the delivery date for order 1042?\r\n\r\nBest regards\r\n<name>",
  "bodyTruncated": false,
  "attachments": [
    { "id": "<attachment id>", "name": "order-1042.csv", "contentType": "text/csv", "size": 2310, "isInline": false }
  ]
}
```

***

## get\_conversation

Reads a whole thread in one call: every message in the mailbox with the given
`conversationId`, oldest first, each in the [message shape](#message-shape) with
`body` (at most 5,000 characters) and `bodyTruncated`. Use `get_message` on one of
them for a longer body or its attachments.

**conversationId** `string` *required*

`conversationId` of any message in the thread.

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

**cursor** `string`

The result echoes `conversationId` on the first page.

```json Request theme={null}
{ "name": "outlook__get_conversation", "arguments": { "conversationId": "<conversation id>" } }
```

***

## download\_attachment

Reads one attachment of a message.

**messageId** `string` *required*

**attachmentId** `string` *required*

Attachment id from `get_message`.

**kind** `string`

`fileAttachment`, `itemAttachment` (an attached Outlook item, e.g. a forwarded
message or an invitation), `referenceAttachment` (a link to a file in OneDrive or
SharePoint) or `unknown`.

**encoding** `string | null`

`utf8` for text-like files, `base64` for everything else, `null` when there is no
content.

**content** `string | null`

The file content. Text-like files (MIME type `text/*`, JSON, XML, YAML, CSV, RTF, or
a text extension such as `.txt`, `.csv`, `.md`, `.html`, `.ics`) come back as UTF-8
text; PDF, Office files, images and other binaries come back base64-encoded.
`null` for an item or reference attachment, together with a `note` explaining why.

The result also has `messageId`, `id`, `name`, `contentType`, `size` and `isInline`.
Attachments larger than 5 MB (5,242,880 bytes) are refused with
`The attachment is <size> bytes, above the 5242880-byte limit of download_attachment`.

```json Request theme={null}
{
  "name": "outlook__download_attachment",
  "arguments": { "messageId": "<message id>", "attachmentId": "<attachment id>" }
}
```

```json Result (payload) theme={null}
{
  "messageId": "<message id>",
  "id": "<attachment id>",
  "name": "order-1042.csv",
  "contentType": "text/csv",
  "size": 2310,
  "isInline": false,
  "kind": "fileAttachment",
  "encoding": "utf8",
  "content": "line;item;quantity\n1;<item>;4\n…"
}
```

***

## list\_calendars

Lists the user's calendars with the calendar id `list_events` takes. The default
calendar has `isDefaultCalendar: true`; the event tools use it when no `calendarId`
is given. Calendars that others have shared with the user can appear in the list,
but the connector only holds `Calendars.Read`, which covers the user's own
calendars, so their events cannot be expected to be readable. The result is a
single page without a cursor.

**Arguments:** none.

**items** `array`

Each item has `id`, `name`, `color`, `isDefaultCalendar`, `canEdit` and `owner`
(`{ "name", "address" }`).

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

```json Result (payload) theme={null}
{
  "count": 1,
  "items": [
    {
      "id": "<calendar id>",
      "name": "Calendar",
      "color": "auto",
      "isDefaultCalendar": true,
      "canEdit": true,
      "owner": { "name": "<name>", "address": "you@example.com" }
    }
  ]
}
```

***

## list\_events

Lists the events in a calendar between two date-times, ordered by start. It is a
calendar view: a recurring meeting appears as its individual occurrences in the
window.

**start** `string` *required*

Window start, ISO 8601 date-time, e.g. `2026-11-01T00:00:00Z` or
`2026-11-01T00:00:00+01:00`.

**end** `string` *required*

Window end, ISO 8601 date-time, after `start` and at most 92 days later.

**calendarId** `string`

Calendar id from `list_calendars`. Omit for the default calendar.

**timeZone** `string` (default: Europe/Copenhagen)

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

**cursor** `string`

**items** `array`

Each event has `id`, `subject`, `start` and `end` (each
`{ "dateTime", "timeZone" }`), `isAllDay`, `isCancelled`, `location` (display
name), `organizer` (`{ "name", "address" }`), `attendees` (each `name`, `address`,
`type` and `response`), `showAs` (free/busy state), `importance`,
`onlineMeetingUrl`, `webLink`, `bodyPreview`, `seriesMasterId` (set on an
occurrence of a recurring meeting) and `recurrence` (the pattern on a series, or
`null`).

The first page echoes `calendarId` (`default` when omitted), `start`, `end` and
`timeZone`.

```json Request theme={null}
{
  "name": "outlook__list_events",
  "arguments": { "start": "2026-11-02T00:00:00+01:00", "end": "2026-11-07T00:00:00+01:00" }
}
```

```json Result (payload, shortened) theme={null}
{
  "calendarId": "default",
  "start": "2026-11-02T00:00:00+01:00",
  "end": "2026-11-07T00:00:00+01:00",
  "timeZone": "Europe/Copenhagen",
  "count": 1,
  "items": [
    {
      "id": "<event id>",
      "subject": "Status meeting",
      "start": { "dateTime": "2026-11-03T10:00:00.0000000", "timeZone": "Europe/Copenhagen" },
      "end": { "dateTime": "2026-11-03T10:30:00.0000000", "timeZone": "Europe/Copenhagen" },
      "isAllDay": false,
      "isCancelled": false,
      "location": "Microsoft Teams Meeting",
      "organizer": { "name": "<name>", "address": "you@example.com" },
      "attendees": [
        { "name": "<name>", "address": "name@example.com", "type": "required", "response": "accepted" },
        { "name": "<name>", "address": "other@example.com", "type": "required", "response": "none" }
      ],
      "showAs": "busy",
      "importance": "normal",
      "onlineMeetingUrl": "https://teams.microsoft.com/l/meetup-join/…",
      "webLink": "https://outlook.office365.com/owa/?itemid=…",
      "bodyPreview": "Weekly status.",
      "seriesMasterId": "<event id>",
      "recurrence": null
    }
  ],
  "nextCursor": null
}
```

***

## get\_event

Reads one event in full: the `list_events` fields plus `body` (the invitation text
as plain text, at most 20,000 characters) and `bodyTruncated`. An occurrence of a
recurring meeting has its own id; `seriesMasterId` points at the series.

**eventId** `string` *required*

Event id from `list_events` or `list_upcoming_events`.

**timeZone** `string` (default: Europe/Copenhagen)

```json Request theme={null}
{ "name": "outlook__get_event", "arguments": { "eventId": "<event id>", "timeZone": "UTC" } }
```

***

## list\_upcoming\_events

The user's events in the default calendar from now and a number of days ahead,
ordered by start, in the same shape as `list_events`. For another calendar or an
explicit window, use `list_events`.

**days** `integer` (default: 7)

Days ahead from now, `1` to `31`.

**timeZone** `string` (default: Europe/Copenhagen)

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

**cursor** `string`

The first page echoes `start` and `end` (UTC, computed from the time of the call),
`days` and `timeZone`.

```json Request theme={null}
{ "name": "outlook__list_upcoming_events", "arguments": { "days": 3 } }
```

***

## connect\_account

`connect_account` is a system tool that the Outlook endpoint lists next to the ten
tools. It takes no arguments and its name has no `outlook__` 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 Outlook 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, and the mailbox of the account that signs in is the one
the connection reads afterwards.

```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. `list_messages`) or another connector's tool | `Unknown tool.` |
| No access to Outlook (no subscription, and no plan that covers it) | `You are not subscribed to outlook. Purchase it here first: https://app.consile.ai/connectors/outlook …` |
| Access exists, but your user has no seat | `Your company subscribes to outlook through Consile, but your user does not hold a seat on it yet. …` (an administrator assigns seats under **Team**) |
| Microsoft account not connected yet | `outlook is not connected yet. Connect it here: <link>` (link valid 10 minutes; an additional instance names itself, e.g. `outlook-2`) |
| The stored access has expired and could not be renewed | `The Outlook access token has expired. The connection must be re-established from the Consile portal.` |
| Microsoft rejected the token (`401`) | `Outlook rejected the access token (401). The connection must be re-established from the Consile portal.` |
| Microsoft answered `403` | `Outlook 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.` |
| Unknown or invisible id (`404`) | `Outlook 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. a malformed or too complex `filter` | `Outlook rejected the request (400 <code>): <message>` (at most 500 characters) |
| Microsoft is still throttling (`429` or `503`) | `Outlook is throttling requests (Microsoft Graph 429); retried once and still limited. Wait a moment and try again, and ask for fewer rows.` |
| `cursor` is not a continuation token | `Invalid cursor for Outlook: pass the nextCursor from a previous page of the same tool` |
| `query` on `search_messages` is empty once double quotes are removed | `search_messages needs a non-empty search text` |
| `start` or `end` on `list_events` is not an ISO 8601 date-time | `start must be an ISO 8601 date-time, e.g. 2026-10-01T00:00:00Z` (or `end must …`) |
| `end` is not after `start` | `end must be after start` |
| The window is longer than 92 days | `The window is longer than 92 days; ask for a shorter period and page through it` |
| Attachment larger than 5 MB | `The attachment is <size> bytes, above the 5242880-byte limit of download_attachment` |
| Arguments outside the input schema (e.g. `pageSize` above `500`, `days` above `31`, a `timeZone` with other characters), a timeout, another `5xx` or `408` from Microsoft, or a non-JSON response | `Tool invocation failed.` |

## Limits

* Page size: default 50, maximum 500 rows.
* Body text: 20,000 characters on `get_message` and `get_event`, 5,000 per message
  on `get_conversation`.
* Attachments: 5 MB.
* Calendar windows: 92 days on `list_events`, 31 days on `list_upcoming_events`.
* Microsoft Graph throttles requests per user and per organization. Narrow a search
  or a filter instead of paging through a whole mailbox.

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.