# Tools

The four tools of the plnnrly MCP server, what each one accepts and returns, and the limits that apply.

The MCP server does not have a tool per feature. Through three general tools, an AI client reaches every operation of the [REST API](https://developers.plnnrly.com/en/docs/api/v2/) except file uploads, always as you and with your permissions. A fourth tool returns your own profile. When the API gains a feature, clients can use it straight away.

| Tool                    | Title clients show | What it does                            |
| ----------------------- | ------------------ | --------------------------------------- |
| `search_api`            | Search the API     | Finds API operations and describes them |
| `api_read`              | Read from the API  | Sends a `GET` to an API operation       |
| `api_write`             | Write to the API   | Sends a `POST`, `PATCH` or `DELETE`     |
| `get-current-user-tool` | Get current user   | Returns your profile                    |

Three tools only read. **Write to the API** is marked as a tool that can change data, so clients ask you to approve it by default. Most clients let you allow it for one conversation or permanently. [Permissions and safety](https://developers.plnnrly.com/en/docs/mcp/permissions-and-safety/) explains what a client may do.

## How a client uses the tools

Every client also receives a short set of instructions from the server. They describe three steps: find the operation with `search_api`, read what it accepts, then call it with `api_read` or `api_write`. They also tell the client that:

- work item paths accept issue keys such as `ACM-12`
- user filters accept `me`
- deleting needs `confirm: true`, sent only after you agreed, and only works when your organisation allows it
- text is added to a Drive document with `mode: append`, because without it the whole document is replaced

## search_api

Finds API operations and describes them. It only returns the API's own documentation, never data from your organisation.

| Input          | Type   | Required | Description                                                     |
| -------------- | ------ | -------- | --------------------------------------------------------------- |
| `query`        | string | no       | Keywords, such as `link work items` or `log time`.              |
| `operation_id` | string | no       | An `id` from a search result, such as `work-items.links.store`. |

- With `query`, it returns at most 12 matches as `{id, method, path, summary}`, plus a `hint` for the next step.
- With `operation_id`, it describes that operation: its description, the permission it needs, path parameters, query parameters, filters, sorts, includes, body fields, the feature it belongs to, and one or two examples. The id works with or without the `api.v2.` prefix.
- Without either input, it lists every operation id, grouped by area.

Operations your organisation cannot use are left out: those of areas your organisation does not use, deletes while **Allow destructive actions** is off, and file uploads.

## api_read

Sends a `GET` to any API operation.

| Input   | Type   | Required | Description                                                                                            |
| ------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ |
| `path`  | string | yes      | The path below `/api/v2`, with its placeholders filled, such as `work-items/ACM-12/comments`.          |
| `query` | object | no       | Query parameters, such as `{"filter": {"assignee_id": "me", "resolved": false}, "include": "labels"}`. |

### Paths and query

- An empty path lists the resources the API offers.
- The path may start with `/api/v2`; the prefix is ignored.
- The whole path is at most 256 characters. Its segments use only letters, digits, `-` and `_`, and every placeholder must be filled in.
- Work item paths accept issue keys, such as `work-items/ACM-12`. User filters accept `me`.
- Query objects nest at most three levels deep, such as `filter[due_date][gte]`, with at most 60 parameters in all.
- Null values are left out. Lists become comma-separated values.

### Lists and paging

Lists come back in pages of 10 in the summary view, unless the call asks for something else. To get the next page, pass the previous answer's `meta.page.next_cursor` as `cursor`. When `next_cursor` is `null`, there are no more pages.

Some lists also accept a page number. Their `meta.page` always has `current_page` and `last_page`, which are `null` unless the call sends `page`. A call that sends `page` always gets `next_cursor: null`. The last page is the one where `current_page` reaches `last_page`.

## api_write

Sends a `POST`, `PATCH` or `DELETE` to any API operation.

| Input     | Type    | Required | Description                                                                  |
| --------- | ------- | -------- | ---------------------------------------------------------------------------- |
| `method`  | string  | yes      | `POST`, `PATCH` or `DELETE`.                                                 |
| `path`    | string  | yes      | The path below `/api/v2`, such as `work-items/ACM-12/links`.                 |
| `query`   | object  | no       | Query parameters, such as `{"view": "detail"}`.                              |
| `body`    | object  | no       | The JSON body, such as `{"link_type": "blocks", "target_work_item_id": 42}`. |
| `confirm` | boolean | no       | `true` for a delete or an overwrite, and only after you agreed to it.        |

The path and query rules of `api_read` apply here too. The body is checked exactly as the REST API checks it: a field the operation does not accept is refused by name, never ignored.

Deleting needs two things: **Allow destructive actions** must be on for your organisation, and the call must carry `confirm: true`. Without either, nothing is deleted. [What counts as deleting](https://developers.plnnrly.com/en/docs/mcp/permissions-and-safety/#destructive) lists the operations this covers. On `DELETE /projects/{project}`, the query `{"force": true}` deletes the project permanently, instead of a delete that can be undone.

Replacing the whole text of a Drive document also needs `confirm: true`, because it discards formatting and anything others typed since the client read it. Adding text with `{"mode": "append"}` in the body needs no confirmation.

`api_write` refuses these calls before anything runs:

| Refusal                                             | Problem type          |
| --------------------------------------------------- | --------------------- |
| A delete while **Allow destructive actions** is off | `forbidden-by-policy` |
| A delete or overwrite without `confirm: true`       | `validation-failed`   |
| A file upload                                       | `endpoint-not-found`  |
| A method the operation does not accept              | `method-not-allowed`  |

Neither of the first two reads the record before refusing, so a refusal never tells whether the record exists.

Writes over MCP are not protected against duplicates. An `Idempotency-Key` is neither needed nor used, so a client that is unsure whether a write went through should read before it tries again.

If an app's sign-in also asked for REST API scopes, it is held to those scopes over MCP as well. Sign-in scopes never include deleting, so such an app cannot delete over MCP.

## get-current-user-tool

Returns your own profile. It takes no input.

```json
{
  "id": 34,
  "name": "Jamie Doe",
  "email": "jamie@acme.example",
  "email_verified_at": "2026-02-03T09:15:00+00:00",
  "timezone": "Europe/Amsterdam",
  "locale": "en",
  "job_title": "Project manager",
  "date_employed": "2025-09-01",
  "roles": ["Project manager"],
  "created_at": "2025-08-28T14:02:11+00:00"
}
```

The timestamps include a time zone offset. `date_employed` is a date (`YYYY-MM-DD`). Fields with no value are `null`.

## Responses

A successful answer is the API's own response body with two changes:

- A `status` key comes first, with the HTTP status the API answered, such as `200` or `201`.
- Every `links` map is removed. A client continues a list with `meta.page.next_cursor`, and calls other records by path.

A delete answers without a body, so the tool returns `{"status": 204}`.

An answer may hold about 30,000 characters. The limit counts bytes, so text with accents or emoji fits a little less. A longer answer is shortened, never silently: `meta.mcp_truncated` says what was cut. When rows were left out of a list, `next_cursor` is cleared, because following it would skip them. The hint says how to page on without gaps. This is the `meta` object of a shortened list:

```json
{
  "page": {
    "per_page": 10,
    "next_cursor": null,
    "prev_cursor": null,
    "total": null
  },
  "mcp_truncated": {
    "reason": "response_budget",
    "returned": 6,
    "omitted": 4,
    "hint": "Only the first 6 of 10 rows fit. Repeat this request with per_page=6 (and the same cursor, if any) to page through without gaps."
  }
}
```

When a single long text is the problem, `mcp_truncated` lists the shortened fields instead, each with its `pointer`, the characters `kept` and the original `length`.

## Errors

A refused call comes back as a tool error that carries the same [problem document](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/) the REST API returns, with the same status and type. Something you may not see answers `404`, exactly as over HTTP.

```json
{
  "type": "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy",
  "title": "Forbidden by policy",
  "status": 403,
  "detail": "This organisation does not allow AI clients to delete data. An administrator can turn on Allow destructive actions under Settings › Administration › MCP & API.",
  "instance": "/api/v2/work-items/ACM-12",
  "request_id": "01M4WVYY50K7T2B9QH3D6N8RXP"
}
```

A path or query the tools cannot use answers [malformed-request](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#malformed-request). Its `errors[].code` is `invalid` (a path or value that is not allowed), `too_many` (more than 60 query parameters) or `too_deep` (nested more than three levels).

## Limits

- Each user may send 60 MCP requests a minute. This counts every request the client sends to the server, including the ones that set up the connection and list the tools, not only tool calls. A client over the limit gets `429`.
- The REST API's own rate limits and rate units do not apply to calls made over MCP.
- An answer holds about 30,000 characters.
- The client renews its sign-in every hour. A client unused for 30 days has to sign in again.

[Limits at a glance](https://developers.plnnrly.com/en/docs/api/v2/guides/limits/) lists every limit of the REST API.

## What MCP cannot do

- **Upload files.** Adding an attachment or a Drive file needs a byte upload that MCP clients cannot make. Use the REST API with an API token; see [Files: upload and download](https://developers.plnnrly.com/en/docs/api/v2/guides/uploads/).
- **Delete while the switch is off.** Deletes only work while your organisation allows destructive actions.

## Related

- [What you can ask](https://developers.plnnrly.com/en/docs/mcp/examples/)
- [Permissions and safety](https://developers.plnnrly.com/en/docs/mcp/permissions-and-safety/)
- [Troubleshooting](https://developers.plnnrly.com/en/docs/mcp/troubleshooting/)
- [Errors and retries](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/)
