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

> The eight read-only tools in the Teams connector: joined teams, channels, channel messages and replies, and the connected user's chats. Covers the admin-consent requirement, the id chain, cursor paging, the message shape and the error contract.

All Teams tools are read-only and are called via `tools/call` over
`POST /teams/mcp`. The connector reads Microsoft Teams through Microsoft Graph and is
connected through OAuth with a Microsoft work or school account (see
[Connect Teams](https://docs.consile.ai/en/connect/connect-teams.md)). Your organization needs access to Teams
(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/teams/mcp`. Teams is its own named connector with
> only its own tools. Tool names are namespaced `teams__<tool>` (e.g.
> `teams__list_joined_teams`); 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 Microsoft user (that
> user's teams, channels and chats). An administrator adds further connections
> (agreements, **aftaler**, in the portal), each served at its own endpoint, e.g.
> `https://mcp.consile.ai/teams-2/mcp`, with tools named `teams-2__<tool>`.

For an account with access to Teams, `tools/list` returns the eight `teams__*` 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 admin consent

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 all read permissions:

* `https://graph.microsoft.com/Team.ReadBasic.All`
* `https://graph.microsoft.com/Channel.ReadBasic.All`
* `https://graph.microsoft.com/ChannelMessage.Read.All`
* `https://graph.microsoft.com/Chat.Read`
* `https://graph.microsoft.com/User.Read`
* `offline_access` (keeps the connection alive)

`ChannelMessage.Read.All` requires admin consent in the customer's Microsoft Entra
tenant; a regular user cannot grant it. Until an administrator has consented for the
organization, Microsoft stops a regular user's sign-in at its *Need admin approval*
page. The connect is rejected, and nothing is stored, if Microsoft's grant is missing
any of the five Graph permissions or contains a permission for a resource other than
Microsoft Graph. There is no chat-only connection. How the customer gives the consent:
[Administrator consent](https://docs.consile.ai/en/connect/connect-teams.md#administrator-consent).

The connector has no write tools and sends only read requests. Messages are read live
and not stored by Consile. 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.

## Shared conventions

### The id chain

Teams content is addressed by ids a user never types, so the tools are meant to be
called in order:

1. [`list_joined_teams`](#list_joined_teams) returns the `teamId` (a GUID).
2. [`list_channels`](#list_channels) with that `teamId` returns the `channelId` (the
   `19:...@thread.tacv2` form).
3. [`list_channel_messages`](#list_channel_messages) with both returns root message
   ids, and [`list_message_replies`](#list_message_replies) reads one thread.

Chats are separate: [`list_chats`](#list_chats) returns the `chatId` (the
`19:...@unq.gbl.spaces` form, or another `19:` form for meeting chats) that
[`list_chat_messages`](#list_chat_messages) takes.

### Paging

`list_channel_messages`, `list_message_replies`, `list_chats` and
`list_chat_messages` are paged. Each returns `count`, `items` and `nextCursor`
(`null` on the last page). Pass `nextCursor` back as `cursor` to the same tool to get
the next page. When `cursor` is set, the other options (`pageSize`, `since`) are
ignored. `list_joined_teams` and `list_channels` return the whole list in one call.

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

Rows per page, 1 to 50. Microsoft Graph caps message pages at 50. The default is 20
on `list_channel_messages`, `list_chats` and `list_chat_messages`, and 50 on
`list_message_replies`.

**cursor** `string`

The `nextCursor` from a previous page of the same tool.

### Message shape

Channel messages, replies and chat messages share one shape:

**id** `string`

The message id.

**createdDateTime / lastModifiedDateTime** `string | null`

ISO 8601 timestamps.

**deleted** `boolean`

`true` for a deleted message.

**from** `object | null`

`{ "name", "userId" }` for a person, `{ "application" }` for an app or bot, or
`null`.

**messageType** `string | null`

`message` for an ordinary message. System events (members added, calls) have
another value.

**importance / subject** `string | null`

As Teams reports them.

**body** `string`

The message as plain text: HTML is reduced to text, line breaks are kept, and the
text of mentions and attachment placeholders stays. Cut at 10,000 characters.

**bodyTruncated** `boolean`

`true` when `body` was cut. The cap is the same in every tool, including
[`get_channel_message`](#get_channel_message).

**mentions** `array`

Each has `id`, `mentionText` and `userId`.

**attachments** `array`

Each has `id`, `name`, `contentType` and `contentUrl`. The file itself is not
fetched.

**reactionCount** `integer`

The number of reactions.

**replyToId** `string | null`

On a reply, the id of the root message it answers.

**webUrl** `string | null`

A link that opens the message in Teams.

**channelIdentity** `object`

`{ "teamId", "channelId" }`, present only when Microsoft returns it (channel
messages).

***

## list\_joined\_teams

Lists the teams the connected user is a member of. The usual first call. When several
teams could match what the user means, ask instead of guessing.

**Arguments:** none.

**items** `array`

Each item has `id` (the `teamId`), `displayName`, `description`, `isArchived`,
`visibility` (`public` or `private`) and `webUrl`.

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

```json Result (payload) theme={null}
{
  "count": 1,
  "items": [
    {
      "id": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b",
      "displayName": "<team>",
      "description": "<description>",
      "isArchived": false,
      "visibility": "private",
      "webUrl": "https://teams.microsoft.com/l/team/..."
    }
  ]
}
```

***

## get\_team

Reads one team.

**teamId** `string (GUID)` *required*

From [`list_joined_teams`](#list_joined_teams).

**(team fields)**

The same fields as `list_joined_teams`, plus `createdDateTime`.

**summary** `object`

`membersCount`, `ownersCount` and `guestsCount`. Present only when Microsoft reports
it.

```json Request theme={null}
{ "name": "teams__get_team", "arguments": { "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b" } }
```

***

## list\_channels

Lists the channels of a team that the connected user can see.

**teamId** `string (GUID)` *required*

**filter** `string`

Only channels of one membership type: `standard` (open to the whole team),
`private` (a subset of the team) or `shared` (shared across teams).

**items** `array`

Each item has `id` (the `channelId`), `displayName`, `description`,
`membershipType`, `webUrl` and `createdDateTime`. The result also echoes `teamId`.

```json Request theme={null}
{
  "name": "teams__list_channels",
  "arguments": { "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b" }
}
```

```json Result (payload) theme={null}
{
  "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b",
  "count": 1,
  "items": [
    {
      "id": "19:0a1b2c3d4e5f60718293a4b5c6d7e8f9@thread.tacv2",
      "displayName": "General",
      "description": null,
      "membershipType": "standard",
      "webUrl": "https://teams.microsoft.com/l/channel/...",
      "createdDateTime": "2025-03-04T09:12:00Z"
    }
  ]
}
```

***

## list\_channel\_messages

Lists the root messages (conversation starters) of a channel, newest first. Replies
are not included, and their number is not reported: call
[`list_message_replies`](#list_message_replies) with a message id to read a thread.
Microsoft offers no filtering, sorting or search here, so page through with
`nextCursor`. Requires the admin-consented `ChannelMessage.Read.All`. Microsoft
throttles these reads more tightly than other reads, so keep pages small.

**teamId** `string (GUID)` *required*

**channelId** `string` *required*

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

1 to 50.

**cursor** `string`

The result echoes `teamId` and `channelId` and has `count`, `items` (see
[Message shape](#message-shape)) and `nextCursor`.

```json Request theme={null}
{
  "name": "teams__list_channel_messages",
  "arguments": {
    "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b",
    "channelId": "19:0a1b2c3d4e5f60718293a4b5c6d7e8f9@thread.tacv2",
    "pageSize": 10
  }
}
```

```json Result (payload, shortened) theme={null}
{
  "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b",
  "channelId": "19:0a1b2c3d4e5f60718293a4b5c6d7e8f9@thread.tacv2",
  "count": 1,
  "items": [
    {
      "id": "1761900000000",
      "createdDateTime": "2026-10-31T08:40:00Z",
      "lastModifiedDateTime": "2026-10-31T08:40:00Z",
      "deleted": false,
      "from": { "name": "<name>", "userId": "2b3c4d5e-6f70-4a81-92b3-c4d5e6f7a8b9" },
      "messageType": "message",
      "importance": "normal",
      "subject": null,
      "body": "<message text>",
      "bodyTruncated": false,
      "mentions": [],
      "attachments": [],
      "reactionCount": 2,
      "replyToId": null,
      "webUrl": "https://teams.microsoft.com/l/message/...",
      "channelIdentity": {
        "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b",
        "channelId": "19:0a1b2c3d4e5f60718293a4b5c6d7e8f9@thread.tacv2"
      }
    }
  ],
  "nextCursor": "<opaque cursor>"
}
```

***

## get\_channel\_message

Reads one channel message by id: a root message from `list_channel_messages` or a
reply from `list_message_replies`. Useful when the user pasted a message link or
refers to one specific message.

**teamId** `string (GUID)` *required*

**channelId** `string` *required*

**messageId** `string` *required*

Returns one message in the [message shape](#message-shape).

```json Request theme={null}
{
  "name": "teams__get_channel_message",
  "arguments": {
    "teamId": "6a1f3c2e-4b5d-4e7f-9a0b-1c2d3e4f5a6b",
    "channelId": "19:0a1b2c3d4e5f60718293a4b5c6d7e8f9@thread.tacv2",
    "messageId": "1761900000000"
  }
}
```

***

## list\_message\_replies

Lists the replies to a root channel message, i.e. one conversation thread, newest
first. A reply's own `replyToId` also names the root. Each reply has the
[message shape](#message-shape). A thread is usually read whole, so the page defaults
to 50.

**teamId** `string (GUID)` *required*

**channelId** `string` *required*

**messageId** `string` *required*

The root message id.

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

1 to 50.

**cursor** `string`

The result echoes `teamId`, `channelId` and `messageId` and has `count`, `items` and
`nextCursor`.

***

## list\_chats

Lists the connected user's own chats, most recently active first: 1:1 chats
(`chatType` `oneOnOne`), group chats (`group`) and meeting chats (`meeting`). A 1:1
chat has no topic, so identify it by its members.

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

1 to 50.

**cursor** `string`

**items** `array`

Each item has `id` (the `chatId`), `topic` (group chats only), `chatType`,
`members` (each with `name`, `userId` and `email`), `lastUpdatedDateTime` and
`webUrl`.

```json Request theme={null}
{ "name": "teams__list_chats", "arguments": { "pageSize": 5 } }
```

```json Result (payload, shortened) theme={null}
{
  "count": 1,
  "items": [
    {
      "id": "19:2b3c4d5e-6f70-4a81-92b3-c4d5e6f7a8b9_7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f@unq.gbl.spaces",
      "topic": null,
      "chatType": "oneOnOne",
      "members": [
        { "name": "<name>", "userId": "2b3c4d5e-6f70-4a81-92b3-c4d5e6f7a8b9", "email": "name@example.com" },
        { "name": "<name>", "userId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "email": "colleague@example.com" }
      ],
      "lastUpdatedDateTime": "2026-10-31T10:05:00Z",
      "webUrl": "https://teams.microsoft.com/l/chat/..."
    }
  ],
  "nextCursor": "<opaque cursor>"
}
```

***

## list\_chat\_messages

Lists the messages of one chat, newest first. System events (members added, calls)
appear with a `messageType` other than `message`.

**chatId** `string` *required*

From [`list_chats`](#list_chats).

**since** `string`

Only messages modified after this instant. ISO 8601 with `Z` or an offset, e.g.
`2026-10-01T00:00:00Z`. The only filter Microsoft supports here.

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

1 to 50.

**cursor** `string`

The result echoes `chatId` and has `count`, `items` (see
[Message shape](#message-shape)) and `nextCursor`.

```json Request theme={null}
{
  "name": "teams__list_chat_messages",
  "arguments": {
    "chatId": "19:2b3c4d5e-6f70-4a81-92b3-c4d5e6f7a8b9_7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f@unq.gbl.spaces",
    "since": "2026-10-27T00:00:00Z"
  }
}
```

***

## connect\_account

`connect_account` is a system tool that the Teams endpoint lists next to the eight
tools. It takes no arguments and its name has no `teams__` 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 Teams and for users who
have accepted Consile's terms in the portal. Use it to reconnect after the access
expired or was removed. The [admin-consent requirement](#authorization-and-admin-consent)
applies here too.

```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_chats`) or another connector's tool | `Unknown tool.` |
| No access to Teams (no subscription, and no plan that covers it) | `You are not subscribed to teams. Purchase it here first: https://app.consile.ai/connectors/teams …` |
| Access exists, but your user has no seat | `Your company subscribes to teams through Consile, but your user does not hold a seat on it yet. …` (an administrator assigns seats under **Team**) |
| Microsoft account not connected yet | `teams is not connected yet. Connect it here: <link>` (link valid 10 minutes; an additional agreement names itself, e.g. `teams-2`) |
| The stored access has expired and could not be renewed | `The Teams access token has expired. The connection must be re-established from the Consile portal.` |
| Microsoft rejected the token (`401`) | `Teams rejected the access token (401). The connection must be re-established from the Consile portal.` |
| Microsoft answered `403` (the user cannot see the team, channel or chat, or the organization has not consented to the permission) | `Teams 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`) | `Teams 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`) | `Teams rejected the request (400 <code>): <message>` (at most 500 characters) |
| Microsoft is still throttling (`429` or `503`) | `Teams 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 from a previous page | `Invalid cursor for Teams: pass the nextCursor from a previous page of the same tool` |
| Arguments outside the input schema (e.g. `pageSize` above `50`, a `since` that is not an ISO 8601 instant, an unknown `filter` value), a timeout, another `5xx` or `408` from Microsoft, or a non-JSON response | `Tool invocation failed.` |

## Limits

* Page size: 1 to 50 rows on every paged tool (Microsoft Graph's cap on message
  pages). Defaults: 20, and 50 on `list_message_replies`.
* Message text: `body` is cut at 10,000 characters (`bodyTruncated: true`).
* Microsoft throttles channel-message reads more tightly than other Graph reads.
  Prefer small pages and stop paging once the question is answered.

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.