@omnicoreos/planka-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +26 -0
- package/.claude-plugin/plugin.json +45 -0
- package/.codex-plugin/mcp.json +16 -0
- package/.codex-plugin/plugin.json +27 -0
- package/.mcp.json +17 -0
- package/CHANGELOG.md +510 -0
- package/README.es.md +294 -55
- package/README.md +293 -55
- package/dist/cli/init.d.ts +101 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +481 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/client.d.ts +32 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +106 -32
- package/dist/client.js.map +1 -1
- package/dist/config/policy.d.ts +82 -0
- package/dist/config/policy.d.ts.map +1 -0
- package/dist/config/policy.js +226 -0
- package/dist/config/policy.js.map +1 -0
- package/dist/errors.d.ts +5 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +62 -5
- package/dist/errors.js.map +1 -1
- package/dist/identity.generated.d.ts +2 -1
- package/dist/identity.generated.d.ts.map +1 -1
- package/dist/identity.generated.js +2 -1
- package/dist/identity.generated.js.map +1 -1
- package/dist/index.js +85 -10
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +21 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +37 -0
- package/dist/instructions.js.map +1 -0
- package/dist/operations/actions.d.ts +654 -0
- package/dist/operations/actions.d.ts.map +1 -0
- package/dist/operations/actions.js +154 -0
- package/dist/operations/actions.js.map +1 -0
- package/dist/operations/archive.d.ts +28 -0
- package/dist/operations/archive.d.ts.map +1 -0
- package/dist/operations/archive.js +74 -0
- package/dist/operations/archive.js.map +1 -0
- package/dist/operations/attachments.d.ts +1 -1
- package/dist/operations/attachments.d.ts.map +1 -1
- package/dist/operations/attachments.js +3 -1
- package/dist/operations/attachments.js.map +1 -1
- package/dist/operations/board-id.d.ts +1 -1
- package/dist/operations/board-id.d.ts.map +1 -1
- package/dist/operations/board-id.js +13 -7
- package/dist/operations/board-id.js.map +1 -1
- package/dist/operations/boards.d.ts +96 -19
- package/dist/operations/boards.d.ts.map +1 -1
- package/dist/operations/boards.js +377 -93
- package/dist/operations/boards.js.map +1 -1
- package/dist/operations/card-brief.d.ts +91 -0
- package/dist/operations/card-brief.d.ts.map +1 -0
- package/dist/operations/card-brief.js +79 -0
- package/dist/operations/card-brief.js.map +1 -0
- package/dist/operations/cards.d.ts +34 -9
- package/dist/operations/cards.d.ts.map +1 -1
- package/dist/operations/cards.js +60 -14
- package/dist/operations/cards.js.map +1 -1
- package/dist/operations/comments.d.ts +61 -4
- package/dist/operations/comments.d.ts.map +1 -1
- package/dist/operations/comments.js +91 -8
- package/dist/operations/comments.js.map +1 -1
- package/dist/operations/duplicate.d.ts +16 -0
- package/dist/operations/duplicate.d.ts.map +1 -0
- package/dist/operations/duplicate.js +43 -0
- package/dist/operations/duplicate.js.map +1 -0
- package/dist/operations/labels.d.ts +1 -1
- package/dist/operations/labels.d.ts.map +1 -1
- package/dist/operations/labels.js +7 -4
- package/dist/operations/labels.js.map +1 -1
- package/dist/operations/lists.d.ts +63 -1
- package/dist/operations/lists.d.ts.map +1 -1
- package/dist/operations/lists.js +97 -2
- package/dist/operations/lists.js.map +1 -1
- package/dist/operations/members.d.ts +39 -0
- package/dist/operations/members.d.ts.map +1 -0
- package/dist/operations/members.js +107 -0
- package/dist/operations/members.js.map +1 -0
- package/dist/operations/projects.d.ts +16 -0
- package/dist/operations/projects.d.ts.map +1 -1
- package/dist/operations/projects.js +54 -9
- package/dist/operations/projects.js.map +1 -1
- package/dist/operations/tasks.d.ts +1 -1
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js +5 -3
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/users.d.ts +123 -0
- package/dist/operations/users.d.ts.map +1 -0
- package/dist/operations/users.js +180 -0
- package/dist/operations/users.js.map +1 -0
- package/dist/operations/verify.d.ts +84 -0
- package/dist/operations/verify.d.ts.map +1 -0
- package/dist/operations/verify.js +124 -0
- package/dist/operations/verify.js.map +1 -0
- package/dist/prompts.d.ts +48 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +155 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +38 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +127 -0
- package/dist/resources.js.map +1 -0
- package/dist/schemas/entities.d.ts +115 -24
- package/dist/schemas/entities.d.ts.map +1 -1
- package/dist/schemas/entities.js +48 -0
- package/dist/schemas/entities.js.map +1 -1
- package/dist/schemas/requests.d.ts +121 -46
- package/dist/schemas/requests.d.ts.map +1 -1
- package/dist/schemas/requests.js +57 -12
- package/dist/schemas/requests.js.map +1 -1
- package/dist/schemas/responses.d.ts +541 -186
- package/dist/schemas/responses.d.ts.map +1 -1
- package/dist/schemas/responses.js +13 -2
- package/dist/schemas/responses.js.map +1 -1
- package/dist/tools/activity.d.ts +150 -0
- package/dist/tools/activity.d.ts.map +1 -0
- package/dist/tools/activity.js +198 -0
- package/dist/tools/activity.js.map +1 -0
- package/dist/tools/annotations.d.ts +52 -0
- package/dist/tools/annotations.d.ts.map +1 -0
- package/dist/tools/annotations.js +214 -0
- package/dist/tools/annotations.js.map +1 -0
- package/dist/tools/attachments.d.ts +28 -4
- package/dist/tools/attachments.d.ts.map +1 -1
- package/dist/tools/attachments.js +53 -34
- package/dist/tools/attachments.js.map +1 -1
- package/dist/tools/card-ops.d.ts +232 -0
- package/dist/tools/card-ops.d.ts.map +1 -0
- package/dist/tools/card-ops.js +333 -0
- package/dist/tools/card-ops.js.map +1 -0
- package/dist/tools/cards.d.ts +90 -8
- package/dist/tools/cards.d.ts.map +1 -1
- package/dist/tools/cards.js +411 -128
- package/dist/tools/cards.js.map +1 -1
- package/dist/tools/comments.d.ts +226 -22
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js +163 -134
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/dispatch.d.ts +47 -0
- package/dist/tools/dispatch.d.ts.map +1 -0
- package/dist/tools/dispatch.js +63 -0
- package/dist/tools/dispatch.js.map +1 -0
- package/dist/tools/guard.d.ts +9 -0
- package/dist/tools/guard.d.ts.map +1 -0
- package/dist/tools/guard.js +20 -0
- package/dist/tools/guard.js.map +1 -0
- package/dist/tools/index.d.ts +748 -450
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +136 -17
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/labels.d.ts +213 -18
- package/dist/tools/labels.d.ts.map +1 -1
- package/dist/tools/labels.js +218 -203
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/lists.d.ts +222 -15
- package/dist/tools/lists.d.ts.map +1 -1
- package/dist/tools/lists.js +175 -156
- package/dist/tools/lists.js.map +1 -1
- package/dist/tools/members.d.ts +128 -0
- package/dist/tools/members.d.ts.map +1 -0
- package/dist/tools/members.js +150 -0
- package/dist/tools/members.js.map +1 -0
- package/dist/tools/navigation.d.ts +22 -2
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/navigation.js +60 -15
- package/dist/tools/navigation.js.map +1 -1
- package/dist/tools/queries.d.ts +196 -166
- package/dist/tools/queries.d.ts.map +1 -1
- package/dist/tools/queries.js +125 -155
- package/dist/tools/queries.js.map +1 -1
- package/dist/tools/tasks.d.ts +26 -6
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +110 -55
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/users.d.ts +130 -0
- package/dist/tools/users.d.ts.map +1 -0
- package/dist/tools/users.js +165 -0
- package/dist/tools/users.js.map +1 -0
- package/docs/planka-2x-gotchas.md +121 -5
- package/docs/tools.md +771 -187
- package/docs/troubleshooting.md +137 -5
- package/hooks/hooks.json +15 -0
- package/hooks/preflight.mjs +100 -0
- package/package.json +6 -1
- package/scripts/setup.sh +8 -26
- package/scripts/sync-identity.mjs +55 -1
- package/server.json +87 -6
- package/tests/smoke/planka-smoke.mjs +512 -72
- package/workflow/skills/planka-close-card/SKILL.md +18 -5
- package/workflow/skills/planka-orchestrator/SKILL.md +36 -7
package/docs/tools.md
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
# Tools reference
|
|
2
2
|
|
|
3
|
-
The server exposes exactly
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The server exposes exactly 41 tools: 34 in active use plus 7 deprecated ones kept
|
|
4
|
+
as aliases (see [Deprecated aliases](#deprecated-aliases); `PLANKA_HIDE_DEPRECATED`
|
|
5
|
+
takes them out of `tools/list`). Tool results are MCP text content containing
|
|
6
|
+
JSON (minified — the indentation was pure token cost). On an expected Planka
|
|
7
|
+
error, the result has `isError: true` and a human-readable message instead of a
|
|
8
|
+
false success.
|
|
9
|
+
|
|
10
|
+
## Every write is verified
|
|
11
|
+
|
|
12
|
+
A `200` from Planka proves the request was accepted, not that the change is in
|
|
13
|
+
the database. So every write re-reads the entity and reports what LANDED, never
|
|
14
|
+
what was asked for. The response fields:
|
|
15
|
+
|
|
16
|
+
| Field | Meaning |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `verified: true` | A re-read confirmed the change. This is the only real success. |
|
|
19
|
+
| `verified: false` + `isError` | The re-read contradicted the write, or the write could not be checked. Do not assume the write failed: read before retrying. |
|
|
20
|
+
| `verificationError` | The re-read itself failed. The write may or may not have landed. |
|
|
21
|
+
| `verificationUnavailable` | Verification was structurally impossible (a task or attachment whose card is unknown). The message names the parameter — usually `cardId` — that would have made the check possible. |
|
|
22
|
+
|
|
23
|
+
**`success` mirrors `verified`, and nothing else.** "I could not check" is not a
|
|
24
|
+
success: a result that carried `success: true` next to `verified: false` and
|
|
25
|
+
`"delete not confirmed"` was read by models as done. Every unverified write now
|
|
26
|
+
answers `success: false` with `isError`, and says what to pass to get it
|
|
27
|
+
verified.
|
|
28
|
+
| `partialFailure` | A follow-up step failed (`planka_create_card` only). The card id is always present anyway. |
|
|
29
|
+
| `notLanded` | The fields, or the list, that the re-read says did not change. |
|
|
30
|
+
|
|
31
|
+
Verification costs one extra read per write — a card read for cards, tasks,
|
|
32
|
+
comments and attachments, and a board read for labels and lists.
|
|
6
33
|
|
|
7
34
|
## Before your first call
|
|
8
35
|
|
|
9
36
|
- All Planka IDs are strings, even when they contain only digits.
|
|
10
|
-
- Discover IDs with `
|
|
11
|
-
`
|
|
37
|
+
- Discover IDs with `planka_board_summary` (columns and labels of one board) or
|
|
38
|
+
`planka_get_structure` (projects and boards).
|
|
12
39
|
- Reading a whole board is the expensive call. When you only need one column,
|
|
13
40
|
one label, or the shape of the board, use a [scoped read](#scoped-reads).
|
|
14
41
|
- Dates use ISO 8601, for example `2026-09-01T17:00:00.000Z`.
|
|
@@ -17,6 +44,59 @@ human-readable message instead of a false success.
|
|
|
17
44
|
- Destructive tools really delete data. Ask for confirmation in your agent prompt
|
|
18
45
|
when that matters.
|
|
19
46
|
|
|
47
|
+
## Server instructions and annotations
|
|
48
|
+
|
|
49
|
+
Two things travel with `tools/list` and the `initialize` handshake, so a client
|
|
50
|
+
gets them without installing anything.
|
|
51
|
+
|
|
52
|
+
**Server instructions.** The `initialize` result carries an `instructions`
|
|
53
|
+
string (~1.8 kB). Claude Code injects it into the session system prompt and
|
|
54
|
+
Codex CLI reads it alongside the tool list. It holds the guidance that is shared
|
|
55
|
+
by many tools and therefore does not belong in any single description: open a
|
|
56
|
+
session with `planka_board_summary` and never `planka_get_board`, IDs are
|
|
57
|
+
strings of digits taken from `planka_get_structure` or `planka_board_summary`,
|
|
58
|
+
read every comment on a card before acting on it, reads return digests with
|
|
59
|
+
`total`/`returned`/`hasMore`, writes report `verified` and `finalLabelIds`, and
|
|
60
|
+
Planka answers `404` where it means `403`. The MCP spec asks that instructions
|
|
61
|
+
not duplicate tool descriptions, which is why the descriptions below are short.
|
|
62
|
+
Source: `src/instructions.ts`, budget enforced at 1500-2500 characters by
|
|
63
|
+
`tests/unit/instructions.test.ts`.
|
|
64
|
+
|
|
65
|
+
**Tool annotations.** Every tool publishes a `title` and all four behavioural
|
|
66
|
+
hints, spelled out rather than left to the spec defaults — which are pessimistic
|
|
67
|
+
(`readOnlyHint: false`, `destructiveHint: true`, `idempotentHint: false`,
|
|
68
|
+
`openWorldHint: true`) and would make `planka_get_comments` look as dangerous as
|
|
69
|
+
`planka_delete_card`. `openWorldHint` is `false` everywhere: the domain is one
|
|
70
|
+
configured Planka instance, not the open web.
|
|
71
|
+
|
|
72
|
+
| Group | `readOnlyHint` | `destructiveHint` | `idempotentHint` |
|
|
73
|
+
|---|---|---|---|
|
|
74
|
+
| Reads (`get_*`, `list_*`, `find_cards`, `board_summary`) | `true` | `false` | `true` |
|
|
75
|
+
| Additive writes (`create_card`, `create_tasks`, `create_comment`, `create_label`, `create_list`, `add_attachment`) | `false` | `false` | `false` |
|
|
76
|
+
| Converging writes (`move_card`, `update_task`, `update_label`, `update_list`, `update_comment`) | `false` | `false` | `true` |
|
|
77
|
+
| Data-losing writes (`update_card`, `set_card_labels`, `set_card_members`, `archive_card`, `move_list_cards`, every `delete_*`) | `false` | `true` | `true` |
|
|
78
|
+
| Deprecated multi-action aliases (`manage_labels`, `manage_lists`, `manage_comment`) | `false` | `true` | `false` |
|
|
79
|
+
|
|
80
|
+
**Anthropic `_meta`.** Claude Code does not gate permissions on the standard
|
|
81
|
+
hints; it reads two `_meta` keys instead, and the server ships both:
|
|
82
|
+
|
|
83
|
+
- `anthropic/requiresUserInteraction: true` forces the permission prompt even
|
|
84
|
+
under an `allow` rule — set on eleven tools: `planka_delete_card`,
|
|
85
|
+
`planka_delete_task`, `planka_delete_attachment`, `planka_delete_label`,
|
|
86
|
+
`planka_delete_list`, `planka_delete_comment`, `planka_archive_card`,
|
|
87
|
+
`planka_move_list_cards`, and the deprecated `planka_manage_comment`,
|
|
88
|
+
`planka_manage_lists` and `planka_manage_labels`. The three aliases are marked
|
|
89
|
+
whole because one of their actions deletes: a list takes its cards with it, a
|
|
90
|
+
label disappears from every card that carried it. `archive_card` and
|
|
91
|
+
`move_list_cards` are there because both move cards a human will look for
|
|
92
|
+
where they used to be.
|
|
93
|
+
- `anthropic/maxResultSizeChars: 4500000` on `planka_view_attachment`, so a
|
|
94
|
+
base64 image up to 3 MB is not clipped by the default per-tool ceiling.
|
|
95
|
+
|
|
96
|
+
The map lives in `src/tools/annotations.ts`. A tool with no entry there fails
|
|
97
|
+
`tests/unit/annotations.test.ts`, and the same file pins the order of
|
|
98
|
+
`tools/list` so clients that cache it see a stable payload.
|
|
99
|
+
|
|
20
100
|
## Navigation
|
|
21
101
|
|
|
22
102
|
### `planka_get_structure`
|
|
@@ -36,58 +116,99 @@ Or restrict it to one project:
|
|
|
36
116
|
}
|
|
37
117
|
```
|
|
38
118
|
|
|
119
|
+
| Field | Default | Notes |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `projectId` | none | An id that is not visible errors and lists the ones that are |
|
|
122
|
+
| `withLists` | `true` | Include each board's columns |
|
|
123
|
+
|
|
124
|
+
Planka has no per-board list endpoint: the columns only exist inside a full
|
|
125
|
+
board read, so `withLists: true` costs **one board read per board**.
|
|
126
|
+
`withLists: false` answers projects and boards from the single
|
|
127
|
+
`GET /api/projects` — 203 chars and 1 request against 696 chars and 4 requests
|
|
128
|
+
on the reference instance. Use `planka_board_summary` when you want the columns
|
|
129
|
+
of one board: it brings their card counts too.
|
|
130
|
+
|
|
39
131
|
### `planka_get_board`
|
|
40
132
|
|
|
41
|
-
|
|
133
|
+
> **DEPRECATED — use `planka_board_summary` + `planka_find_cards`.**
|
|
134
|
+
> It still works, and it is still the most expensive read of the set.
|
|
135
|
+
|
|
136
|
+
Reads one board with its labels, lists and cards.
|
|
42
137
|
|
|
43
138
|
```json
|
|
44
139
|
{
|
|
45
140
|
"boardId": "2234567890123456789",
|
|
46
|
-
"includeTaskCounts": true
|
|
141
|
+
"includeTaskCounts": true,
|
|
142
|
+
"limit": 50
|
|
47
143
|
}
|
|
48
144
|
```
|
|
49
145
|
|
|
50
|
-
|
|
51
|
-
raw `color` string; reads intentionally accept colors added by newer Planka versions.
|
|
52
|
-
|
|
53
|
-
## Scoped reads
|
|
54
|
-
|
|
55
|
-
`planka_get_board` returns the whole board: every card, every description, every
|
|
56
|
-
label link. That is the right call when you really need all of it, and an
|
|
57
|
-
expensive one when you do not. The four tools in this section answer narrower
|
|
58
|
-
questions and return proportionally less text.
|
|
59
|
-
|
|
60
|
-
Measured against one real board holding 100 cards, in characters of tool
|
|
61
|
-
response:
|
|
62
|
-
|
|
63
|
-
| Call | Response size | Compared to `planka_get_board` |
|
|
146
|
+
| Field | Default | Notes |
|
|
64
147
|
|---|---|---|
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
148
|
+
| `includeTaskCounts` | `true` | Checklist counts per card |
|
|
149
|
+
| `limit` | `50` | Cards across the WHOLE board, maximum `200` |
|
|
150
|
+
|
|
151
|
+
It hands out cards in board order until `limit` runs out and then reports
|
|
152
|
+
`totalCards`, `returnedCards` and `truncated: true` — it no longer grows without
|
|
153
|
+
a ceiling. Every response carries `excludesArchived: true`: the board read has
|
|
154
|
+
no cards from the archive and trash lists (see
|
|
155
|
+
[planka-2x-gotchas.md](planka-2x-gotchas.md)).
|
|
70
156
|
|
|
71
|
-
|
|
72
|
-
the number of cards.
|
|
157
|
+
## Scoped reads
|
|
73
158
|
|
|
74
|
-
|
|
159
|
+
Three tools, one principle: **a small digest by default, the detail through
|
|
160
|
+
parameters**.
|
|
75
161
|
|
|
76
162
|
| Question | Tool |
|
|
77
163
|
|---|---|
|
|
78
|
-
| What
|
|
79
|
-
| What is
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
`
|
|
164
|
+
| What is on this board — columns, counts, labels? | `planka_board_summary` |
|
|
165
|
+
| What is in this one column? | `planka_find_cards` with `listId` |
|
|
166
|
+
| Which cards carry this label, mention this text, belong to this person? | `planka_find_cards` |
|
|
167
|
+
| I need every field of every card, in one object | `planka_get_board` (deprecated) |
|
|
168
|
+
|
|
169
|
+
`planka_list_lists` and `planka_list_cards` are **deprecated aliases** kept for
|
|
170
|
+
existing callers: `planka_list_lists` is `planka_board_summary` with
|
|
171
|
+
`cardsFrom: []`, and `planka_list_cards` is `planka_find_cards` with `listId`.
|
|
172
|
+
Same implementation, same response — and together they were the two most
|
|
173
|
+
expensive schemas in `tools/list`, which is why `PLANKA_HIDE_DEPRECATED` now
|
|
174
|
+
drops them too.
|
|
175
|
+
|
|
176
|
+
### Measured, against the real reference board
|
|
177
|
+
|
|
178
|
+
185 cards, 10 columns, 23 labels. `chars` is the tool response as serialized;
|
|
179
|
+
`reqs` is how many requests to Planka it took.
|
|
180
|
+
|
|
181
|
+
| Call | before | after | Δ chars |
|
|
182
|
+
|---|---|---|---|
|
|
183
|
+
| `board_summary` — session opener | 2,045 chars / 1 req | 2,037 / 1 | −0 % |
|
|
184
|
+
| `board_summary` columns only (= `list_lists`) | 782 / 1 | 1,947 / 1 | +149 % |
|
|
185
|
+
| One column of 15 cards (default `limit` 25) | 2,572 / 2 | 3,366 / 2 | +31 % |
|
|
186
|
+
| One column of 159 cards (default `limit` 25) | 4,272 / **5** | 5,834 / **2** | +37 % |
|
|
187
|
+
| One column of 159 cards, `limit: 200` | 27,044 / **5** | 36,230 / **2** | +34 % |
|
|
188
|
+
| `find_cards` by label (29 hits, `limit` 25) | 6,085 / 1 | 6,758 / 1 | +11 % |
|
|
189
|
+
| `find_cards` by text | 1,724 / 1 | 1,986 / 1 | +15 % |
|
|
190
|
+
| `get_board` | 56,112 / 1 | **17,215** / 1 | **−69 %** |
|
|
191
|
+
| `get_structure` with columns | 696 / 4 | 696 / 4 | = |
|
|
192
|
+
| `get_structure` `withLists: false` | — | **203 / 1** | — |
|
|
193
|
+
|
|
194
|
+
Read it honestly: the card digest got **richer**, not smaller. Each card now
|
|
195
|
+
also carries `commentsTotal`, `updatedAt` and its members — around 40 chars each
|
|
196
|
+
— which is what stops the follow-up call that used to be needed to find out
|
|
197
|
+
whether a card had comments or had moved since the last session. What got
|
|
198
|
+
cheaper is everything else: the whole-board dump (−69 %), the structure without
|
|
199
|
+
columns (−71 %), and the number of requests behind a column read (5 → 2, and
|
|
200
|
+
1 with `withLabels: false`).
|
|
201
|
+
|
|
202
|
+
`planka_list_lists` grew because it is now `planka_board_summary`'s payload: it
|
|
203
|
+
also returns the board's labels **with their IDs**, which is exactly what
|
|
204
|
+
`planka_set_card_labels` needs and what used to send callers to the deprecated
|
|
205
|
+
`planka_get_board`.
|
|
86
206
|
|
|
87
|
-
### `
|
|
207
|
+
### `planka_board_summary`
|
|
88
208
|
|
|
89
|
-
|
|
90
|
-
|
|
209
|
+
One call that describes a board well enough to start working: every column with
|
|
210
|
+
its card count, every label with its ID, and — when asked — the cards of the
|
|
211
|
+
column(s) named in `cardsFrom`.
|
|
91
212
|
|
|
92
213
|
```json
|
|
93
214
|
{
|
|
@@ -95,140 +216,118 @@ at all. Use it to see the shape of a board before deciding which column to open.
|
|
|
95
216
|
}
|
|
96
217
|
```
|
|
97
218
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
### `planka_board_summary`
|
|
102
|
-
|
|
103
|
-
One call that describes a board well enough to start working: every column with
|
|
104
|
-
its card count, every label, and the cards sitting in the column or columns that
|
|
105
|
-
are waiting on a decision. Cards already carrying the unblocking label are sorted
|
|
106
|
-
first and marked `highlighted: true`.
|
|
219
|
+
That answers columns and labels and **no cards at all**. Ask for cards by naming
|
|
220
|
+
the columns:
|
|
107
221
|
|
|
108
222
|
```json
|
|
109
223
|
{
|
|
110
224
|
"boardId": "2234567890123456789",
|
|
111
225
|
"cardsFrom": [
|
|
112
|
-
"blocked"
|
|
226
|
+
"blocked",
|
|
227
|
+
"needs review"
|
|
113
228
|
],
|
|
114
|
-
"highlightLabel": "
|
|
229
|
+
"highlightLabel": "approved",
|
|
115
230
|
"limit": 50
|
|
116
231
|
}
|
|
117
232
|
```
|
|
118
233
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| Field | Default | What the default does |
|
|
234
|
+
| Field | Default | What it does |
|
|
122
235
|
|---|---|---|
|
|
123
|
-
| `cardsFrom` | `
|
|
124
|
-
| `highlightLabel` | `
|
|
236
|
+
| `cardsFrom` | `PLANKA_SUMMARY_DECISION_LISTS`, else none | List IDs, or column-name fragments matched case- and accent-insensitively. `[]` means columns only |
|
|
237
|
+
| `highlightLabel` | `PLANKA_SUMMARY_HIGHLIGHT_LABEL`, else none | Cards carrying that label sort first and get `highlighted: true` |
|
|
125
238
|
| `limit` | `50` | Caps returned cards; maximum `200` |
|
|
126
239
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
your own values and the tool behaves exactly the same on an English board.
|
|
240
|
+
**No board vocabulary is hardcoded in the server any more.** The defaults come
|
|
241
|
+
from the environment, so a deployment describes its own board:
|
|
130
242
|
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"cardsFrom": [
|
|
135
|
-
"blocked",
|
|
136
|
-
"needs review"
|
|
137
|
-
],
|
|
138
|
-
"highlightLabel": "approved"
|
|
139
|
-
}
|
|
243
|
+
```bash
|
|
244
|
+
PLANKA_SUMMARY_DECISION_LISTS="decision,probalo,miralo"
|
|
245
|
+
PLANKA_SUMMARY_HIGHLIGHT_LABEL="decidido"
|
|
140
246
|
```
|
|
141
247
|
|
|
142
|
-
`cardsFrom`
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
hint that matched nothing is visible instead of silent.
|
|
248
|
+
A `cardsFrom` entry that matches no column, or a `highlightLabel` that is not on
|
|
249
|
+
the board, comes back in `warnings` **naming the columns or labels that do
|
|
250
|
+
exist** — never as a silently empty result. The response also carries
|
|
251
|
+
`excludesArchived: true`.
|
|
147
252
|
|
|
148
|
-
|
|
149
|
-
with `planka_get_card` for one card rather than `planka_get_board` for all of
|
|
150
|
-
them.
|
|
253
|
+
### `planka_find_cards`
|
|
151
254
|
|
|
152
|
-
|
|
255
|
+
The one read over a board's cards. At least one filter is required
|
|
256
|
+
(`listId`, `listName`, `query`, `labelName`, `labelIds` or `userIds`); an
|
|
257
|
+
unfiltered search would just be a slower `planka_get_board`.
|
|
153
258
|
|
|
154
|
-
|
|
155
|
-
date. Descriptions are off by default because they are the expensive field.
|
|
259
|
+
One column, the cheap path:
|
|
156
260
|
|
|
157
261
|
```json
|
|
158
262
|
{
|
|
159
|
-
"listId": "3234567890123456789"
|
|
160
|
-
"limit": 25,
|
|
161
|
-
"offset": 0
|
|
263
|
+
"listId": "3234567890123456789"
|
|
162
264
|
}
|
|
163
265
|
```
|
|
164
266
|
|
|
165
|
-
|
|
267
|
+
`GET /api/lists/:id` returns **every card of that column in one response**, with
|
|
268
|
+
no cursor to follow: `total` is the real size of the column and the answer
|
|
269
|
+
carries `truncated: false`. `boardId` is not needed — the list names its own
|
|
270
|
+
board.
|
|
271
|
+
|
|
272
|
+
Search a board:
|
|
166
273
|
|
|
167
274
|
```json
|
|
168
275
|
{
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
276
|
+
"boardId": "2234567890123456789",
|
|
277
|
+
"query": "export timeout",
|
|
278
|
+
"labelName": "ready",
|
|
279
|
+
"descriptionChars": 200
|
|
173
280
|
}
|
|
174
281
|
```
|
|
175
282
|
|
|
176
283
|
| Field | Default | Notes |
|
|
177
284
|
|---|---|---|
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
285
|
+
| `boardId` | `PLANKA_DEFAULT_BOARD_ID` | Ignored when `listId` is given |
|
|
286
|
+
| `listId` | none | One column, one request, never truncated |
|
|
287
|
+
| `listName` | none | Column-name fragment; costs a board read |
|
|
288
|
+
| `query` | none | Name **and** description. Several words are ANDed; a leading `/` makes the rest a case-insensitive regular expression |
|
|
289
|
+
| `labelName` | none | Exact label name, case- and accent-insensitive. `labelFilter` is still accepted as an alias, but is no longer announced in the schema |
|
|
290
|
+
| `labelIds` | none | Label IDs, OR between them |
|
|
291
|
+
| `userIds` | none | Members **or** task assignees, OR between them |
|
|
292
|
+
| `withLabels` | `true` | `false` skips the extra board read on the `listId` path |
|
|
293
|
+
| `detail` | `"digest"` | `"full"` returns every field of the card, description included |
|
|
294
|
+
| `descriptionChars` | `0` | Per-match description preview, in characters. `withDescription: true` is still accepted (it means 200 characters) but is no longer announced in the schema |
|
|
181
295
|
| `limit` | `25` | Maximum `200` |
|
|
182
296
|
| `offset` | `0` | Pass the previous response's `nextOffset` to page |
|
|
183
297
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
Get list IDs from `planka_list_lists` or `planka_get_structure`.
|
|
191
|
-
|
|
192
|
-
### `planka_find_cards`
|
|
193
|
-
|
|
194
|
-
Searches a board's cards by label, by text, or by column, and returns only the
|
|
195
|
-
matches.
|
|
196
|
-
|
|
197
|
-
Sweep every card carrying a label:
|
|
198
|
-
|
|
199
|
-
```json
|
|
200
|
-
{
|
|
201
|
-
"boardId": "2234567890123456789",
|
|
202
|
-
"labelName": "ready"
|
|
203
|
-
}
|
|
204
|
-
```
|
|
298
|
+
The digest of a card is `id`, `name`, `labels`, `labelIds`, `members`, `tasks`
|
|
299
|
+
(`"2/5"`), `dueDate`, `commentsTotal` and `updatedAt`. Empty values are left out
|
|
300
|
+
entirely, and the column is **not repeated on every card**: when the search is
|
|
301
|
+
scoped to one column the response carries `list: {id, name}` once, and only a
|
|
302
|
+
result spanning several columns puts `listId` and `listName` on each match.
|
|
205
303
|
|
|
206
|
-
|
|
207
|
-
|
|
304
|
+
**The two digests share one vocabulary.** `labelIds` is in the digest, not only
|
|
305
|
+
in `detail: "full"`, because `planka_set_card_labels` takes label *ids* and only
|
|
306
|
+
ids: publishing the names alone sent the caller back to `planka_board_summary`
|
|
307
|
+
for a value `find_cards` already had. Where a field exists in both
|
|
308
|
+
`planka_find_cards` and `planka_get_card` it means the same thing and carries the
|
|
309
|
+
same name:
|
|
208
310
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
311
|
+
| Concept | `planka_find_cards` (digest) | `planka_get_card` (digest) |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| column | `listId` (multi-column results) + `listName` | `card.listId` + `card.listName` (`null` plus `listNameUnavailable` when the board could not be read) |
|
|
314
|
+
| labels | `labels` (names) + `labelIds` | `labels: [{id, name}]` |
|
|
315
|
+
| members | `members` (names) | `members: [{id, name}]` |
|
|
316
|
+
| tasks | `tasks: "2/5"` | `tasks: {done, total, lists}` |
|
|
317
|
+
| description | `description`, only when `descriptionChars > 0` | `descriptionPreview` + `descriptionChars` + `descriptionTruncated` |
|
|
216
318
|
|
|
217
|
-
|
|
218
|
-
required; an unfiltered search would just be a slower `planka_get_board`.
|
|
319
|
+
The response says where it came from and what it could not see:
|
|
219
320
|
|
|
220
|
-
| Field |
|
|
221
|
-
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
321
|
+
| Field | Meaning |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `source: "list"` | Read through the list show: one request, whole column |
|
|
324
|
+
| `truncated: false` | Guaranteed on the list path |
|
|
325
|
+
| `source: "board"` | Filtered over one board read |
|
|
326
|
+
| `excludesArchived: true` | The board read has no archive/trash cards |
|
|
327
|
+
| `labelsResolved: false` | Label names were not fetched, so `labels` is absent |
|
|
328
|
+
| `warnings` | A label, column or filter that matched nothing, with what does exist |
|
|
228
329
|
|
|
229
|
-
|
|
230
|
-
and carries a `warning` naming the labels that do exist. The same happens for a
|
|
231
|
-
`listId` that is not on the board.
|
|
330
|
+
Get list IDs from `planka_board_summary` or `planka_get_structure`.
|
|
232
331
|
|
|
233
332
|
## Cards
|
|
234
333
|
|
|
@@ -254,12 +353,21 @@ same tool.
|
|
|
254
353
|
}
|
|
255
354
|
```
|
|
256
355
|
|
|
257
|
-
Only `listId` and `name` are required.
|
|
258
|
-
|
|
356
|
+
Only `listId` and `name` are required.
|
|
357
|
+
|
|
358
|
+
The result reports `tasksRequested` / `tasksCreated` and `labelsRequested` /
|
|
359
|
+
`labelsAttached` **counted from a re-read of the card**, plus `finalLabelIds`.
|
|
360
|
+
If a follow-up step fails the card is still created: `card.id` always comes
|
|
361
|
+
back, together with `partialFailure` (`tasks`, `tasksMissing`, `labelIds`) and
|
|
362
|
+
`isError`. Never retry a `planka_create_card` that errored without reading the
|
|
363
|
+
id it returned — that is how boards grow duplicates. Every label failing is an
|
|
364
|
+
error, not a `success: true` with zero labels.
|
|
259
365
|
|
|
260
366
|
### `planka_get_card`
|
|
261
367
|
|
|
262
|
-
|
|
368
|
+
Returns a **digest** by default, and the full record only when asked for. On a
|
|
369
|
+
real card of the OmnicoreOS board the digest is **1.3 KB where the old
|
|
370
|
+
everything-always answer was 13.2 KB** — a 90 % cut with nothing important lost.
|
|
263
371
|
|
|
264
372
|
```json
|
|
265
373
|
{
|
|
@@ -267,6 +375,38 @@ Reads full card details, including tasks, comments, labels, and attachments.
|
|
|
267
375
|
}
|
|
268
376
|
```
|
|
269
377
|
|
|
378
|
+
The digest carries: `id`, `name`, `listId` **and `listName`**, `boardId`,
|
|
379
|
+
`labels` (ids **with names**), `members`, `tasks` as `{done, total, lists:[…]}`
|
|
380
|
+
— one entry per checklist, never flattened —, `attachmentsCount`,
|
|
381
|
+
`commentsTotal` (Planka's own counter), `dueDate`, `isDueCompleted`, `isClosed`,
|
|
382
|
+
`updatedAt` and `descriptionPreview` (first 200 chars, with `descriptionChars`
|
|
383
|
+
and `descriptionTruncated`).
|
|
384
|
+
|
|
385
|
+
| Parameter | Default | What it does |
|
|
386
|
+
|---|---|---|
|
|
387
|
+
| `detail` | `"digest"` | `"full"` adds the whole description, every task item, the attachment list and the custom field values |
|
|
388
|
+
| `withComments` | `0` | A number returns the N most recent comments; `"all"` pages through every one of them |
|
|
389
|
+
| `descriptionChars` | `200` | Size of the description preview (max 2000). Ignored when `detail` is `"full"` |
|
|
390
|
+
|
|
391
|
+
```json
|
|
392
|
+
{
|
|
393
|
+
"cardId": "5234567890123456789",
|
|
394
|
+
"detail": "full",
|
|
395
|
+
"withComments": 10
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Comments are **off by default**: they are the biggest and most variable part of
|
|
400
|
+
a card, and most reads do not need them. When they are requested they come back
|
|
401
|
+
oldest first — reading order — with `commentsReturned`, `commentsHasMore`,
|
|
402
|
+
`nextBeforeId` and, for `"all"`, `commentsTruncated` when the card has more than
|
|
403
|
+
the page ceiling.
|
|
404
|
+
|
|
405
|
+
`listName` and the label names come from one board read, cached for a minute.
|
|
406
|
+
If that read fails the names are simply absent; the card is never lost with
|
|
407
|
+
them. If the comments endpoint fails, the tool ERRORS: it never reports an empty
|
|
408
|
+
comment list it could not actually read.
|
|
409
|
+
|
|
270
410
|
### `planka_update_card`
|
|
271
411
|
|
|
272
412
|
Updates only the fields present in the request.
|
|
@@ -283,6 +423,10 @@ Updates only the fields present in the request.
|
|
|
283
423
|
|
|
284
424
|
Use `null` to clear `description` or `dueDate`.
|
|
285
425
|
|
|
426
|
+
Passing only `cardId` is rejected (`nothing to do`): an empty PATCH used to
|
|
427
|
+
answer `success: true` without changing anything. The result re-reads the card
|
|
428
|
+
and lists in `notLanded` any field that did not actually change.
|
|
429
|
+
|
|
286
430
|
### `planka_move_card`
|
|
287
431
|
|
|
288
432
|
Moves a card to a list and optionally assigns its numeric position.
|
|
@@ -291,15 +435,22 @@ Moves a card to a list and optionally assigns its numeric position.
|
|
|
291
435
|
{
|
|
292
436
|
"cardId": "5234567890123456789",
|
|
293
437
|
"listId": "6234567890123456789",
|
|
438
|
+
"boardId": "2234567890123456789",
|
|
294
439
|
"position": 65536
|
|
295
440
|
}
|
|
296
441
|
```
|
|
297
442
|
|
|
298
|
-
If `position` is omitted,
|
|
443
|
+
If `position` is omitted, the card goes to the end of the list. `boardId` is
|
|
444
|
+
only for moving a card to another board, and the `listId` must belong to it.
|
|
445
|
+
|
|
446
|
+
The card is re-read: if it did not end up in the requested list the result is
|
|
447
|
+
`verified: false` + `isError`, with the list it is actually in.
|
|
299
448
|
|
|
300
449
|
### `planka_delete_card`
|
|
301
450
|
|
|
302
|
-
Permanently deletes a card.
|
|
451
|
+
Permanently deletes a card, with its tasks, comments and attachments. The
|
|
452
|
+
deletion is confirmed by re-reading the card: a 404 is the proof, and anything
|
|
453
|
+
else comes back as `verified: false` + `isError`.
|
|
303
454
|
|
|
304
455
|
```json
|
|
305
456
|
{
|
|
@@ -323,69 +474,101 @@ Adds one or more checklist tasks to a card.
|
|
|
323
474
|
}
|
|
324
475
|
```
|
|
325
476
|
|
|
477
|
+
The result reports `tasksCreated` counted from a re-read of the card, not from
|
|
478
|
+
the request.
|
|
479
|
+
|
|
326
480
|
### `planka_update_task`
|
|
327
481
|
|
|
328
|
-
Renames a task, changes completion, or does both.
|
|
482
|
+
Renames a task, changes completion, or does both. Passing only `taskId` is
|
|
483
|
+
rejected (`nothing to do`).
|
|
329
484
|
|
|
330
485
|
```json
|
|
331
486
|
{
|
|
332
487
|
"taskId": "7234567890123456789",
|
|
488
|
+
"cardId": "5234567890123456789",
|
|
333
489
|
"name": "Add and run a regression test",
|
|
334
490
|
"isCompleted": true
|
|
335
491
|
}
|
|
336
492
|
```
|
|
337
493
|
|
|
494
|
+
`cardId` is optional but strongly recommended: Planka has no GET for a single
|
|
495
|
+
task, so the card is the only place the change can be verified. Without it the
|
|
496
|
+
result carries `verificationUnavailable` instead of a proven success.
|
|
497
|
+
|
|
338
498
|
### `planka_delete_task`
|
|
339
499
|
|
|
340
|
-
Deletes one checklist task.
|
|
500
|
+
Deletes one checklist task. Same rule: pass `cardId` to have the deletion
|
|
501
|
+
confirmed against the card.
|
|
341
502
|
|
|
342
503
|
```json
|
|
343
504
|
{
|
|
344
|
-
"taskId": "7234567890123456789"
|
|
505
|
+
"taskId": "7234567890123456789",
|
|
506
|
+
"cardId": "5234567890123456789"
|
|
345
507
|
}
|
|
346
508
|
```
|
|
347
509
|
|
|
348
510
|
## Labels
|
|
349
511
|
|
|
350
|
-
### `
|
|
351
|
-
|
|
352
|
-
Creates, updates, or deletes a board label. The `action` determines which IDs
|
|
353
|
-
are required.
|
|
512
|
+
### `planka_create_label`
|
|
354
513
|
|
|
355
|
-
|
|
514
|
+
Creates a board label.
|
|
356
515
|
|
|
357
516
|
```json
|
|
358
517
|
{
|
|
359
|
-
"action": "create",
|
|
360
518
|
"boardId": "2234567890123456789",
|
|
361
519
|
"name": "needs-decision",
|
|
362
|
-
"color": "berry-red"
|
|
520
|
+
"color": "berry-red",
|
|
521
|
+
"position": 65536
|
|
363
522
|
}
|
|
364
523
|
```
|
|
365
524
|
|
|
366
|
-
|
|
525
|
+
`position` is optional; Planka appends the label when it is omitted.
|
|
526
|
+
|
|
527
|
+
`boardId`, `name` and `color` are `required` in the JSON Schema, so a client
|
|
528
|
+
rejects an incomplete call before it leaves. With `PLANKA_DEFAULT_BOARD_ID` set,
|
|
529
|
+
`boardId` drops out of `required` and is filled in.
|
|
530
|
+
|
|
531
|
+
`color` is a real JSON Schema `enum` of the 43 Planka 2.x colors **here and only
|
|
532
|
+
here** — repeating the enum on `planka_update_label` and `planka_manage_labels`
|
|
533
|
+
cost ~650 characters of every session for the same list. Those two take the same
|
|
534
|
+
values, validated server-side with a message that spells them out, and the
|
|
535
|
+
`planka://labels/colors` resource serves the list on demand.
|
|
536
|
+
A bad color is rejected before the call leaves the client. Unknown colors are still accepted
|
|
537
|
+
on reads, so one new upstream color cannot break a board response. Run
|
|
538
|
+
`planka_board_summary` to see the colors already in use.
|
|
539
|
+
|
|
540
|
+
The write is verified against a re-read of the board's labels.
|
|
541
|
+
|
|
542
|
+
### `planka_update_label`
|
|
543
|
+
|
|
544
|
+
Renames a label or changes its color.
|
|
367
545
|
|
|
368
546
|
```json
|
|
369
547
|
{
|
|
370
|
-
"action": "update",
|
|
371
548
|
"labelId": "4234567890123456789",
|
|
372
549
|
"name": "decision-needed",
|
|
373
550
|
"color": "pumpkin-orange"
|
|
374
551
|
}
|
|
375
552
|
```
|
|
376
553
|
|
|
377
|
-
|
|
554
|
+
A call carrying only `labelId` is rejected (`nothing to do`) instead of sending
|
|
555
|
+
an empty `PATCH` that Planka would answer `200` to. The response reports the
|
|
556
|
+
board's copy of the label, not the `PATCH` echo.
|
|
557
|
+
|
|
558
|
+
### `planka_delete_label`
|
|
559
|
+
|
|
560
|
+
Deletes a board label.
|
|
378
561
|
|
|
379
562
|
```json
|
|
380
563
|
{
|
|
381
|
-
"action": "delete",
|
|
382
564
|
"labelId": "4234567890123456789"
|
|
383
565
|
}
|
|
384
566
|
```
|
|
385
567
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
568
|
+
> **DESTRUCTIVE.** The label disappears from every card that carried it.
|
|
569
|
+
|
|
570
|
+
Confirmed by re-reading the board: a label still there afterwards comes back as
|
|
571
|
+
`verified: false` + `isError`.
|
|
389
572
|
|
|
390
573
|
### `planka_set_card_labels`
|
|
391
574
|
|
|
@@ -413,10 +596,11 @@ absent, failed, and final label IDs. This makes silent no-op writes visible.
|
|
|
413
596
|
|
|
414
597
|
## Comments
|
|
415
598
|
|
|
416
|
-
### `
|
|
599
|
+
### `planka_create_comment`
|
|
417
600
|
|
|
418
601
|
Adds a Markdown-capable comment through Planka 2.x's dedicated card action
|
|
419
|
-
endpoint.
|
|
602
|
+
endpoint. (Formerly `planka_add_comment`, which still works — see
|
|
603
|
+
[Deprecated aliases](#deprecated-aliases).)
|
|
420
604
|
|
|
421
605
|
```json
|
|
422
606
|
{
|
|
@@ -425,59 +609,81 @@ endpoint.
|
|
|
425
609
|
}
|
|
426
610
|
```
|
|
427
611
|
|
|
612
|
+
The comment is re-read from the card before the tool reports success.
|
|
613
|
+
|
|
428
614
|
### `planka_get_comments`
|
|
429
615
|
|
|
430
|
-
Reads the dedicated comment collection for one card
|
|
616
|
+
Reads the dedicated comment collection for one card, **oldest first**, one page
|
|
617
|
+
at a time. Planka serves comments 50 at a time and ignores `?limit`/`?page`; the
|
|
618
|
+
only cursor is `beforeId`, and this tool exposes it honestly.
|
|
431
619
|
|
|
432
620
|
```json
|
|
433
621
|
{
|
|
434
|
-
"cardId": "5234567890123456789"
|
|
622
|
+
"cardId": "5234567890123456789",
|
|
623
|
+
"limit": 20,
|
|
624
|
+
"order": "asc"
|
|
435
625
|
}
|
|
436
626
|
```
|
|
437
627
|
|
|
628
|
+
| Parameter | Default | What it does |
|
|
629
|
+
|---|---|---|
|
|
630
|
+
| `limit` | `20` | Comments to return, max 50 (Planka's page) |
|
|
631
|
+
| `beforeId` | — | Cursor: returns comments **older** than that comment id |
|
|
632
|
+
| `all` | `false` | Walks every page, up to 10 of them (500 comments) |
|
|
633
|
+
| `order` | `"asc"` | `"asc"` oldest first (reading order), `"desc"` newest first |
|
|
634
|
+
|
|
635
|
+
The answer carries `commentCount` (**what this response holds — never the card's
|
|
636
|
+
total**), `hasMore`, `nextBeforeId` and, with `all`, `truncated` plus the reason.
|
|
637
|
+
Continue by passing the `nextBeforeId` you were handed:
|
|
638
|
+
|
|
639
|
+
```json
|
|
640
|
+
{ "cardId": "5234567890123456789", "beforeId": "1034567890123456789" }
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
The card's real total comes from `planka_get_card` (`commentsTotal`), which is
|
|
644
|
+
Planka's own counter.
|
|
645
|
+
|
|
438
646
|
This does not rely on `included.comments`, which is empty in affected Planka 2.x
|
|
439
647
|
responses even when the card has comments.
|
|
440
648
|
|
|
441
|
-
### `
|
|
442
|
-
|
|
443
|
-
Edits or deletes an existing comment. Get comment IDs from
|
|
444
|
-
`planka_get_comments`.
|
|
649
|
+
### `planka_update_comment`
|
|
445
650
|
|
|
446
|
-
|
|
651
|
+
Rewrites a comment. Get comment IDs from `planka_get_comments`.
|
|
447
652
|
|
|
448
653
|
```json
|
|
449
654
|
{
|
|
450
|
-
"action": "update",
|
|
451
655
|
"commentId": "1034567890123456789",
|
|
452
656
|
"text": "Verification complete: unit tests and the manual export both passed."
|
|
453
657
|
}
|
|
454
658
|
```
|
|
455
659
|
|
|
456
|
-
|
|
660
|
+
`commentId` and `text` are both `required`. Planka only lets the authenticated
|
|
661
|
+
user modify comments that user wrote, so an agent can correct its own notes but
|
|
662
|
+
not someone else's. The card's comments are re-read before the tool reports: an
|
|
663
|
+
edit that did not stick comes back as `verified: false` + `isError`.
|
|
664
|
+
|
|
665
|
+
### `planka_delete_comment`
|
|
666
|
+
|
|
667
|
+
Deletes a comment.
|
|
457
668
|
|
|
458
669
|
```json
|
|
459
670
|
{
|
|
460
|
-
"action": "delete",
|
|
461
671
|
"commentId": "1034567890123456789"
|
|
462
672
|
}
|
|
463
673
|
```
|
|
464
674
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
own notes but not someone else's.
|
|
675
|
+
Confirmed by re-reading the card's comments; a comment still present afterwards
|
|
676
|
+
comes back as `verified: false` + `isError`.
|
|
468
677
|
|
|
469
678
|
## Lists
|
|
470
679
|
|
|
471
|
-
### `
|
|
680
|
+
### `planka_create_list`
|
|
472
681
|
|
|
473
|
-
Creates
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
Create:
|
|
682
|
+
Creates a board list (column). Creation sends an explicit Planka 2.x list
|
|
683
|
+
`type`; it defaults to `active`.
|
|
477
684
|
|
|
478
685
|
```json
|
|
479
686
|
{
|
|
480
|
-
"action": "create",
|
|
481
687
|
"boardId": "2234567890123456789",
|
|
482
688
|
"name": "Pending",
|
|
483
689
|
"position": 65536,
|
|
@@ -485,11 +691,16 @@ Create:
|
|
|
485
691
|
}
|
|
486
692
|
```
|
|
487
693
|
|
|
488
|
-
|
|
694
|
+
`boardId` and `name` are `required` (`boardId` drops out with
|
|
695
|
+
`PLANKA_DEFAULT_BOARD_ID`). Only `active` and `closed` are accepted for list
|
|
696
|
+
writes; internal Planka list types such as archive and trash remain readable.
|
|
697
|
+
|
|
698
|
+
### `planka_update_list`
|
|
699
|
+
|
|
700
|
+
Renames, repositions or reclassifies a column.
|
|
489
701
|
|
|
490
702
|
```json
|
|
491
703
|
{
|
|
492
|
-
"action": "update",
|
|
493
704
|
"listId": "3234567890123456789",
|
|
494
705
|
"name": "Ready",
|
|
495
706
|
"position": 131072,
|
|
@@ -497,17 +708,26 @@ Update:
|
|
|
497
708
|
}
|
|
498
709
|
```
|
|
499
710
|
|
|
500
|
-
|
|
711
|
+
A call carrying only `listId` is rejected (`nothing to do`). The response reports
|
|
712
|
+
the board's copy of the list.
|
|
713
|
+
|
|
714
|
+
### `planka_delete_list`
|
|
715
|
+
|
|
716
|
+
Deletes a column.
|
|
501
717
|
|
|
502
718
|
```json
|
|
503
719
|
{
|
|
504
|
-
"action": "delete",
|
|
505
720
|
"listId": "3234567890123456789"
|
|
506
721
|
}
|
|
507
722
|
```
|
|
508
723
|
|
|
509
|
-
|
|
510
|
-
|
|
724
|
+
> **DESTRUCTIVE — it takes the cards with it.** Deleting a list deletes every
|
|
725
|
+
> card inside it, and Planka does not ask. Move the cards out first with
|
|
726
|
+
> `planka_move_list_cards` or `planka_move_card`, or delete a column you created
|
|
727
|
+
> yourself. This cannot be undone.
|
|
728
|
+
|
|
729
|
+
Confirmed by re-reading the board: a list still there afterwards comes back as
|
|
730
|
+
`verified: false` + `isError`.
|
|
511
731
|
|
|
512
732
|
## Attachments
|
|
513
733
|
|
|
@@ -573,15 +793,379 @@ What comes back depends on the content type:
|
|
|
573
793
|
|
|
574
794
|
### `planka_delete_attachment`
|
|
575
795
|
|
|
576
|
-
Permanently deletes one attachment.
|
|
577
|
-
by card.
|
|
796
|
+
Permanently deletes one attachment.
|
|
578
797
|
|
|
579
798
|
```json
|
|
580
799
|
{
|
|
581
|
-
"attachmentId": "1134567890123456789"
|
|
800
|
+
"attachmentId": "1134567890123456789",
|
|
801
|
+
"cardId": "5234567890123456789"
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
`cardId` is optional: Planka normally echoes the deleted attachment (which
|
|
806
|
+
carries its card), and that is what makes the deletion verifiable. Pass it when
|
|
807
|
+
you have it; if neither is available the result says
|
|
808
|
+
`verificationUnavailable` rather than claiming success.
|
|
809
|
+
|
|
810
|
+
## Activity
|
|
811
|
+
|
|
812
|
+
Planka records **six** kinds of event, and only six
|
|
813
|
+
(`Action.Types` in `server/api/models/Action.js`):
|
|
814
|
+
|
|
815
|
+
`createCard` · `moveCard` · `addMemberToCard` · `removeMemberFromCard` ·
|
|
816
|
+
`completeTask` · `uncompleteTask`
|
|
817
|
+
|
|
818
|
+
There is no event for a comment, a rename or a description edit. A card with an
|
|
819
|
+
empty history was not necessarily untouched — it was only never moved, assigned
|
|
820
|
+
or checked off. Both tools say so in a `note` when they return nothing.
|
|
821
|
+
|
|
822
|
+
Both endpoints return at most **50 events** (a Planka constant, not a query
|
|
823
|
+
parameter) and page backwards with a cursor.
|
|
824
|
+
|
|
825
|
+
### `planka_card_history`
|
|
826
|
+
|
|
827
|
+
One card's log, newest first, as one human line per event.
|
|
828
|
+
|
|
829
|
+
```json
|
|
830
|
+
{
|
|
831
|
+
"cardId": "5234567890123456789",
|
|
832
|
+
"limit": 20,
|
|
833
|
+
"beforeId": "7234567890123456789"
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
`limit` defaults to 20 and is capped at 50. Response:
|
|
838
|
+
|
|
839
|
+
```json
|
|
840
|
+
{
|
|
841
|
+
"cardId": "5234567890123456789",
|
|
842
|
+
"cardName": "Retire the nginx failover",
|
|
843
|
+
"returned": 2,
|
|
844
|
+
"events": [
|
|
845
|
+
{
|
|
846
|
+
"id": "1855662785539605720",
|
|
847
|
+
"type": "moveCard",
|
|
848
|
+
"at": "2026-09-03T02:50:42.309Z",
|
|
849
|
+
"user": "Claude",
|
|
850
|
+
"summary": "moved the card from \"In progress\" to \"Review\""
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
"id": "1855530665752134803",
|
|
854
|
+
"type": "createCard",
|
|
855
|
+
"at": "2026-09-02T22:28:12.404Z",
|
|
856
|
+
"user": "Claude",
|
|
857
|
+
"summary": "created the card in \"Backlog\""
|
|
858
|
+
}
|
|
859
|
+
],
|
|
860
|
+
"hasMore": false
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
`hasMore` with `nextBeforeId` means there is older activity: call again passing
|
|
865
|
+
`beforeId: nextBeforeId`. List, user and task names come from the event itself —
|
|
866
|
+
no extra read is made to resolve them.
|
|
867
|
+
|
|
868
|
+
### `planka_board_activity`
|
|
869
|
+
|
|
870
|
+
What moved on a whole board, grouped by card. The call to make when resuming a
|
|
871
|
+
session someone else (or a past you) left behind.
|
|
872
|
+
|
|
873
|
+
```json
|
|
874
|
+
{
|
|
875
|
+
"boardId": "2234567890123456789",
|
|
876
|
+
"since": "2026-09-01T00:00:00.000Z",
|
|
877
|
+
"limit": 50,
|
|
878
|
+
"types": ["moveCard", "createCard"]
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
| Field | Meaning |
|
|
883
|
+
|---|---|
|
|
884
|
+
| `since` | ISO 8601. The sweep walks pages backwards and stops at it. |
|
|
885
|
+
| `limit` | Maximum events returned. Default 50, capped at 250. |
|
|
886
|
+
| `types` | Keep only these event types. |
|
|
887
|
+
|
|
888
|
+
The sweep is capped at **5 pages** (250 events). Response:
|
|
889
|
+
|
|
890
|
+
```json
|
|
891
|
+
{
|
|
892
|
+
"boardId": "2234567890123456789",
|
|
893
|
+
"since": "2026-09-01T00:00:00.000Z",
|
|
894
|
+
"pagesFetched": 1,
|
|
895
|
+
"scanned": 50,
|
|
896
|
+
"returned": 5,
|
|
897
|
+
"cardsTouched": 1,
|
|
898
|
+
"truncated": true,
|
|
899
|
+
"nextBeforeId": "1855558191031518415",
|
|
900
|
+
"cards": [
|
|
901
|
+
{
|
|
902
|
+
"cardId": "5234567890123456789",
|
|
903
|
+
"cardName": "Retire the nginx failover",
|
|
904
|
+
"events": [
|
|
905
|
+
{
|
|
906
|
+
"id": "1855662785539605720",
|
|
907
|
+
"type": "moveCard",
|
|
908
|
+
"at": "2026-09-03T02:50:42.309Z",
|
|
909
|
+
"user": "Claude",
|
|
910
|
+
"summary": "moved the card from \"In progress\" to \"Review\""
|
|
911
|
+
}
|
|
912
|
+
]
|
|
913
|
+
}
|
|
914
|
+
]
|
|
915
|
+
}
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
`truncated: true` means older activity exists that this answer does not carry —
|
|
919
|
+
either `limit` or the page cap stopped the sweep. It is `false` only when the
|
|
920
|
+
log was exhausted or `since` was reached.
|
|
921
|
+
|
|
922
|
+
## People
|
|
923
|
+
|
|
924
|
+
### `planka_list_users`
|
|
925
|
+
|
|
926
|
+
Who can be assigned, with the IDs `planka_set_card_members` needs.
|
|
927
|
+
|
|
928
|
+
```json
|
|
929
|
+
{
|
|
930
|
+
"boardId": "2234567890123456789",
|
|
931
|
+
"detail": "digest"
|
|
932
|
+
}
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
`GET /api/users` is admin-only in Planka. When this account is a plain
|
|
936
|
+
`boardUser` the call is refused and the tool falls back to the **board's own
|
|
937
|
+
members**, which is the assignable set anyway. The fallback is never silent:
|
|
938
|
+
|
|
939
|
+
| Field | Meaning |
|
|
940
|
+
|---|---|
|
|
941
|
+
| `source: "instance"` | The instance roster. |
|
|
942
|
+
| `source: "board"` + `note` | The instance roster was refused; these are the board's members, each with `boardRole` and `canComment`. |
|
|
943
|
+
|
|
944
|
+
`boardId` is only used by the fallback. `detail: "full"` adds email addresses,
|
|
945
|
+
which the digest deliberately omits.
|
|
946
|
+
|
|
947
|
+
```json
|
|
948
|
+
{
|
|
949
|
+
"source": "instance",
|
|
950
|
+
"total": 4,
|
|
951
|
+
"users": [
|
|
952
|
+
{ "id": "1830226958957413619", "name": "Fede", "username": "fede", "role": "boardUser" }
|
|
953
|
+
]
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### `planka_whoami`
|
|
958
|
+
|
|
959
|
+
The first call to make when something answers `404` that should not: it names
|
|
960
|
+
the account, what it may do on the board, and what the local policy switched
|
|
961
|
+
off — the three usual causes of a confusing refusal.
|
|
962
|
+
|
|
963
|
+
```json
|
|
964
|
+
{
|
|
965
|
+
"user": { "id": "1829420929365574677", "name": "Claude", "username": "claude", "role": "admin" },
|
|
966
|
+
"plankaVersion": "2.2.1",
|
|
967
|
+
"authMode": "password",
|
|
968
|
+
"boardMembership": { "boardId": "2234567890123456789", "role": "editor", "canComment": null },
|
|
969
|
+
"policy": {
|
|
970
|
+
"defaultBoardId": null,
|
|
971
|
+
"allowedBoardIds": null,
|
|
972
|
+
"allowedProjectIds": null,
|
|
973
|
+
"readOnly": false,
|
|
974
|
+
"disabledTools": [],
|
|
975
|
+
"protectedListIds": []
|
|
976
|
+
}
|
|
582
977
|
}
|
|
583
978
|
```
|
|
584
979
|
|
|
980
|
+
`plankaVersion` comes from `/api/bootstrap`, the only endpoint in 2.x that
|
|
981
|
+
carries it. A version that does not start with `2.` adds a `versionWarning`:
|
|
982
|
+
this server targets 2.x and the paths it uses may not exist elsewhere.
|
|
983
|
+
`boardMembership` is read from `/api/projects` (~3 kB), not from the board show
|
|
984
|
+
(~900 kB) — both carry the memberships.
|
|
985
|
+
|
|
986
|
+
### `planka_set_card_members`
|
|
987
|
+
|
|
988
|
+
Assigns and unassigns people on a card, then re-reads the card and reports the
|
|
989
|
+
verified final state. The mirror of `planka_set_card_labels`, argument for
|
|
990
|
+
argument.
|
|
991
|
+
|
|
992
|
+
```json
|
|
993
|
+
{
|
|
994
|
+
"cardId": "5234567890123456789",
|
|
995
|
+
"add": ["1830226958957413619"],
|
|
996
|
+
"remove": ["1830228857995658485"],
|
|
997
|
+
"addUserNames": ["fede"],
|
|
998
|
+
"removeUserNames": ["Pat"],
|
|
999
|
+
"boardId": "2234567890123456789"
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
Names are matched exactly and case-insensitively, on `username` first and then
|
|
1004
|
+
on `name`. An ambiguous name is **not** guessed — assigning the wrong person is
|
|
1005
|
+
worse than failing — and comes back in `failed`. Removals are applied before
|
|
1006
|
+
additions, so one call can hand a card from one person to another.
|
|
1007
|
+
|
|
1008
|
+
The response distinguishes `added`, `removed`, `alreadyPresent`, `notPresent`,
|
|
1009
|
+
`failed` and `finalMemberIds`. A `404` on an addition almost always means the
|
|
1010
|
+
user is not a member of that board; the reason says so.
|
|
1011
|
+
|
|
1012
|
+
## Card operations
|
|
1013
|
+
|
|
1014
|
+
### `planka_duplicate_card`
|
|
1015
|
+
|
|
1016
|
+
Copies a card through Planka's own `duplicate` endpoint, which carries over the
|
|
1017
|
+
card's tasks, labels, members and custom field values. **Attachments are not
|
|
1018
|
+
copied.**
|
|
1019
|
+
|
|
1020
|
+
```json
|
|
1021
|
+
{
|
|
1022
|
+
"cardId": "5234567890123456789",
|
|
1023
|
+
"listId": "3234567890123456789",
|
|
1024
|
+
"name": "Retire the nginx failover (round 2)",
|
|
1025
|
+
"position": 65536
|
|
1026
|
+
}
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
Without `listId` the copy lands in the original's column; without `name` Planka
|
|
1030
|
+
appends its own suffix. `boardId` copies across boards and then `listId` is
|
|
1031
|
+
required — a card cannot land on a board without a column. The counts in
|
|
1032
|
+
`copied` come from re-reading the new card, not from the write's response.
|
|
1033
|
+
|
|
1034
|
+
```json
|
|
1035
|
+
{
|
|
1036
|
+
"success": true,
|
|
1037
|
+
"verified": true,
|
|
1038
|
+
"card": { "id": "6234567890123456789", "name": "…", "listId": "3234567890123456789" },
|
|
1039
|
+
"sourceCardId": "5234567890123456789",
|
|
1040
|
+
"copied": { "tasks": 2, "labels": 1, "members": 1, "attachments": 0 }
|
|
1041
|
+
}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### `planka_archive_card`
|
|
1045
|
+
|
|
1046
|
+
Planka has no "archive" verb. Every board owns two invisible lists besides its
|
|
1047
|
+
columns — one of type `archive` and one of type `trash` — with no name, no
|
|
1048
|
+
position, and no `GET` of their own (`GET /api/lists/:id` answers 404 for
|
|
1049
|
+
them). Archiving is a move into the first of those, and this tool resolves it
|
|
1050
|
+
from the board.
|
|
1051
|
+
|
|
1052
|
+
```json
|
|
1053
|
+
{ "cardId": "5234567890123456789" }
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
To bring a card back, name the column it should land in:
|
|
1057
|
+
|
|
1058
|
+
```json
|
|
1059
|
+
{ "cardId": "5234567890123456789", "restoreToListId": "3234567890123456789" }
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
Two behaviours worth knowing, both verified against Planka 2.2.1:
|
|
1063
|
+
|
|
1064
|
+
- `isClosed` does **not** change when a card is archived, so it is useless as
|
|
1065
|
+
proof. The only honest verification is the card's `listId`.
|
|
1066
|
+
- An archived card has `position: null`, and Planka rejects a `PATCH` that only
|
|
1067
|
+
carries `listId` with *"Position must be present"*. The restore path always
|
|
1068
|
+
sends a position (`position` is accepted; it defaults to the end).
|
|
1069
|
+
|
|
1070
|
+
The archive response reports `previousListId`, which is the argument to pass to
|
|
1071
|
+
`restoreToListId` later. Archiving an already-archived card does not move it
|
|
1072
|
+
again: it answers `alreadyArchived: true` and names the column it came from.
|
|
1073
|
+
|
|
1074
|
+
### `planka_move_list_cards`
|
|
1075
|
+
|
|
1076
|
+
Moves **every** card of one column into another.
|
|
1077
|
+
|
|
1078
|
+
```json
|
|
1079
|
+
{
|
|
1080
|
+
"fromListId": "3234567890123456789",
|
|
1081
|
+
"toListId": "4234567890123456789",
|
|
1082
|
+
"boardId": "2234567890123456789"
|
|
1083
|
+
}
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
Planka has a bulk endpoint, `POST /api/lists/:id/move-cards`, and it is far
|
|
1087
|
+
narrower than its name: `server/api/controllers/lists/move-cards.js` in 2.2.1
|
|
1088
|
+
refuses everything except archiving a whole closed column.
|
|
1089
|
+
|
|
1090
|
+
```js
|
|
1091
|
+
if (list.type !== List.Types.CLOSED) throw NOT_ENOUGH_RIGHTS; // 403
|
|
1092
|
+
if (nextList.type !== List.Types.ARCHIVE) throw LIST_NOT_FOUND; // 404
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
So an ordinary column-to-column call comes back **403 "Not enough rights"**,
|
|
1096
|
+
which reads like a permission problem and is not one. This tool uses the bulk
|
|
1097
|
+
call for the single case it accepts and moves card by card everywhere else, one
|
|
1098
|
+
`PATCH` each, keeping each card's position so the relative order survives.
|
|
1099
|
+
`strategy` says which path ran: `"bulk"` or `"perCard"`.
|
|
1100
|
+
|
|
1101
|
+
Both columns are counted before and after, and success is only claimed when the
|
|
1102
|
+
numbers add up:
|
|
1103
|
+
|
|
1104
|
+
```json
|
|
1105
|
+
{
|
|
1106
|
+
"success": true,
|
|
1107
|
+
"verified": true,
|
|
1108
|
+
"fromListId": "3234567890123456789",
|
|
1109
|
+
"toListId": "4234567890123456789",
|
|
1110
|
+
"strategy": "perCard",
|
|
1111
|
+
"moved": 3,
|
|
1112
|
+
"fromRemaining": 0,
|
|
1113
|
+
"toTotal": 8,
|
|
1114
|
+
"expectedTotal": 8
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
| Field | Meaning |
|
|
1119
|
+
|---|---|
|
|
1120
|
+
| `strategy` | `bulk` (closed → archive, one call) or `perCard` (one PATCH per card) |
|
|
1121
|
+
| `moved` | Cards that actually left the source column |
|
|
1122
|
+
| `failed` | Cards whose PATCH was rejected, with the reason. Present only on a partial move |
|
|
1123
|
+
| `toCountUnavailable` | The destination is the archive, which Planka will not show; only the source could be counted |
|
|
1124
|
+
|
|
1125
|
+
`verified: false` with `isError` means the counts disagree — read both columns
|
|
1126
|
+
before retrying. An already empty source is not an error (`moved: 0`). A source
|
|
1127
|
+
column Planka refuses to show (`archive`, `trash`, or an id that does not exist)
|
|
1128
|
+
is rejected with a message that says so. `PLANKA_PROTECTED_LIST_IDS` guards both
|
|
1129
|
+
ends, so a protected column can be neither filled nor drained.
|
|
1130
|
+
|
|
1131
|
+
## Deprecated aliases
|
|
1132
|
+
|
|
1133
|
+
Seven names are kept alive only so callers written against them keep working.
|
|
1134
|
+
They add no behaviour: each routes its arguments to the tool that replaced it.
|
|
1135
|
+
|
|
1136
|
+
| Deprecated | Use instead |
|
|
1137
|
+
|---|---|
|
|
1138
|
+
| `planka_list_cards` | `planka_find_cards` with `listId` |
|
|
1139
|
+
| `planka_list_lists` | `planka_board_summary` with `cardsFrom: []` |
|
|
1140
|
+
| `planka_manage_labels` | `planka_create_label` · `planka_update_label` · `planka_delete_label` |
|
|
1141
|
+
| `planka_manage_lists` | `planka_create_list` · `planka_update_list` · `planka_delete_list` |
|
|
1142
|
+
| `planka_manage_comment` | `planka_update_comment` · `planka_delete_comment` (and `planka_create_comment`) |
|
|
1143
|
+
| `planka_add_comment` | `planka_create_comment` |
|
|
1144
|
+
| `planka_get_board` | `planka_board_summary` + `planka_find_cards` |
|
|
1145
|
+
|
|
1146
|
+
**Why they were split.** A single tool with an `action` enum cannot express
|
|
1147
|
+
"`boardId` is required when `action` is `create`" in JSON Schema. So each
|
|
1148
|
+
`manage_*` tool carried 20-40 lines of hand-written "which field does this
|
|
1149
|
+
action need?" checks — and a forgotten branch is exactly how `update` with only
|
|
1150
|
+
an id shipped an empty `PATCH` that Planka answered `200` to. One tool per
|
|
1151
|
+
action puts the answer back in `required`, where the client enforces it before
|
|
1152
|
+
the call is made and the server's generic presence check covers the rest.
|
|
1153
|
+
|
|
1154
|
+
The aliases borrow their target's `required` at dispatch time, so
|
|
1155
|
+
`planka_manage_labels` with `action: "update"` and no `labelId` is refused with a
|
|
1156
|
+
message naming `planka_update_label` rather than sending an empty write.
|
|
1157
|
+
|
|
1158
|
+
They also borrow their target's **access policy**: `planka_manage_lists` with
|
|
1159
|
+
`action: "delete"` is refused when `planka_delete_list` is in
|
|
1160
|
+
`PLANKA_DISABLED_TOOLS`, and `planka_add_comment` is refused when
|
|
1161
|
+
`planka_create_comment` is. An alias is a second name for a tool, never a way
|
|
1162
|
+
around the configuration that switched it off.
|
|
1163
|
+
|
|
1164
|
+
**Hiding them.** `PLANKA_HIDE_DEPRECATED=true` drops all seven from `tools/list`,
|
|
1165
|
+
which is worth ~6.8 kB (32,840 → 26,034 characters, measured on 0.3.0).
|
|
1166
|
+
They stay callable: a model working from a cached tool list, or from a skill that
|
|
1167
|
+
names one, gets the real behaviour instead of "unknown tool".
|
|
1168
|
+
|
|
585
1169
|
## When arguments are wrong
|
|
586
1170
|
|
|
587
1171
|
Invalid input is reported as an error result naming the tool, the field, what
|
|
@@ -625,9 +1209,9 @@ Unit tests validate schemas, request construction, and error formatting without
|
|
|
625
1209
|
network: run them with `npm test`.
|
|
626
1210
|
|
|
627
1211
|
The opt-in smoke test is the other half. It starts the built MCP server over stdio,
|
|
628
|
-
drives all
|
|
1212
|
+
drives all 41 tools through real MCP calls, and independently queries the raw
|
|
629
1213
|
Planka HTTP API after every mutation, so a tool that reports success on something
|
|
630
|
-
that never landed is caught here. It runs
|
|
1214
|
+
that never landed is caught here. It runs over 90 named checks.
|
|
631
1215
|
|
|
632
1216
|
```bash
|
|
633
1217
|
export PLANKA_BASE_URL="https://planka.example.com"
|