> ## 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: Planner & To Do

> The ten read-only tools in the Planner & To Do connector: Microsoft Planner plans, buckets and tasks, the connected user's own tasks across plans, and Microsoft To Do lists, tasks and search. Covers the id conventions, client-side filters, cursor paging, caps and the error contract.

All Planner & To Do tools are read-only and are called via `tools/call` over
`POST /planner/mcp`. The connector reads Microsoft Planner and Microsoft To Do
through Microsoft Graph and is connected through OAuth with a Microsoft work or
school account (see [Connect Planner & To Do](https://docs.consile.ai/en/connect/connect-planner.md)). Your
organization needs access to Planner & To Do (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/planner/mcp`. Planner & To Do is its own named
> connector with only its own tools. Tool names are namespaced `planner__<tool>`
> (e.g. `planner__list_my_tasks`); 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,
> because Planner membership and To Do lists belong to a user, not to an
> organization. An administrator adds further connections (agreements, **aftaler**,
> in the portal), each served at its own endpoint, e.g.
> `https://mcp.consile.ai/planner-2/mcp`, with tools named `planner-2__<tool>`.

For an account with access to Planner & To Do, `tools/list` returns the ten
`planner__*` 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

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:

* `Tasks.Read` (the user's Planner plans and tasks and their To Do lists)
* `User.Read` (the signed-in user's own profile, used to label the connection)
* `offline_access` (keeps the connection alive)

Both Graph permissions are read-only, so a token issued on this consent cannot
create, change or complete a task. The connector has no write tools, and Consile
sends only read requests to Microsoft Graph. A connect is refused if Microsoft's
grant lacks one of the requested permissions. In many organizations, `Tasks.Read`
needs an administrator's consent before a regular user can connect (see
[Connect Planner & To Do](https://docs.consile.ai/en/connect/connect-planner.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.

## Shared conventions

### The connected user

Every tool reads as the Microsoft user who connected the instance, whichever Consile
user makes the call. `list_plans` returns the plans that user is a member of,
`list_my_tasks` returns the tasks assigned to that user, and the To Do tools read
that user's lists.

### Assignees are user ids

Planner identifies people by their Microsoft Entra user id (a GUID), not by name.
`assignedUserIds` on a task is that list of ids. The connector does not request a
directory permission, so it cannot resolve an id to a name. A client must not guess
the names behind the ids. To read the connected user's own tasks, use
[`list_my_tasks`](#list_my_tasks); to narrow a plan to one person, pass their id as
`assignedToUserId` on [`list_plan_tasks`](#list_plan_tasks).

### Planner and To Do address things differently

* **Planner** (team boards) is keyed by a `planId` from [`list_plans`](#list_plans),
  with buckets (board columns) and tasks inside it. Planner returns a whole
  collection at once, so the Planner tools take no `pageSize` or `cursor`. Filters
  run after the collection is read, and a task listing is capped at 500 rows.
* **To Do** (personal lists) is keyed by a `listId` from
  [`list_todo_lists`](#list_todo_lists), with tasks inside it. To Do is paged: the
  listing tools take `pageSize` and `cursor` and return `nextCursor`.

### Paging in To Do

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

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

**cursor** `string`

The `nextCursor` from a previous page of the same tool. When it is set, the other
options are ignored and the cursor continues the original query. Omit it for the
first page and never construct or edit one. A cursor that is not a continuation
token is refused with
`Invalid cursor for Planner: pass the nextCursor from a previous page of the same tool`.

`nextCursor` is `null` when the collection is exhausted. To Do listings return no
total count.

### Dates

Planner dates (`startDateTime`, `dueDateTime`, `completedDateTime`,
`createdDateTime`) are ISO 8601 strings in UTC. To Do due and completed dates are
objects `{ "dateTime": "…", "timeZone": "…" }`; `createdDateTime` and
`lastModifiedDateTime` on a To Do task are ISO 8601 strings. A missing value is
`null`.

### Typical call order

* Planner: `list_plans`, then `list_buckets` and `list_plan_tasks` with the plan id,
  then `get_task` for one task's description, checklist and references. `get_plan`
  when the user asks about tasks by category label.
* Own work: `list_my_tasks`.
* To Do: `list_todo_lists`, then `list_todo_tasks`, then `get_todo_task`.
  `search_todo_tasks` when the list is unknown.

***

## list\_plans

Lists the Planner plans (team task boards) the connected user is a member of. The
whole set is returned at once; there is no paging.

**Arguments:** none.

**items** `array`

Each item has `id` (the `planId`), `title`, `ownerGroupId` (the owning Microsoft
365 group id), `containerType` and `containerUrl` (the container the plan belongs
to, for example a group) and `createdDateTime`. A missing value is `null`.

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

```json Result (payload) theme={null}
{
  "count": 1,
  "items": [
    {
      "id": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F",
      "title": "<plan name>",
      "ownerGroupId": "6b1f3c2a-9d4e-4a7b-8c5d-2e1f0a9b8c7d",
      "containerType": "group",
      "containerUrl": "https://graph.microsoft.com/v1.0/groups/6b1f3c2a-9d4e-4a7b-8c5d-2e1f0a9b8c7d",
      "createdDateTime": "2026-03-02T08:15:00Z"
    }
  ]
}
```

***

## get\_plan

Reads one plan with its category labels: the names behind `category1` to
`category25`, which the task tools report as `appliedCategories`.

**planId** `string` *required*

Plan id from [`list_plans`](#list_plans).

**categories** `object`

The labels that are set, keyed by category: `{ "category1": "Marketing", … }`.
Categories without a label are left out. The other fields are the same as one
`list_plans` item.

```json Request theme={null}
{ "name": "planner__get_plan", "arguments": { "planId": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F" } }
```

```json Result (payload) theme={null}
{
  "id": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F",
  "title": "<plan name>",
  "ownerGroupId": "6b1f3c2a-9d4e-4a7b-8c5d-2e1f0a9b8c7d",
  "containerType": "group",
  "containerUrl": "https://graph.microsoft.com/v1.0/groups/6b1f3c2a-9d4e-4a7b-8c5d-2e1f0a9b8c7d",
  "createdDateTime": "2026-03-02T08:15:00Z",
  "categories": { "category1": "Marketing", "category2": "Blocked" }
}
```

***

## list\_buckets

Lists a plan's buckets (board columns) in board order, with the bucket id
`list_plan_tasks` filters on. No paging.

**planId** `string` *required*

**items** `array`

Each item has `id`, `name` and `orderHint` (Planner's opaque sort key; the items
are already sorted by it).

```json Request theme={null}
{ "name": "planner__list_buckets", "arguments": { "planId": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F" } }
```

```json Result (payload) theme={null}
{
  "planId": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F",
  "count": 3,
  "items": [
    { "id": "b7Tn2QxZ0UeLp4RkW9sJmGQAA3cP", "name": "To do", "orderHint": "8585269235419217847" },
    { "id": "f2Hs8VyM1OaQr6NtX3dKlGQABx7R", "name": "In progress", "orderHint": "8585269235419217846P(" },
    { "id": "p9Lw4CzB5IuYt1MvE8gHnGQACs2T", "name": "Done", "orderHint": "8585269235419217845PL" }
  ]
}
```

***

## list\_plan\_tasks

Lists the tasks of one plan. Planner returns every task of the plan at once; the
optional filters narrow the result after it is read. Completed tasks are included
unless `status` excludes them.

**planId** `string` *required*

**bucketId** `string`

Keep only tasks in this bucket (id from [`list_buckets`](#list_buckets)).

**status** `string`

Keep only tasks in this state: `notStarted` (0 %), `inProgress` (1 to 99 %) or
`completed` (100 %).

**assignedToUserId** `string`

Keep only tasks assigned to this Entra user id (as reported in
`assignedUserIds`).

**totalCount** `integer`

The number of tasks in the plan before filtering. `count` is the number of rows
returned.

**truncated** `boolean`

Present and `true` when more than 500 tasks matched; only the first 500 are
returned. Narrow with the filters.

**items** `array`

Each task has:

* `id`, `title`, `bucketId`, `planId`
* `status` (`notStarted`, `inProgress` or `completed`, derived from
  `percentComplete`) and `percentComplete`
* `priority` (Planner's 0 to 10) and `priorityLabel`: `urgent` (0 to 1),
  `important` (2 to 4), `medium` (5 to 7) or `low` (8 to 10)
* `startDateTime`, `dueDateTime`, `completedDateTime`, `createdDateTime`
* `assignedUserIds` (Entra user ids, never names)
* `hasDescription`, `checklistItemCount`, `activeChecklistItemCount` (unchecked
  items), `referenceCount`
* `appliedCategories` (the category keys switched on, e.g. `["category1"]`; the
  labels come from [`get_plan`](#get_plan))

```json Request theme={null}
{
  "name": "planner__list_plan_tasks",
  "arguments": { "planId": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F", "status": "inProgress" }
}
```

```json Result (payload) theme={null}
{
  "planId": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F",
  "count": 1,
  "totalCount": 14,
  "items": [
    {
      "id": "t4Rm7KxP2WaNq8SbZ1fJcGQAE5vD",
      "title": "<task title>",
      "bucketId": "f2Hs8VyM1OaQr6NtX3dKlGQABx7R",
      "planId": "Kq3vN8sWbE2xR7tYp0LmZGQAHd4F",
      "status": "inProgress",
      "percentComplete": 50,
      "priority": 3,
      "priorityLabel": "important",
      "startDateTime": null,
      "dueDateTime": "2026-10-28T10:00:00Z",
      "completedDateTime": null,
      "createdDateTime": "2026-10-01T09:12:44Z",
      "assignedUserIds": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"],
      "hasDescription": true,
      "checklistItemCount": 4,
      "activeChecklistItemCount": 2,
      "referenceCount": 1,
      "appliedCategories": ["category1"]
    }
  ]
}
```

***

## list\_my\_tasks

Lists every Planner task assigned to the connected user, across all plans. Each row
carries its `planId` and `bucketId`. Completed tasks are left out unless
`includeCompleted` is `true` or `status` is `completed`. Use it for "what is on my
plate", "what is overdue" (compare `dueDateTime` with today) and "what did I finish
this week". The whole set is returned at once.

**status** `string`

`notStarted`, `inProgress` or `completed`, as on `list_plan_tasks`.

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

Include completed tasks. Ignored when `status` is set.

The result has the same shape as `list_plan_tasks` without `planId` at the top
level: `count`, `totalCount` (all of the user's tasks before filtering), `items` and,
above 500 matching rows, `truncated: true`.

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

***

## get\_task

Reads one Planner task in full: everything `list_plan_tasks` reports, plus the
description, the checklist and the references (links and attached files).

**taskId** `string` *required*

Planner task id from `list_plan_tasks` or `list_my_tasks`.

**description** `string | null`

The task's description as plain text.

**previewType** `string | null`

What the task card shows as its preview in Planner (for example `checklist` or
`description`).

**checklist** `array`

The checklist items in order, each with `id`, `title`, `isChecked` and
`orderHint`.

**references** `array`

Each reference has `alias` (its display name), `type` (for example `Word`,
`Excel`, `Pdf` or `Other`) and `url`. The connector does not download the files.

```json Request theme={null}
{ "name": "planner__get_task", "arguments": { "taskId": "t4Rm7KxP2WaNq8SbZ1fJcGQAE5vD" } }
```

```json Result (payload, shortened) theme={null}
{
  "id": "t4Rm7KxP2WaNq8SbZ1fJcGQAE5vD",
  "title": "<task title>",
  "status": "inProgress",
  "priorityLabel": "important",
  "dueDateTime": "2026-10-28T10:00:00Z",
  "assignedUserIds": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"],
  "description": "<description>",
  "previewType": "checklist",
  "checklist": [
    { "id": "51234", "title": "<step>", "isChecked": true, "orderHint": "8585269235419217847" },
    { "id": "62345", "title": "<step>", "isChecked": false, "orderHint": "8585269235419217846P(" }
  ],
  "references": [
    { "alias": "<file name>", "type": "Excel", "url": "https://example.com/sites/team/Shared%20Documents/<file name>.xlsx" }
  ]
}
```

***

## list\_todo\_lists

Lists the connected user's Microsoft To Do lists, with the `listId` the other To Do
tools take. Paged (see [Paging](#paging-in-to-do)).

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

**cursor** `string`

**items** `array`

Each item has `id`, `displayName`, `isOwner`, `isShared` and
`wellknownListName`: `defaultList` for the built-in Tasks list, `flaggedEmails`
for the e-mails the user flagged in Outlook, or `null` for a list the user
created.

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

```json Result (payload) theme={null}
{
  "count": 2,
  "items": [
    { "id": "AAMkADg3NjE1ZTgxLTdkZTktNDAwAAA=", "displayName": "Tasks", "isOwner": true, "isShared": false, "wellknownListName": "defaultList" },
    { "id": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=", "displayName": "<list name>", "isOwner": true, "isShared": true, "wellknownListName": null }
  ],
  "nextCursor": null
}
```

***

## list\_todo\_tasks

Lists the tasks of one To Do list, newest first. Completed tasks are left out unless
`includeCompleted` is `true` or `status` is `completed`. Paged.

**listId** `string` *required*

To Do list id from [`list_todo_lists`](#list_todo_lists).

**status** `string`

Keep only tasks in this state: `notStarted`, `inProgress`, `completed`,
`waitingOnOthers` or `deferred`. Overrides `includeCompleted`.

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

Include completed tasks. Ignored when `status` is set.

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

**cursor** `string`

**items** `array`

Each task has `id`, `title`, `status`, `importance` (`low`, `normal` or `high`),
`isReminderOn`, `createdDateTime`, `lastModifiedDateTime`, `dueDateTime`,
`completedDateTime`, `description`, `categories` and `hasAttachments`.
`description` is the task body as plain text, at most 2,000 characters; a longer
body ends with `[truncated]`.

```json Request theme={null}
{
  "name": "planner__list_todo_tasks",
  "arguments": { "listId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=", "pageSize": 20 }
}
```

```json Result (payload) theme={null}
{
  "listId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=",
  "count": 1,
  "items": [
    {
      "id": "AAMkADg3NjE1ZTgxLTdkZTktNDAxRgAAAA==",
      "title": "<task title>",
      "status": "notStarted",
      "importance": "high",
      "isReminderOn": false,
      "createdDateTime": "2026-10-20T07:41:09.1234567Z",
      "lastModifiedDateTime": "2026-10-21T13:02:55.7654321Z",
      "dueDateTime": { "dateTime": "2026-11-03T00:00:00.0000000", "timeZone": "UTC" },
      "completedDateTime": null,
      "description": "<description>",
      "categories": [],
      "hasAttachments": false
    }
  ],
  "nextCursor": "<nextCursor>"
}
```

```json Request: next page theme={null}
{
  "name": "planner__list_todo_tasks",
  "arguments": { "listId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=", "cursor": "<nextCursor from the previous call>" }
}
```

`listId` is still required by the schema when you pass `cursor`.

***

## get\_todo\_task

Reads one To Do task in full: everything `list_todo_tasks` reports, the description
as plain text up to 20,000 characters, and the checklist (sub-steps).

**listId** `string` *required*

**taskId** `string` *required*

Task id from `list_todo_tasks` or `search_todo_tasks`.

**checklist** `array`

The sub-steps, each with `id`, `displayName`, `isChecked` and
`createdDateTime`.

```json Request theme={null}
{
  "name": "planner__get_todo_task",
  "arguments": {
    "listId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=",
    "taskId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxRgAAAA=="
  }
}
```

```json Result (payload, shortened) theme={null}
{
  "listId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=",
  "id": "AAMkADg3NjE1ZTgxLTdkZTktNDAxRgAAAA==",
  "title": "<task title>",
  "status": "notStarted",
  "description": "<description>",
  "checklist": [
    { "id": "5e1c2d3a-0b4f-4e6a-9c8d-7f6e5d4c3b2a", "displayName": "<step>", "isChecked": false, "createdDateTime": "2026-10-20T07:42:00.0000000Z" }
  ]
}
```

***

## search\_todo\_tasks

Finds To Do tasks across all the user's lists whose title or description contains
the text, case-insensitively. Each hit carries its `listId` and `listName`, so
`get_todo_task` can follow. Completed tasks are skipped unless `includeCompleted` is
`true`. The tool reads every list, so it costs one or more requests per list. Use
`list_todo_tasks` when the list is already known.

**query** `string` *required*

Text to look for in the title or description. At least one character.

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

**items** `array`

The matching tasks in the `list_todo_tasks` shape, plus `listId` and `listName`.
At most 200 hits.

**truncated** `boolean`

Present and `true` when the search stopped at 200 hits, or when not every task in
every list could be read. A narrower `query` helps.

```json Request theme={null}
{ "name": "planner__search_todo_tasks", "arguments": { "query": "<company name>" } }
```

```json Result (payload, shortened) theme={null}
{
  "query": "<company name>",
  "count": 1,
  "items": [
    {
      "id": "AAMkADg3NjE1ZTgxLTdkZTktNDAxRgAAAA==",
      "title": "<task title>",
      "status": "notStarted",
      "description": "<description>",
      "listId": "AAMkADg3NjE1ZTgxLTdkZTktNDAxAAA=",
      "listName": "<list name>"
    }
  ]
}
```

***

## connect\_account

`connect_account` is a system tool that the Planner & To Do endpoint lists next to
the ten tools. It takes no arguments and its name has no `planner__` 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 Planner & To
Do 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. The messages name the product as `Planner`, also for the To Do
tools.

| Situation | Text in the result |
| - | - |
| Bare tool name (e.g. `list_plans`) or another connector's tool | `Unknown tool.` |
| No access to Planner & To Do (no subscription, and no plan that covers it) | `You are not subscribed to planner. Purchase it here first: https://app.consile.ai/connectors/planner …` |
| Access exists, but your user has no seat | `Your company subscribes to planner through Consile, but your user does not hold a seat on it yet. …` (an administrator assigns seats under **Team**) |
| Microsoft account not connected yet | `planner is not connected yet. Connect it here: <link>` (link valid 10 minutes; an additional instance names itself, e.g. `planner-2`) |
| The stored access has expired and could not be renewed | `The Planner access token has expired. The connection must be re-established from the Consile portal.` |
| Microsoft Graph rejected the token (`401`) | `Planner rejected the access token (401). The connection must be re-established from the Consile portal.` |
| Microsoft Graph answered `403` | `Planner 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`) | `Planner 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 Graph rejected the request (`400`) | `Planner rejected the request (400 <code>): <message>` (at most 500 characters) |
| Microsoft Graph's rate limit is still reached (`429`) | `Planner 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 valid continuation token | `Invalid cursor for Planner: pass the nextCursor from a previous page of the same tool` |
| Arguments outside the input schema (e.g. an empty `planId`, `pageSize` above `500`, an unknown `status`), a timeout, another `5xx` or `408` from Microsoft Graph, or a non-JSON response | `Tool invocation failed.` |

## Limits

* Planner task listings (`list_plan_tasks`, `list_my_tasks`): at most 500 rows per
  result, flagged with `truncated`.
* To Do page size: default 50, maximum 500 rows.
* To Do descriptions: 2,000 characters per row in listings and search, 20,000 in
  `get_todo_task`.
* `search_todo_tasks`: at most 200 hits.
* Microsoft Graph applies its own rate limits. When a request is still throttled,
  the tool returns the `429` message in [Errors](#errors). Prefer filters and known
  ids over broad searches.

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.